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