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 
28 static acpi_handle acpi_thermal_rel_handle;
29 static DEFINE_SPINLOCK(acpi_thermal_rel_chrdev_lock);
30 static int acpi_thermal_rel_chrdev_count;	/* #times opened */
31 static int acpi_thermal_rel_chrdev_exclu;	/* already open exclusive? */
32 
33 static 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 
51 static 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  * @trt_count: the number of valid entries resulted from parsing _TRT
66  * @trtp: pointer to pointer of array of _TRT entries in parsing result
67  * @create_dev: whether to create platform devices for target and source
68  *
69  */
70 int 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 = kcalloc(*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;
135 end:
136 	kfree(buffer.pointer);
137 	return result;
138 }
139 EXPORT_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  */
150 int 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 = kcalloc(*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 target ACPI device\n");
212 		}
213 	}
214 
215 	*artp = arts;
216 	/* don't count bad entries */
217 	*art_count -= nr_bad_entries;
218 end:
219 	kfree(buffer.pointer);
220 	return result;
221 }
222 EXPORT_SYMBOL(acpi_parse_art);
223 
224 
225 /* get device name from acpi handle */
226 static 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 to get device name from acpi handle\n");
232 	else {
233 		memcpy(name, buffer.pointer, ACPI_NAME_SIZE);
234 		kfree(buffer.pointer);
235 	}
236 }
237 
238 static 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);
269 free_art:
270 	kfree(arts);
271 	return ret;
272 }
273 
274 static 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);
304 free_trt:
305 	kfree(trts);
306 	return ret;
307 }
308 
309 static 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 
361 static 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 
369 static struct miscdevice acpi_thermal_rel_misc_device = {
370 	.minor	= MISC_DYNAMIC_MINOR,
371 	"acpi_thermal_rel",
372 	&acpi_thermal_rel_fops
373 };
374 
375 int 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 }
381 EXPORT_SYMBOL(acpi_thermal_rel_misc_device_add);
382 
383 int acpi_thermal_rel_misc_device_remove(acpi_handle handle)
384 {
385 	misc_deregister(&acpi_thermal_rel_misc_device);
386 
387 	return 0;
388 }
389 EXPORT_SYMBOL(acpi_thermal_rel_misc_device_remove);
390 
391 MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
392 MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@intel.com");
393 MODULE_DESCRIPTION("Intel acpi thermal rel misc dev driver");
394 MODULE_LICENSE("GPL v2");
395