595 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/* acpi_thermal_rel.c driver for exporting ACPI thermal relationship3 *4 * Copyright (c) 2014 Intel Corp5 */6 7/*8 * Two functionalities included:9 * 1. Export _TRT, _ART, via misc device interface to the userspace.10 * 2. Provide parsing result to kernel drivers11 *12 */13#include <linux/init.h>14#include <linux/export.h>15#include <linux/module.h>16#include <linux/device.h>17#include <linux/platform_device.h>18#include <linux/io.h>19#include <linux/acpi.h>20#include <linux/uaccess.h>21#include <linux/miscdevice.h>22#include <linux/fs.h>23#include "acpi_thermal_rel.h"24 25static acpi_handle acpi_thermal_rel_handle;26static DEFINE_SPINLOCK(acpi_thermal_rel_chrdev_lock);27static int acpi_thermal_rel_chrdev_count; /* #times opened */28static int acpi_thermal_rel_chrdev_exclu; /* already open exclusive? */29 30static int acpi_thermal_rel_open(struct inode *inode, struct file *file)31{32 spin_lock(&acpi_thermal_rel_chrdev_lock);33 if (acpi_thermal_rel_chrdev_exclu ||34 (acpi_thermal_rel_chrdev_count && (file->f_flags & O_EXCL))) {35 spin_unlock(&acpi_thermal_rel_chrdev_lock);36 return -EBUSY;37 }38 39 if (file->f_flags & O_EXCL)40 acpi_thermal_rel_chrdev_exclu = 1;41 acpi_thermal_rel_chrdev_count++;42 43 spin_unlock(&acpi_thermal_rel_chrdev_lock);44 45 return nonseekable_open(inode, file);46}47 48static int acpi_thermal_rel_release(struct inode *inode, struct file *file)49{50 spin_lock(&acpi_thermal_rel_chrdev_lock);51 acpi_thermal_rel_chrdev_count--;52 acpi_thermal_rel_chrdev_exclu = 0;53 spin_unlock(&acpi_thermal_rel_chrdev_lock);54 55 return 0;56}57 58/**59 * acpi_parse_trt - Thermal Relationship Table _TRT for passive cooling60 *61 * @handle: ACPI handle of the device contains _TRT62 * @trt_count: the number of valid entries resulted from parsing _TRT63 * @trtp: pointer to pointer of array of _TRT entries in parsing result64 * @create_dev: whether to create platform devices for target and source65 *66 */67int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trtp,68 bool create_dev)69{70 acpi_status status;71 int result = 0;72 int i;73 int nr_bad_entries = 0;74 struct trt *trts;75 union acpi_object *p;76 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };77 struct acpi_buffer element = { 0, NULL };78 struct acpi_buffer trt_format = { sizeof("RRNNNNNN"), "RRNNNNNN" };79 80 status = acpi_evaluate_object(handle, "_TRT", NULL, &buffer);81 if (ACPI_FAILURE(status))82 return -ENODEV;83 84 p = buffer.pointer;85 if (!p || (p->type != ACPI_TYPE_PACKAGE)) {86 pr_err("Invalid _TRT data\n");87 result = -EFAULT;88 goto end;89 }90 91 *trt_count = p->package.count;92 trts = kcalloc(*trt_count, sizeof(struct trt), GFP_KERNEL);93 if (!trts) {94 result = -ENOMEM;95 goto end;96 }97 98 for (i = 0; i < *trt_count; i++) {99 struct trt *trt = &trts[i - nr_bad_entries];100 101 element.length = sizeof(struct trt);102 element.pointer = trt;103 104 status = acpi_extract_package(&(p->package.elements[i]),105 &trt_format, &element);106 if (ACPI_FAILURE(status)) {107 nr_bad_entries++;108 pr_warn("_TRT package %d is invalid, ignored\n", i);109 continue;110 }111 if (!create_dev)112 continue;113 114 if (!acpi_fetch_acpi_dev(trt->source))115 pr_warn("Failed to get source ACPI device\n");116 117 if (!acpi_fetch_acpi_dev(trt->target))118 pr_warn("Failed to get target ACPI device\n");119 }120 121 result = 0;122 123 *trtp = trts;124 /* don't count bad entries */125 *trt_count -= nr_bad_entries;126end:127 kfree(buffer.pointer);128 return result;129}130EXPORT_SYMBOL(acpi_parse_trt);131 132/**133 * acpi_parse_art - Parse Active Relationship Table _ART134 *135 * @handle: ACPI handle of the device contains _ART136 * @art_count: the number of valid entries resulted from parsing _ART137 * @artp: pointer to pointer of array of art entries in parsing result138 * @create_dev: whether to create platform devices for target and source139 *140 */141int acpi_parse_art(acpi_handle handle, int *art_count, struct art **artp,142 bool create_dev)143{144 acpi_status status;145 int result = 0;146 int i;147 int nr_bad_entries = 0;148 struct art *arts;149 union acpi_object *p;150 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };151 struct acpi_buffer element = { 0, NULL };152 struct acpi_buffer art_format = {153 sizeof("RRNNNNNNNNNNN"), "RRNNNNNNNNNNN" };154 155 status = acpi_evaluate_object(handle, "_ART", NULL, &buffer);156 if (ACPI_FAILURE(status))157 return -ENODEV;158 159 p = buffer.pointer;160 if (!p || (p->type != ACPI_TYPE_PACKAGE)) {161 pr_err("Invalid _ART data\n");162 result = -EFAULT;163 goto end;164 }165 166 /* ignore p->package.elements[0], as this is _ART Revision field */167 *art_count = p->package.count - 1;168 arts = kcalloc(*art_count, sizeof(struct art), GFP_KERNEL);169 if (!arts) {170 result = -ENOMEM;171 goto end;172 }173 174 for (i = 0; i < *art_count; i++) {175 struct art *art = &arts[i - nr_bad_entries];176 177 element.length = sizeof(struct art);178 element.pointer = art;179 180 status = acpi_extract_package(&(p->package.elements[i + 1]),181 &art_format, &element);182 if (ACPI_FAILURE(status)) {183 pr_warn("_ART package %d is invalid, ignored", i);184 nr_bad_entries++;185 continue;186 }187 if (!create_dev)188 continue;189 190 if (!acpi_fetch_acpi_dev(art->source))191 pr_warn("Failed to get source ACPI device\n");192 193 if (!acpi_fetch_acpi_dev(art->target))194 pr_warn("Failed to get target ACPI device\n");195 }196 197 *artp = arts;198 /* don't count bad entries */199 *art_count -= nr_bad_entries;200end:201 kfree(buffer.pointer);202 return result;203}204EXPORT_SYMBOL(acpi_parse_art);205 206/*207 * acpi_parse_psvt - Passive Table (PSVT) for passive cooling208 *209 * @handle: ACPI handle of the device which contains PSVT210 * @psvt_count: the number of valid entries resulted from parsing PSVT211 * @psvtp: pointer to array of psvt entries212 *213 */214static int acpi_parse_psvt(acpi_handle handle, int *psvt_count, struct psvt **psvtp)215{216 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };217 int nr_bad_entries = 0, revision = 0;218 union acpi_object *p;219 acpi_status status;220 int i, result = 0;221 struct psvt *psvts;222 223 if (!acpi_has_method(handle, "PSVT"))224 return -ENODEV;225 226 status = acpi_evaluate_object(handle, "PSVT", NULL, &buffer);227 if (ACPI_FAILURE(status))228 return -ENODEV;229 230 p = buffer.pointer;231 if (!p || (p->type != ACPI_TYPE_PACKAGE)) {232 result = -EFAULT;233 goto end;234 }235 236 /* first package is the revision number */237 if (p->package.count > 0) {238 union acpi_object *prev = &(p->package.elements[0]);239 240 if (prev->type == ACPI_TYPE_INTEGER)241 revision = (int)prev->integer.value;242 } else {243 result = -EFAULT;244 goto end;245 }246 247 /* Support only version 2 */248 if (revision != 2) {249 result = -EFAULT;250 goto end;251 }252 253 *psvt_count = p->package.count - 1;254 if (!*psvt_count) {255 result = -EFAULT;256 goto end;257 }258 259 psvts = kcalloc(*psvt_count, sizeof(*psvts), GFP_KERNEL);260 if (!psvts) {261 result = -ENOMEM;262 goto end;263 }264 265 /* Start index is 1 because the first package is the revision number */266 for (i = 1; i < p->package.count; i++) {267 struct acpi_buffer psvt_int_format = { sizeof("RRNNNNNNNNNN"), "RRNNNNNNNNNN" };268 struct acpi_buffer psvt_str_format = { sizeof("RRNNNNNSNNNN"), "RRNNNNNSNNNN" };269 union acpi_object *package = &(p->package.elements[i]);270 struct psvt *psvt = &psvts[i - 1 - nr_bad_entries];271 struct acpi_buffer *psvt_format = &psvt_int_format;272 struct acpi_buffer element = { 0, NULL };273 union acpi_object *knob;274 struct acpi_device *res;275 struct psvt *psvt_ptr;276 277 element.length = ACPI_ALLOCATE_BUFFER;278 element.pointer = NULL;279 280 if (package->package.count >= ACPI_NR_PSVT_ELEMENTS) {281 knob = &(package->package.elements[ACPI_PSVT_CONTROL_KNOB]);282 } else {283 nr_bad_entries++;284 pr_info("PSVT package %d is invalid, ignored\n", i);285 continue;286 }287 288 if (knob->type == ACPI_TYPE_STRING) {289 psvt_format = &psvt_str_format;290 if (knob->string.length > ACPI_LIMIT_STR_MAX_LEN - 1) {291 pr_info("PSVT package %d limit string len exceeds max\n", i);292 knob->string.length = ACPI_LIMIT_STR_MAX_LEN - 1;293 }294 }295 296 status = acpi_extract_package(&(p->package.elements[i]), psvt_format, &element);297 if (ACPI_FAILURE(status)) {298 nr_bad_entries++;299 pr_info("PSVT package %d is invalid, ignored\n", i);300 continue;301 }302 303 psvt_ptr = (struct psvt *)element.pointer;304 305 memcpy(psvt, psvt_ptr, sizeof(*psvt));306 307 /* The limit element can be string or U64 */308 psvt->control_knob_type = (u64)knob->type;309 310 if (knob->type == ACPI_TYPE_STRING) {311 memset(&psvt->limit, 0, sizeof(u64));312 strscpy(psvt->limit.string, psvt_ptr->limit.str_ptr, ACPI_LIMIT_STR_MAX_LEN);313 } else {314 psvt->limit.integer = psvt_ptr->limit.integer;315 }316 317 kfree(element.pointer);318 319 res = acpi_fetch_acpi_dev(psvt->source);320 if (!res) {321 nr_bad_entries++;322 pr_info("Failed to get source ACPI device\n");323 continue;324 }325 326 res = acpi_fetch_acpi_dev(psvt->target);327 if (!res) {328 nr_bad_entries++;329 pr_info("Failed to get target ACPI device\n");330 continue;331 }332 }333 334 /* don't count bad entries */335 *psvt_count -= nr_bad_entries;336 337 if (!*psvt_count) {338 result = -EFAULT;339 kfree(psvts);340 goto end;341 }342 343 *psvtp = psvts;344 345 return 0;346 347end:348 kfree(buffer.pointer);349 return result;350}351 352/* get device name from acpi handle */353static void get_single_name(acpi_handle handle, char *name)354{355 struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER};356 357 if (ACPI_FAILURE(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)))358 pr_warn("Failed to get device name from acpi handle\n");359 else {360 memcpy(name, buffer.pointer, ACPI_NAMESEG_SIZE);361 kfree(buffer.pointer);362 }363}364 365static int fill_art(char __user *ubuf)366{367 int i;368 int ret;369 int count;370 int art_len;371 struct art *arts = NULL;372 union art_object *art_user;373 374 ret = acpi_parse_art(acpi_thermal_rel_handle, &count, &arts, false);375 if (ret)376 goto free_art;377 art_len = count * sizeof(union art_object);378 art_user = kzalloc(art_len, GFP_KERNEL);379 if (!art_user) {380 ret = -ENOMEM;381 goto free_art;382 }383 /* now fill in user art data */384 for (i = 0; i < count; i++) {385 /* userspace art needs device name instead of acpi reference */386 get_single_name(arts[i].source, art_user[i].source_device);387 get_single_name(arts[i].target, art_user[i].target_device);388 /* copy the rest int data in addition to source and target */389 BUILD_BUG_ON(sizeof(art_user[i].data) !=390 sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2));391 memcpy(&art_user[i].data, &arts[i].data, sizeof(art_user[i].data));392 }393 394 if (copy_to_user(ubuf, art_user, art_len))395 ret = -EFAULT;396 kfree(art_user);397free_art:398 kfree(arts);399 return ret;400}401 402static int fill_trt(char __user *ubuf)403{404 int i;405 int ret;406 int count;407 int trt_len;408 struct trt *trts = NULL;409 union trt_object *trt_user;410 411 ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, &trts, false);412 if (ret)413 goto free_trt;414 trt_len = count * sizeof(union trt_object);415 trt_user = kzalloc(trt_len, GFP_KERNEL);416 if (!trt_user) {417 ret = -ENOMEM;418 goto free_trt;419 }420 /* now fill in user trt data */421 for (i = 0; i < count; i++) {422 /* userspace trt needs device name instead of acpi reference */423 get_single_name(trts[i].source, trt_user[i].source_device);424 get_single_name(trts[i].target, trt_user[i].target_device);425 trt_user[i].sample_period = trts[i].sample_period;426 trt_user[i].influence = trts[i].influence;427 }428 429 if (copy_to_user(ubuf, trt_user, trt_len))430 ret = -EFAULT;431 kfree(trt_user);432free_trt:433 kfree(trts);434 return ret;435}436 437static int fill_psvt(char __user *ubuf)438{439 int i, ret, count, psvt_len;440 union psvt_object *psvt_user;441 struct psvt *psvts;442 443 ret = acpi_parse_psvt(acpi_thermal_rel_handle, &count, &psvts);444 if (ret)445 return ret;446 447 psvt_len = count * sizeof(*psvt_user);448 449 psvt_user = kzalloc(psvt_len, GFP_KERNEL);450 if (!psvt_user) {451 ret = -ENOMEM;452 goto free_psvt;453 }454 455 /* now fill in user psvt data */456 for (i = 0; i < count; i++) {457 /* userspace psvt needs device name instead of acpi reference */458 get_single_name(psvts[i].source, psvt_user[i].source_device);459 get_single_name(psvts[i].target, psvt_user[i].target_device);460 461 psvt_user[i].priority = psvts[i].priority;462 psvt_user[i].sample_period = psvts[i].sample_period;463 psvt_user[i].passive_temp = psvts[i].passive_temp;464 psvt_user[i].source_domain = psvts[i].source_domain;465 psvt_user[i].control_knob = psvts[i].control_knob;466 psvt_user[i].step_size = psvts[i].step_size;467 psvt_user[i].limit_coeff = psvts[i].limit_coeff;468 psvt_user[i].unlimit_coeff = psvts[i].unlimit_coeff;469 psvt_user[i].control_knob_type = psvts[i].control_knob_type;470 if (psvt_user[i].control_knob_type == ACPI_TYPE_STRING)471 strscpy(psvt_user[i].limit.string, psvts[i].limit.string,472 ACPI_LIMIT_STR_MAX_LEN);473 else474 psvt_user[i].limit.integer = psvts[i].limit.integer;475 476 }477 478 if (copy_to_user(ubuf, psvt_user, psvt_len))479 ret = -EFAULT;480 481 kfree(psvt_user);482 483free_psvt:484 kfree(psvts);485 return ret;486}487 488static long acpi_thermal_rel_ioctl(struct file *f, unsigned int cmd,489 unsigned long __arg)490{491 int ret = 0;492 unsigned long length = 0;493 int count = 0;494 char __user *arg = (void __user *)__arg;495 struct trt *trts = NULL;496 struct art *arts = NULL;497 struct psvt *psvts;498 499 switch (cmd) {500 case ACPI_THERMAL_GET_TRT_COUNT:501 ret = acpi_parse_trt(acpi_thermal_rel_handle, &count,502 &trts, false);503 kfree(trts);504 if (!ret)505 return put_user(count, (unsigned long __user *)__arg);506 return ret;507 case ACPI_THERMAL_GET_TRT_LEN:508 ret = acpi_parse_trt(acpi_thermal_rel_handle, &count,509 &trts, false);510 kfree(trts);511 length = count * sizeof(union trt_object);512 if (!ret)513 return put_user(length, (unsigned long __user *)__arg);514 return ret;515 case ACPI_THERMAL_GET_TRT:516 return fill_trt(arg);517 case ACPI_THERMAL_GET_ART_COUNT:518 ret = acpi_parse_art(acpi_thermal_rel_handle, &count,519 &arts, false);520 kfree(arts);521 if (!ret)522 return put_user(count, (unsigned long __user *)__arg);523 return ret;524 case ACPI_THERMAL_GET_ART_LEN:525 ret = acpi_parse_art(acpi_thermal_rel_handle, &count,526 &arts, false);527 kfree(arts);528 length = count * sizeof(union art_object);529 if (!ret)530 return put_user(length, (unsigned long __user *)__arg);531 return ret;532 533 case ACPI_THERMAL_GET_ART:534 return fill_art(arg);535 536 case ACPI_THERMAL_GET_PSVT_COUNT:537 ret = acpi_parse_psvt(acpi_thermal_rel_handle, &count, &psvts);538 if (!ret) {539 kfree(psvts);540 return put_user(count, (unsigned long __user *)__arg);541 }542 return ret;543 544 case ACPI_THERMAL_GET_PSVT_LEN:545 /* total length of the data retrieved (count * PSVT entry size) */546 ret = acpi_parse_psvt(acpi_thermal_rel_handle, &count, &psvts);547 length = count * sizeof(union psvt_object);548 if (!ret) {549 kfree(psvts);550 return put_user(length, (unsigned long __user *)__arg);551 }552 return ret;553 554 case ACPI_THERMAL_GET_PSVT:555 return fill_psvt(arg);556 557 default:558 return -ENOTTY;559 }560}561 562static const struct file_operations acpi_thermal_rel_fops = {563 .owner = THIS_MODULE,564 .open = acpi_thermal_rel_open,565 .release = acpi_thermal_rel_release,566 .unlocked_ioctl = acpi_thermal_rel_ioctl,567};568 569static struct miscdevice acpi_thermal_rel_misc_device = {570 .minor = MISC_DYNAMIC_MINOR,571 "acpi_thermal_rel",572 &acpi_thermal_rel_fops573};574 575int acpi_thermal_rel_misc_device_add(acpi_handle handle)576{577 acpi_thermal_rel_handle = handle;578 579 return misc_register(&acpi_thermal_rel_misc_device);580}581EXPORT_SYMBOL(acpi_thermal_rel_misc_device_add);582 583int acpi_thermal_rel_misc_device_remove(acpi_handle handle)584{585 misc_deregister(&acpi_thermal_rel_misc_device);586 587 return 0;588}589EXPORT_SYMBOL(acpi_thermal_rel_misc_device_remove);590 591MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");592MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@intel.com");593MODULE_DESCRIPTION("Intel acpi thermal rel misc dev driver");594MODULE_LICENSE("GPL v2");595