]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blob - drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c
treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 500
[mirror_ubuntu-hirsute-kernel.git] / drivers / thermal / intel / int340x_thermal / acpi_thermal_rel.c
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");