1 // SPDX-License-Identifier: GPL-2.0-only 2 /* acpi_thermal_rel.c driver for exporting ACPI thermal relationship 3 * 4 * Copyright (c) 2014 Intel Corp 5 */ 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 drivers 11 * 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 "acpi_thermal_rel.h" 23 24 static acpi_handle acpi_thermal_rel_handle; 25 static DEFINE_SPINLOCK(acpi_thermal_rel_chrdev_lock); 26 static int acpi_thermal_rel_chrdev_count; /* #times opened */ 27 static int acpi_thermal_rel_chrdev_exclu; /* already open exclusive? */ 28 29 static int acpi_thermal_rel_open(struct inode *inode, struct file *file) 30 { 31 spin_lock(&acpi_thermal_rel_chrdev_lock); 32 if (acpi_thermal_rel_chrdev_exclu || 33 (acpi_thermal_rel_chrdev_count && (file->f_flags & O_EXCL))) { 34 spin_unlock(&acpi_thermal_rel_chrdev_lock); 35 return -EBUSY; 36 } 37 38 if (file->f_flags & O_EXCL) 39 acpi_thermal_rel_chrdev_exclu = 1; 40 acpi_thermal_rel_chrdev_count++; 41 42 spin_unlock(&acpi_thermal_rel_chrdev_lock); 43 44 return nonseekable_open(inode, file); 45 } 46 47 static int acpi_thermal_rel_release(struct inode *inode, struct file *file) 48 { 49 spin_lock(&acpi_thermal_rel_chrdev_lock); 50 acpi_thermal_rel_chrdev_count--; 51 acpi_thermal_rel_chrdev_exclu = 0; 52 spin_unlock(&acpi_thermal_rel_chrdev_lock); 53 54 return 0; 55 } 56 57 /** 58 * acpi_parse_trt - Thermal Relationship Table _TRT for passive cooling 59 * 60 * @handle: ACPI handle of the device contains _TRT 61 * @trt_count: the number of valid entries resulted from parsing _TRT 62 * @trtp: pointer to pointer of array of _TRT entries in parsing result 63 * @create_dev: whether to create platform devices for target and source 64 * 65 */ 66 int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trtp, 67 bool create_dev) 68 { 69 acpi_status status; 70 int result = 0; 71 int i; 72 int nr_bad_entries = 0; 73 struct trt *trts; 74 struct acpi_device *adev; 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 if (!acpi_has_method(handle, "_TRT")) 81 return -ENODEV; 82 83 status = acpi_evaluate_object(handle, "_TRT", NULL, &buffer); 84 if (ACPI_FAILURE(status)) 85 return -ENODEV; 86 87 p = buffer.pointer; 88 if (!p || (p->type != ACPI_TYPE_PACKAGE)) { 89 pr_err("Invalid _TRT data\n"); 90 result = -EFAULT; 91 goto end; 92 } 93 94 *trt_count = p->package.count; 95 trts = kcalloc(*trt_count, sizeof(struct trt), GFP_KERNEL); 96 if (!trts) { 97 result = -ENOMEM; 98 goto end; 99 } 100 101 for (i = 0; i < *trt_count; i++) { 102 struct trt *trt = &trts[i - nr_bad_entries]; 103 104 element.length = sizeof(struct trt); 105 element.pointer = trt; 106 107 status = acpi_extract_package(&(p->package.elements[i]), 108 &trt_format, &element); 109 if (ACPI_FAILURE(status)) { 110 nr_bad_entries++; 111 pr_warn("_TRT package %d is invalid, ignored\n", i); 112 continue; 113 } 114 if (!create_dev) 115 continue; 116 117 result = acpi_bus_get_device(trt->source, &adev); 118 if (result) 119 pr_warn("Failed to get source ACPI device\n"); 120 121 result = acpi_bus_get_device(trt->target, &adev); 122 if (result) 123 pr_warn("Failed to get target ACPI device\n"); 124 } 125 126 result = 0; 127 128 *trtp = trts; 129 /* don't count bad entries */ 130 *trt_count -= nr_bad_entries; 131 end: 132 kfree(buffer.pointer); 133 return result; 134 } 135 EXPORT_SYMBOL(acpi_parse_trt); 136 137 /** 138 * acpi_parse_art - Parse Active Relationship Table _ART 139 * 140 * @handle: ACPI handle of the device contains _ART 141 * @art_count: the number of valid entries resulted from parsing _ART 142 * @artp: pointer to pointer of array of art entries in parsing result 143 * @create_dev: whether to create platform devices for target and source 144 * 145 */ 146 int acpi_parse_art(acpi_handle handle, int *art_count, struct art **artp, 147 bool create_dev) 148 { 149 acpi_status status; 150 int result = 0; 151 int i; 152 int nr_bad_entries = 0; 153 struct art *arts; 154 struct acpi_device *adev; 155 union acpi_object *p; 156 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 157 struct acpi_buffer element = { 0, NULL }; 158 struct acpi_buffer art_format = { 159 sizeof("RRNNNNNNNNNNN"), "RRNNNNNNNNNNN" }; 160 161 if (!acpi_has_method(handle, "_ART")) 162 return -ENODEV; 163 164 status = acpi_evaluate_object(handle, "_ART", NULL, &buffer); 165 if (ACPI_FAILURE(status)) 166 return -ENODEV; 167 168 p = buffer.pointer; 169 if (!p || (p->type != ACPI_TYPE_PACKAGE)) { 170 pr_err("Invalid _ART data\n"); 171 result = -EFAULT; 172 goto end; 173 } 174 175 /* ignore p->package.elements[0], as this is _ART Revision field */ 176 *art_count = p->package.count - 1; 177 arts = kcalloc(*art_count, sizeof(struct art), GFP_KERNEL); 178 if (!arts) { 179 result = -ENOMEM; 180 goto end; 181 } 182 183 for (i = 0; i < *art_count; i++) { 184 struct art *art = &arts[i - nr_bad_entries]; 185 186 element.length = sizeof(struct art); 187 element.pointer = art; 188 189 status = acpi_extract_package(&(p->package.elements[i + 1]), 190 &art_format, &element); 191 if (ACPI_FAILURE(status)) { 192 pr_warn("_ART package %d is invalid, ignored", i); 193 nr_bad_entries++; 194 continue; 195 } 196 if (!create_dev) 197 continue; 198 199 if (art->source) { 200 result = acpi_bus_get_device(art->source, &adev); 201 if (result) 202 pr_warn("Failed to get source ACPI device\n"); 203 } 204 if (art->target) { 205 result = acpi_bus_get_device(art->target, &adev); 206 if (result) 207 pr_warn("Failed to get target ACPI device\n"); 208 } 209 } 210 211 *artp = arts; 212 /* don't count bad entries */ 213 *art_count -= nr_bad_entries; 214 end: 215 kfree(buffer.pointer); 216 return result; 217 } 218 EXPORT_SYMBOL(acpi_parse_art); 219 220 221 /* get device name from acpi handle */ 222 static void get_single_name(acpi_handle handle, char *name) 223 { 224 struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER}; 225 226 if (ACPI_FAILURE(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer))) 227 pr_warn("Failed to get device name from acpi handle\n"); 228 else { 229 memcpy(name, buffer.pointer, ACPI_NAMESEG_SIZE); 230 kfree(buffer.pointer); 231 } 232 } 233 234 static int fill_art(char __user *ubuf) 235 { 236 int i; 237 int ret; 238 int count; 239 int art_len; 240 struct art *arts = NULL; 241 union art_object *art_user; 242 243 ret = acpi_parse_art(acpi_thermal_rel_handle, &count, &arts, false); 244 if (ret) 245 goto free_art; 246 art_len = count * sizeof(union art_object); 247 art_user = kzalloc(art_len, GFP_KERNEL); 248 if (!art_user) { 249 ret = -ENOMEM; 250 goto free_art; 251 } 252 /* now fill in user art data */ 253 for (i = 0; i < count; i++) { 254 /* userspace art needs device name instead of acpi reference */ 255 get_single_name(arts[i].source, art_user[i].source_device); 256 get_single_name(arts[i].target, art_user[i].target_device); 257 /* copy the rest int data in addition to source and target */ 258 memcpy(&art_user[i].weight, &arts[i].weight, 259 sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2)); 260 } 261 262 if (copy_to_user(ubuf, art_user, art_len)) 263 ret = -EFAULT; 264 kfree(art_user); 265 free_art: 266 kfree(arts); 267 return ret; 268 } 269 270 static int fill_trt(char __user *ubuf) 271 { 272 int i; 273 int ret; 274 int count; 275 int trt_len; 276 struct trt *trts = NULL; 277 union trt_object *trt_user; 278 279 ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, &trts, false); 280 if (ret) 281 goto free_trt; 282 trt_len = count * sizeof(union trt_object); 283 trt_user = kzalloc(trt_len, GFP_KERNEL); 284 if (!trt_user) { 285 ret = -ENOMEM; 286 goto free_trt; 287 } 288 /* now fill in user trt data */ 289 for (i = 0; i < count; i++) { 290 /* userspace trt needs device name instead of acpi reference */ 291 get_single_name(trts[i].source, trt_user[i].source_device); 292 get_single_name(trts[i].target, trt_user[i].target_device); 293 trt_user[i].sample_period = trts[i].sample_period; 294 trt_user[i].influence = trts[i].influence; 295 } 296 297 if (copy_to_user(ubuf, trt_user, trt_len)) 298 ret = -EFAULT; 299 kfree(trt_user); 300 free_trt: 301 kfree(trts); 302 return ret; 303 } 304 305 static long acpi_thermal_rel_ioctl(struct file *f, unsigned int cmd, 306 unsigned long __arg) 307 { 308 int ret = 0; 309 unsigned long length = 0; 310 int count = 0; 311 char __user *arg = (void __user *)__arg; 312 struct trt *trts = NULL; 313 struct art *arts = NULL; 314 315 switch (cmd) { 316 case ACPI_THERMAL_GET_TRT_COUNT: 317 ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, 318 &trts, false); 319 kfree(trts); 320 if (!ret) 321 return put_user(count, (unsigned long __user *)__arg); 322 return ret; 323 case ACPI_THERMAL_GET_TRT_LEN: 324 ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, 325 &trts, false); 326 kfree(trts); 327 length = count * sizeof(union trt_object); 328 if (!ret) 329 return put_user(length, (unsigned long __user *)__arg); 330 return ret; 331 case ACPI_THERMAL_GET_TRT: 332 return fill_trt(arg); 333 case ACPI_THERMAL_GET_ART_COUNT: 334 ret = acpi_parse_art(acpi_thermal_rel_handle, &count, 335 &arts, false); 336 kfree(arts); 337 if (!ret) 338 return put_user(count, (unsigned long __user *)__arg); 339 return ret; 340 case ACPI_THERMAL_GET_ART_LEN: 341 ret = acpi_parse_art(acpi_thermal_rel_handle, &count, 342 &arts, false); 343 kfree(arts); 344 length = count * sizeof(union art_object); 345 if (!ret) 346 return put_user(length, (unsigned long __user *)__arg); 347 return ret; 348 349 case ACPI_THERMAL_GET_ART: 350 return fill_art(arg); 351 352 default: 353 return -ENOTTY; 354 } 355 } 356 357 static const struct file_operations acpi_thermal_rel_fops = { 358 .owner = THIS_MODULE, 359 .open = acpi_thermal_rel_open, 360 .release = acpi_thermal_rel_release, 361 .unlocked_ioctl = acpi_thermal_rel_ioctl, 362 .llseek = no_llseek, 363 }; 364 365 static struct miscdevice acpi_thermal_rel_misc_device = { 366 .minor = MISC_DYNAMIC_MINOR, 367 "acpi_thermal_rel", 368 &acpi_thermal_rel_fops 369 }; 370 371 int acpi_thermal_rel_misc_device_add(acpi_handle handle) 372 { 373 acpi_thermal_rel_handle = handle; 374 375 return misc_register(&acpi_thermal_rel_misc_device); 376 } 377 EXPORT_SYMBOL(acpi_thermal_rel_misc_device_add); 378 379 int acpi_thermal_rel_misc_device_remove(acpi_handle handle) 380 { 381 misc_deregister(&acpi_thermal_rel_misc_device); 382 383 return 0; 384 } 385 EXPORT_SYMBOL(acpi_thermal_rel_misc_device_remove); 386 387 MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>"); 388 MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@intel.com"); 389 MODULE_DESCRIPTION("Intel acpi thermal rel misc dev driver"); 390 MODULE_LICENSE("GPL v2"); 391