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