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
acpi_thermal_rel_open(struct inode * inode,struct file * file)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
acpi_thermal_rel_release(struct inode * inode,struct file * file)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 */
acpi_parse_trt(acpi_handle handle,int * trt_count,struct trt ** trtp,bool create_dev)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 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 result = acpi_bus_get_device(trt->source, &adev);
115 if (result)
116 pr_warn("Failed to get source ACPI device\n");
117
118 result = acpi_bus_get_device(trt->target, &adev);
119 if (result)
120 pr_warn("Failed to get target ACPI device\n");
121 }
122
123 result = 0;
124
125 *trtp = trts;
126 /* don't count bad entries */
127 *trt_count -= nr_bad_entries;
128 end:
129 kfree(buffer.pointer);
130 return result;
131 }
132 EXPORT_SYMBOL(acpi_parse_trt);
133
134 /**
135 * acpi_parse_art - Parse Active Relationship Table _ART
136 *
137 * @handle: ACPI handle of the device contains _ART
138 * @art_count: the number of valid entries resulted from parsing _ART
139 * @artp: pointer to pointer of array of art entries in parsing result
140 * @create_dev: whether to create platform devices for target and source
141 *
142 */
acpi_parse_art(acpi_handle handle,int * art_count,struct art ** artp,bool create_dev)143 int acpi_parse_art(acpi_handle handle, int *art_count, struct art **artp,
144 bool create_dev)
145 {
146 acpi_status status;
147 int result = 0;
148 int i;
149 int nr_bad_entries = 0;
150 struct art *arts;
151 struct acpi_device *adev;
152 union acpi_object *p;
153 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
154 struct acpi_buffer element = { 0, NULL };
155 struct acpi_buffer art_format = {
156 sizeof("RRNNNNNNNNNNN"), "RRNNNNNNNNNNN" };
157
158 status = acpi_evaluate_object(handle, "_ART", NULL, &buffer);
159 if (ACPI_FAILURE(status))
160 return -ENODEV;
161
162 p = buffer.pointer;
163 if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
164 pr_err("Invalid _ART data\n");
165 result = -EFAULT;
166 goto end;
167 }
168
169 /* ignore p->package.elements[0], as this is _ART Revision field */
170 *art_count = p->package.count - 1;
171 arts = kcalloc(*art_count, sizeof(struct art), GFP_KERNEL);
172 if (!arts) {
173 result = -ENOMEM;
174 goto end;
175 }
176
177 for (i = 0; i < *art_count; i++) {
178 struct art *art = &arts[i - nr_bad_entries];
179
180 element.length = sizeof(struct art);
181 element.pointer = art;
182
183 status = acpi_extract_package(&(p->package.elements[i + 1]),
184 &art_format, &element);
185 if (ACPI_FAILURE(status)) {
186 pr_warn("_ART package %d is invalid, ignored", i);
187 nr_bad_entries++;
188 continue;
189 }
190 if (!create_dev)
191 continue;
192
193 if (art->source) {
194 result = acpi_bus_get_device(art->source, &adev);
195 if (result)
196 pr_warn("Failed to get source ACPI device\n");
197 }
198 if (art->target) {
199 result = acpi_bus_get_device(art->target, &adev);
200 if (result)
201 pr_warn("Failed to get target ACPI device\n");
202 }
203 }
204
205 *artp = arts;
206 /* don't count bad entries */
207 *art_count -= nr_bad_entries;
208 end:
209 kfree(buffer.pointer);
210 return result;
211 }
212 EXPORT_SYMBOL(acpi_parse_art);
213
214
215 /* get device name from acpi handle */
get_single_name(acpi_handle handle,char * name)216 static void get_single_name(acpi_handle handle, char *name)
217 {
218 struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER};
219
220 if (ACPI_FAILURE(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)))
221 pr_warn("Failed to get device name from acpi handle\n");
222 else {
223 memcpy(name, buffer.pointer, ACPI_NAMESEG_SIZE);
224 kfree(buffer.pointer);
225 }
226 }
227
fill_art(char __user * ubuf)228 static int fill_art(char __user *ubuf)
229 {
230 int i;
231 int ret;
232 int count;
233 int art_len;
234 struct art *arts = NULL;
235 union art_object *art_user;
236
237 ret = acpi_parse_art(acpi_thermal_rel_handle, &count, &arts, false);
238 if (ret)
239 goto free_art;
240 art_len = count * sizeof(union art_object);
241 art_user = kzalloc(art_len, GFP_KERNEL);
242 if (!art_user) {
243 ret = -ENOMEM;
244 goto free_art;
245 }
246 /* now fill in user art data */
247 for (i = 0; i < count; i++) {
248 /* userspace art needs device name instead of acpi reference */
249 get_single_name(arts[i].source, art_user[i].source_device);
250 get_single_name(arts[i].target, art_user[i].target_device);
251 /* copy the rest int data in addition to source and target */
252 memcpy(&art_user[i].weight, &arts[i].weight,
253 sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2));
254 }
255
256 if (copy_to_user(ubuf, art_user, art_len))
257 ret = -EFAULT;
258 kfree(art_user);
259 free_art:
260 kfree(arts);
261 return ret;
262 }
263
fill_trt(char __user * ubuf)264 static int fill_trt(char __user *ubuf)
265 {
266 int i;
267 int ret;
268 int count;
269 int trt_len;
270 struct trt *trts = NULL;
271 union trt_object *trt_user;
272
273 ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, &trts, false);
274 if (ret)
275 goto free_trt;
276 trt_len = count * sizeof(union trt_object);
277 trt_user = kzalloc(trt_len, GFP_KERNEL);
278 if (!trt_user) {
279 ret = -ENOMEM;
280 goto free_trt;
281 }
282 /* now fill in user trt data */
283 for (i = 0; i < count; i++) {
284 /* userspace trt needs device name instead of acpi reference */
285 get_single_name(trts[i].source, trt_user[i].source_device);
286 get_single_name(trts[i].target, trt_user[i].target_device);
287 trt_user[i].sample_period = trts[i].sample_period;
288 trt_user[i].influence = trts[i].influence;
289 }
290
291 if (copy_to_user(ubuf, trt_user, trt_len))
292 ret = -EFAULT;
293 kfree(trt_user);
294 free_trt:
295 kfree(trts);
296 return ret;
297 }
298
acpi_thermal_rel_ioctl(struct file * f,unsigned int cmd,unsigned long __arg)299 static long acpi_thermal_rel_ioctl(struct file *f, unsigned int cmd,
300 unsigned long __arg)
301 {
302 int ret = 0;
303 unsigned long length = 0;
304 int count = 0;
305 char __user *arg = (void __user *)__arg;
306 struct trt *trts = NULL;
307 struct art *arts = NULL;
308
309 switch (cmd) {
310 case ACPI_THERMAL_GET_TRT_COUNT:
311 ret = acpi_parse_trt(acpi_thermal_rel_handle, &count,
312 &trts, false);
313 kfree(trts);
314 if (!ret)
315 return put_user(count, (unsigned long __user *)__arg);
316 return ret;
317 case ACPI_THERMAL_GET_TRT_LEN:
318 ret = acpi_parse_trt(acpi_thermal_rel_handle, &count,
319 &trts, false);
320 kfree(trts);
321 length = count * sizeof(union trt_object);
322 if (!ret)
323 return put_user(length, (unsigned long __user *)__arg);
324 return ret;
325 case ACPI_THERMAL_GET_TRT:
326 return fill_trt(arg);
327 case ACPI_THERMAL_GET_ART_COUNT:
328 ret = acpi_parse_art(acpi_thermal_rel_handle, &count,
329 &arts, false);
330 kfree(arts);
331 if (!ret)
332 return put_user(count, (unsigned long __user *)__arg);
333 return ret;
334 case ACPI_THERMAL_GET_ART_LEN:
335 ret = acpi_parse_art(acpi_thermal_rel_handle, &count,
336 &arts, false);
337 kfree(arts);
338 length = count * sizeof(union art_object);
339 if (!ret)
340 return put_user(length, (unsigned long __user *)__arg);
341 return ret;
342
343 case ACPI_THERMAL_GET_ART:
344 return fill_art(arg);
345
346 default:
347 return -ENOTTY;
348 }
349 }
350
351 static const struct file_operations acpi_thermal_rel_fops = {
352 .owner = THIS_MODULE,
353 .open = acpi_thermal_rel_open,
354 .release = acpi_thermal_rel_release,
355 .unlocked_ioctl = acpi_thermal_rel_ioctl,
356 .llseek = no_llseek,
357 };
358
359 static struct miscdevice acpi_thermal_rel_misc_device = {
360 .minor = MISC_DYNAMIC_MINOR,
361 "acpi_thermal_rel",
362 &acpi_thermal_rel_fops
363 };
364
acpi_thermal_rel_misc_device_add(acpi_handle handle)365 int acpi_thermal_rel_misc_device_add(acpi_handle handle)
366 {
367 acpi_thermal_rel_handle = handle;
368
369 return misc_register(&acpi_thermal_rel_misc_device);
370 }
371 EXPORT_SYMBOL(acpi_thermal_rel_misc_device_add);
372
acpi_thermal_rel_misc_device_remove(acpi_handle handle)373 int acpi_thermal_rel_misc_device_remove(acpi_handle handle)
374 {
375 misc_deregister(&acpi_thermal_rel_misc_device);
376
377 return 0;
378 }
379 EXPORT_SYMBOL(acpi_thermal_rel_misc_device_remove);
380
381 MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
382 MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@intel.com");
383 MODULE_DESCRIPTION("Intel acpi thermal rel misc dev driver");
384 MODULE_LICENSE("GPL v2");
385