1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * thermal.c - sysfs interface of thermal devices |
4 | * |
5 | * Copyright (C) 2016 Eduardo Valentin <edubezval@gmail.com> |
6 | * |
7 | * Highly based on original thermal_core.c |
8 | * Copyright (C) 2008 Intel Corp |
9 | * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> |
10 | * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> |
11 | */ |
12 | |
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
14 | |
15 | #include <linux/sysfs.h> |
16 | #include <linux/device.h> |
17 | #include <linux/err.h> |
18 | #include <linux/slab.h> |
19 | #include <linux/string.h> |
20 | #include <linux/jiffies.h> |
21 | |
22 | #include "thermal_core.h" |
23 | |
24 | /* sys I/F for thermal zone */ |
25 | |
26 | static ssize_t |
27 | type_show(struct device *dev, struct device_attribute *attr, char *buf) |
28 | { |
29 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
30 | |
31 | return sprintf(buf, fmt: "%s\n" , tz->type); |
32 | } |
33 | |
34 | static ssize_t |
35 | temp_show(struct device *dev, struct device_attribute *attr, char *buf) |
36 | { |
37 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
38 | int temperature, ret; |
39 | |
40 | ret = thermal_zone_get_temp(tz, temp: &temperature); |
41 | |
42 | if (ret) |
43 | return ret; |
44 | |
45 | return sprintf(buf, fmt: "%d\n" , temperature); |
46 | } |
47 | |
48 | static ssize_t |
49 | mode_show(struct device *dev, struct device_attribute *attr, char *buf) |
50 | { |
51 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
52 | int enabled; |
53 | |
54 | mutex_lock(&tz->lock); |
55 | enabled = thermal_zone_device_is_enabled(tz); |
56 | mutex_unlock(lock: &tz->lock); |
57 | |
58 | return sprintf(buf, fmt: "%s\n" , enabled ? "enabled" : "disabled" ); |
59 | } |
60 | |
61 | static ssize_t |
62 | mode_store(struct device *dev, struct device_attribute *attr, |
63 | const char *buf, size_t count) |
64 | { |
65 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
66 | int result; |
67 | |
68 | if (!strncmp(buf, "enabled" , sizeof("enabled" ) - 1)) |
69 | result = thermal_zone_device_enable(tz); |
70 | else if (!strncmp(buf, "disabled" , sizeof("disabled" ) - 1)) |
71 | result = thermal_zone_device_disable(tz); |
72 | else |
73 | result = -EINVAL; |
74 | |
75 | if (result) |
76 | return result; |
77 | |
78 | return count; |
79 | } |
80 | |
81 | static ssize_t |
82 | trip_point_type_show(struct device *dev, struct device_attribute *attr, |
83 | char *buf) |
84 | { |
85 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
86 | struct thermal_trip trip; |
87 | int trip_id, result; |
88 | |
89 | if (sscanf(attr->attr.name, "trip_point_%d_type" , &trip_id) != 1) |
90 | return -EINVAL; |
91 | |
92 | mutex_lock(&tz->lock); |
93 | |
94 | if (device_is_registered(dev)) |
95 | result = __thermal_zone_get_trip(tz, trip_id, trip: &trip); |
96 | else |
97 | result = -ENODEV; |
98 | |
99 | mutex_unlock(lock: &tz->lock); |
100 | |
101 | if (result) |
102 | return result; |
103 | |
104 | switch (trip.type) { |
105 | case THERMAL_TRIP_CRITICAL: |
106 | return sprintf(buf, fmt: "critical\n" ); |
107 | case THERMAL_TRIP_HOT: |
108 | return sprintf(buf, fmt: "hot\n" ); |
109 | case THERMAL_TRIP_PASSIVE: |
110 | return sprintf(buf, fmt: "passive\n" ); |
111 | case THERMAL_TRIP_ACTIVE: |
112 | return sprintf(buf, fmt: "active\n" ); |
113 | default: |
114 | return sprintf(buf, fmt: "unknown\n" ); |
115 | } |
116 | } |
117 | |
118 | static ssize_t |
119 | trip_point_temp_store(struct device *dev, struct device_attribute *attr, |
120 | const char *buf, size_t count) |
121 | { |
122 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
123 | struct thermal_trip trip; |
124 | int trip_id, ret; |
125 | |
126 | if (sscanf(attr->attr.name, "trip_point_%d_temp" , &trip_id) != 1) |
127 | return -EINVAL; |
128 | |
129 | mutex_lock(&tz->lock); |
130 | |
131 | if (!device_is_registered(dev)) { |
132 | ret = -ENODEV; |
133 | goto unlock; |
134 | } |
135 | |
136 | ret = __thermal_zone_get_trip(tz, trip_id, trip: &trip); |
137 | if (ret) |
138 | goto unlock; |
139 | |
140 | ret = kstrtoint(s: buf, base: 10, res: &trip.temperature); |
141 | if (ret) |
142 | goto unlock; |
143 | |
144 | ret = thermal_zone_set_trip(tz, trip_id, trip: &trip); |
145 | unlock: |
146 | mutex_unlock(lock: &tz->lock); |
147 | |
148 | return ret ? ret : count; |
149 | } |
150 | |
151 | static ssize_t |
152 | trip_point_temp_show(struct device *dev, struct device_attribute *attr, |
153 | char *buf) |
154 | { |
155 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
156 | struct thermal_trip trip; |
157 | int trip_id, ret; |
158 | |
159 | if (sscanf(attr->attr.name, "trip_point_%d_temp" , &trip_id) != 1) |
160 | return -EINVAL; |
161 | |
162 | mutex_lock(&tz->lock); |
163 | |
164 | if (device_is_registered(dev)) |
165 | ret = __thermal_zone_get_trip(tz, trip_id, trip: &trip); |
166 | else |
167 | ret = -ENODEV; |
168 | |
169 | mutex_unlock(lock: &tz->lock); |
170 | |
171 | if (ret) |
172 | return ret; |
173 | |
174 | return sprintf(buf, fmt: "%d\n" , trip.temperature); |
175 | } |
176 | |
177 | static ssize_t |
178 | trip_point_hyst_store(struct device *dev, struct device_attribute *attr, |
179 | const char *buf, size_t count) |
180 | { |
181 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
182 | struct thermal_trip trip; |
183 | int trip_id, ret; |
184 | |
185 | if (sscanf(attr->attr.name, "trip_point_%d_hyst" , &trip_id) != 1) |
186 | return -EINVAL; |
187 | |
188 | mutex_lock(&tz->lock); |
189 | |
190 | if (!device_is_registered(dev)) { |
191 | ret = -ENODEV; |
192 | goto unlock; |
193 | } |
194 | |
195 | ret = __thermal_zone_get_trip(tz, trip_id, trip: &trip); |
196 | if (ret) |
197 | goto unlock; |
198 | |
199 | ret = kstrtoint(s: buf, base: 10, res: &trip.hysteresis); |
200 | if (ret) |
201 | goto unlock; |
202 | |
203 | ret = thermal_zone_set_trip(tz, trip_id, trip: &trip); |
204 | unlock: |
205 | mutex_unlock(lock: &tz->lock); |
206 | |
207 | return ret ? ret : count; |
208 | } |
209 | |
210 | static ssize_t |
211 | trip_point_hyst_show(struct device *dev, struct device_attribute *attr, |
212 | char *buf) |
213 | { |
214 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
215 | struct thermal_trip trip; |
216 | int trip_id, ret; |
217 | |
218 | if (sscanf(attr->attr.name, "trip_point_%d_hyst" , &trip_id) != 1) |
219 | return -EINVAL; |
220 | |
221 | mutex_lock(&tz->lock); |
222 | |
223 | if (device_is_registered(dev)) |
224 | ret = __thermal_zone_get_trip(tz, trip_id, trip: &trip); |
225 | else |
226 | ret = -ENODEV; |
227 | |
228 | mutex_unlock(lock: &tz->lock); |
229 | |
230 | return ret ? ret : sprintf(buf, fmt: "%d\n" , trip.hysteresis); |
231 | } |
232 | |
233 | static ssize_t |
234 | policy_store(struct device *dev, struct device_attribute *attr, |
235 | const char *buf, size_t count) |
236 | { |
237 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
238 | char name[THERMAL_NAME_LENGTH]; |
239 | int ret; |
240 | |
241 | snprintf(buf: name, size: sizeof(name), fmt: "%s" , buf); |
242 | |
243 | ret = thermal_zone_device_set_policy(tz, name); |
244 | if (!ret) |
245 | ret = count; |
246 | |
247 | return ret; |
248 | } |
249 | |
250 | static ssize_t |
251 | policy_show(struct device *dev, struct device_attribute *devattr, char *buf) |
252 | { |
253 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
254 | |
255 | return sprintf(buf, fmt: "%s\n" , tz->governor->name); |
256 | } |
257 | |
258 | static ssize_t |
259 | available_policies_show(struct device *dev, struct device_attribute *devattr, |
260 | char *buf) |
261 | { |
262 | return thermal_build_list_of_policies(buf); |
263 | } |
264 | |
265 | #if (IS_ENABLED(CONFIG_THERMAL_EMULATION)) |
266 | static ssize_t |
267 | emul_temp_store(struct device *dev, struct device_attribute *attr, |
268 | const char *buf, size_t count) |
269 | { |
270 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
271 | int ret = 0; |
272 | int temperature; |
273 | |
274 | if (kstrtoint(s: buf, base: 10, res: &temperature)) |
275 | return -EINVAL; |
276 | |
277 | mutex_lock(&tz->lock); |
278 | |
279 | if (!device_is_registered(dev)) { |
280 | ret = -ENODEV; |
281 | goto unlock; |
282 | } |
283 | |
284 | if (!tz->ops->set_emul_temp) |
285 | tz->emul_temperature = temperature; |
286 | else |
287 | ret = tz->ops->set_emul_temp(tz, temperature); |
288 | |
289 | if (!ret) |
290 | __thermal_zone_device_update(tz, event: THERMAL_EVENT_UNSPECIFIED); |
291 | |
292 | unlock: |
293 | mutex_unlock(lock: &tz->lock); |
294 | |
295 | return ret ? ret : count; |
296 | } |
297 | static DEVICE_ATTR_WO(emul_temp); |
298 | #endif |
299 | |
300 | static ssize_t |
301 | sustainable_power_show(struct device *dev, struct device_attribute *devattr, |
302 | char *buf) |
303 | { |
304 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
305 | |
306 | if (tz->tzp) |
307 | return sprintf(buf, fmt: "%u\n" , tz->tzp->sustainable_power); |
308 | else |
309 | return -EIO; |
310 | } |
311 | |
312 | static ssize_t |
313 | sustainable_power_store(struct device *dev, struct device_attribute *devattr, |
314 | const char *buf, size_t count) |
315 | { |
316 | struct thermal_zone_device *tz = to_thermal_zone(dev); |
317 | u32 sustainable_power; |
318 | |
319 | if (!tz->tzp) |
320 | return -EIO; |
321 | |
322 | if (kstrtou32(s: buf, base: 10, res: &sustainable_power)) |
323 | return -EINVAL; |
324 | |
325 | tz->tzp->sustainable_power = sustainable_power; |
326 | |
327 | return count; |
328 | } |
329 | |
330 | #define create_s32_tzp_attr(name) \ |
331 | static ssize_t \ |
332 | name##_show(struct device *dev, struct device_attribute *devattr, \ |
333 | char *buf) \ |
334 | { \ |
335 | struct thermal_zone_device *tz = to_thermal_zone(dev); \ |
336 | \ |
337 | if (tz->tzp) \ |
338 | return sprintf(buf, "%d\n", tz->tzp->name); \ |
339 | else \ |
340 | return -EIO; \ |
341 | } \ |
342 | \ |
343 | static ssize_t \ |
344 | name##_store(struct device *dev, struct device_attribute *devattr, \ |
345 | const char *buf, size_t count) \ |
346 | { \ |
347 | struct thermal_zone_device *tz = to_thermal_zone(dev); \ |
348 | s32 value; \ |
349 | \ |
350 | if (!tz->tzp) \ |
351 | return -EIO; \ |
352 | \ |
353 | if (kstrtos32(buf, 10, &value)) \ |
354 | return -EINVAL; \ |
355 | \ |
356 | tz->tzp->name = value; \ |
357 | \ |
358 | return count; \ |
359 | } \ |
360 | static DEVICE_ATTR_RW(name) |
361 | |
362 | create_s32_tzp_attr(k_po); |
363 | create_s32_tzp_attr(k_pu); |
364 | create_s32_tzp_attr(k_i); |
365 | create_s32_tzp_attr(k_d); |
366 | create_s32_tzp_attr(integral_cutoff); |
367 | create_s32_tzp_attr(slope); |
368 | create_s32_tzp_attr(offset); |
369 | #undef create_s32_tzp_attr |
370 | |
371 | /* |
372 | * These are thermal zone device attributes that will always be present. |
373 | * All the attributes created for tzp (create_s32_tzp_attr) also are always |
374 | * present on the sysfs interface. |
375 | */ |
376 | static DEVICE_ATTR_RO(type); |
377 | static DEVICE_ATTR_RO(temp); |
378 | static DEVICE_ATTR_RW(policy); |
379 | static DEVICE_ATTR_RO(available_policies); |
380 | static DEVICE_ATTR_RW(sustainable_power); |
381 | |
382 | /* These thermal zone device attributes are created based on conditions */ |
383 | static DEVICE_ATTR_RW(mode); |
384 | |
385 | /* These attributes are unconditionally added to a thermal zone */ |
386 | static struct attribute *thermal_zone_dev_attrs[] = { |
387 | &dev_attr_type.attr, |
388 | &dev_attr_temp.attr, |
389 | #if (IS_ENABLED(CONFIG_THERMAL_EMULATION)) |
390 | &dev_attr_emul_temp.attr, |
391 | #endif |
392 | &dev_attr_policy.attr, |
393 | &dev_attr_available_policies.attr, |
394 | &dev_attr_sustainable_power.attr, |
395 | &dev_attr_k_po.attr, |
396 | &dev_attr_k_pu.attr, |
397 | &dev_attr_k_i.attr, |
398 | &dev_attr_k_d.attr, |
399 | &dev_attr_integral_cutoff.attr, |
400 | &dev_attr_slope.attr, |
401 | &dev_attr_offset.attr, |
402 | NULL, |
403 | }; |
404 | |
405 | static const struct attribute_group thermal_zone_attribute_group = { |
406 | .attrs = thermal_zone_dev_attrs, |
407 | }; |
408 | |
409 | static struct attribute *thermal_zone_mode_attrs[] = { |
410 | &dev_attr_mode.attr, |
411 | NULL, |
412 | }; |
413 | |
414 | static const struct attribute_group thermal_zone_mode_attribute_group = { |
415 | .attrs = thermal_zone_mode_attrs, |
416 | }; |
417 | |
418 | static const struct attribute_group *thermal_zone_attribute_groups[] = { |
419 | &thermal_zone_attribute_group, |
420 | &thermal_zone_mode_attribute_group, |
421 | /* This is not NULL terminated as we create the group dynamically */ |
422 | }; |
423 | |
424 | /** |
425 | * create_trip_attrs() - create attributes for trip points |
426 | * @tz: the thermal zone device |
427 | * @mask: Writeable trip point bitmap. |
428 | * |
429 | * helper function to instantiate sysfs entries for every trip |
430 | * point and its properties of a struct thermal_zone_device. |
431 | * |
432 | * Return: 0 on success, the proper error value otherwise. |
433 | */ |
434 | static int create_trip_attrs(struct thermal_zone_device *tz, int mask) |
435 | { |
436 | struct attribute **attrs; |
437 | int indx; |
438 | |
439 | /* This function works only for zones with at least one trip */ |
440 | if (tz->num_trips <= 0) |
441 | return -EINVAL; |
442 | |
443 | tz->trip_type_attrs = kcalloc(n: tz->num_trips, size: sizeof(*tz->trip_type_attrs), |
444 | GFP_KERNEL); |
445 | if (!tz->trip_type_attrs) |
446 | return -ENOMEM; |
447 | |
448 | tz->trip_temp_attrs = kcalloc(n: tz->num_trips, size: sizeof(*tz->trip_temp_attrs), |
449 | GFP_KERNEL); |
450 | if (!tz->trip_temp_attrs) { |
451 | kfree(objp: tz->trip_type_attrs); |
452 | return -ENOMEM; |
453 | } |
454 | |
455 | tz->trip_hyst_attrs = kcalloc(n: tz->num_trips, |
456 | size: sizeof(*tz->trip_hyst_attrs), |
457 | GFP_KERNEL); |
458 | if (!tz->trip_hyst_attrs) { |
459 | kfree(objp: tz->trip_type_attrs); |
460 | kfree(objp: tz->trip_temp_attrs); |
461 | return -ENOMEM; |
462 | } |
463 | |
464 | attrs = kcalloc(n: tz->num_trips * 3 + 1, size: sizeof(*attrs), GFP_KERNEL); |
465 | if (!attrs) { |
466 | kfree(objp: tz->trip_type_attrs); |
467 | kfree(objp: tz->trip_temp_attrs); |
468 | kfree(objp: tz->trip_hyst_attrs); |
469 | return -ENOMEM; |
470 | } |
471 | |
472 | for (indx = 0; indx < tz->num_trips; indx++) { |
473 | /* create trip type attribute */ |
474 | snprintf(buf: tz->trip_type_attrs[indx].name, THERMAL_NAME_LENGTH, |
475 | fmt: "trip_point_%d_type" , indx); |
476 | |
477 | sysfs_attr_init(&tz->trip_type_attrs[indx].attr.attr); |
478 | tz->trip_type_attrs[indx].attr.attr.name = |
479 | tz->trip_type_attrs[indx].name; |
480 | tz->trip_type_attrs[indx].attr.attr.mode = S_IRUGO; |
481 | tz->trip_type_attrs[indx].attr.show = trip_point_type_show; |
482 | attrs[indx] = &tz->trip_type_attrs[indx].attr.attr; |
483 | |
484 | /* create trip temp attribute */ |
485 | snprintf(buf: tz->trip_temp_attrs[indx].name, THERMAL_NAME_LENGTH, |
486 | fmt: "trip_point_%d_temp" , indx); |
487 | |
488 | sysfs_attr_init(&tz->trip_temp_attrs[indx].attr.attr); |
489 | tz->trip_temp_attrs[indx].attr.attr.name = |
490 | tz->trip_temp_attrs[indx].name; |
491 | tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO; |
492 | tz->trip_temp_attrs[indx].attr.show = trip_point_temp_show; |
493 | if (IS_ENABLED(CONFIG_THERMAL_WRITABLE_TRIPS) && |
494 | mask & (1 << indx)) { |
495 | tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR; |
496 | tz->trip_temp_attrs[indx].attr.store = |
497 | trip_point_temp_store; |
498 | } |
499 | attrs[indx + tz->num_trips] = &tz->trip_temp_attrs[indx].attr.attr; |
500 | |
501 | snprintf(buf: tz->trip_hyst_attrs[indx].name, THERMAL_NAME_LENGTH, |
502 | fmt: "trip_point_%d_hyst" , indx); |
503 | |
504 | sysfs_attr_init(&tz->trip_hyst_attrs[indx].attr.attr); |
505 | tz->trip_hyst_attrs[indx].attr.attr.name = |
506 | tz->trip_hyst_attrs[indx].name; |
507 | tz->trip_hyst_attrs[indx].attr.attr.mode = S_IRUGO; |
508 | tz->trip_hyst_attrs[indx].attr.show = trip_point_hyst_show; |
509 | if (tz->ops->set_trip_hyst) { |
510 | tz->trip_hyst_attrs[indx].attr.attr.mode |= S_IWUSR; |
511 | tz->trip_hyst_attrs[indx].attr.store = |
512 | trip_point_hyst_store; |
513 | } |
514 | attrs[indx + tz->num_trips * 2] = |
515 | &tz->trip_hyst_attrs[indx].attr.attr; |
516 | } |
517 | attrs[tz->num_trips * 3] = NULL; |
518 | |
519 | tz->trips_attribute_group.attrs = attrs; |
520 | |
521 | return 0; |
522 | } |
523 | |
524 | /** |
525 | * destroy_trip_attrs() - destroy attributes for trip points |
526 | * @tz: the thermal zone device |
527 | * |
528 | * helper function to free resources allocated by create_trip_attrs() |
529 | */ |
530 | static void destroy_trip_attrs(struct thermal_zone_device *tz) |
531 | { |
532 | if (!tz) |
533 | return; |
534 | |
535 | kfree(objp: tz->trip_type_attrs); |
536 | kfree(objp: tz->trip_temp_attrs); |
537 | kfree(objp: tz->trip_hyst_attrs); |
538 | kfree(objp: tz->trips_attribute_group.attrs); |
539 | } |
540 | |
541 | int thermal_zone_create_device_groups(struct thermal_zone_device *tz, |
542 | int mask) |
543 | { |
544 | const struct attribute_group **groups; |
545 | int i, size, result; |
546 | |
547 | /* we need one extra for trips and the NULL to terminate the array */ |
548 | size = ARRAY_SIZE(thermal_zone_attribute_groups) + 2; |
549 | /* This also takes care of API requirement to be NULL terminated */ |
550 | groups = kcalloc(n: size, size: sizeof(*groups), GFP_KERNEL); |
551 | if (!groups) |
552 | return -ENOMEM; |
553 | |
554 | for (i = 0; i < size - 2; i++) |
555 | groups[i] = thermal_zone_attribute_groups[i]; |
556 | |
557 | if (tz->num_trips) { |
558 | result = create_trip_attrs(tz, mask); |
559 | if (result) { |
560 | kfree(objp: groups); |
561 | |
562 | return result; |
563 | } |
564 | |
565 | groups[size - 2] = &tz->trips_attribute_group; |
566 | } |
567 | |
568 | tz->device.groups = groups; |
569 | |
570 | return 0; |
571 | } |
572 | |
573 | void thermal_zone_destroy_device_groups(struct thermal_zone_device *tz) |
574 | { |
575 | if (!tz) |
576 | return; |
577 | |
578 | if (tz->num_trips) |
579 | destroy_trip_attrs(tz); |
580 | |
581 | kfree(objp: tz->device.groups); |
582 | } |
583 | |
584 | /* sys I/F for cooling device */ |
585 | static ssize_t |
586 | cdev_type_show(struct device *dev, struct device_attribute *attr, char *buf) |
587 | { |
588 | struct thermal_cooling_device *cdev = to_cooling_device(dev); |
589 | |
590 | return sprintf(buf, fmt: "%s\n" , cdev->type); |
591 | } |
592 | |
593 | static ssize_t max_state_show(struct device *dev, struct device_attribute *attr, |
594 | char *buf) |
595 | { |
596 | struct thermal_cooling_device *cdev = to_cooling_device(dev); |
597 | |
598 | return sprintf(buf, fmt: "%ld\n" , cdev->max_state); |
599 | } |
600 | |
601 | static ssize_t cur_state_show(struct device *dev, struct device_attribute *attr, |
602 | char *buf) |
603 | { |
604 | struct thermal_cooling_device *cdev = to_cooling_device(dev); |
605 | unsigned long state; |
606 | int ret; |
607 | |
608 | ret = cdev->ops->get_cur_state(cdev, &state); |
609 | if (ret) |
610 | return ret; |
611 | return sprintf(buf, fmt: "%ld\n" , state); |
612 | } |
613 | |
614 | static ssize_t |
615 | cur_state_store(struct device *dev, struct device_attribute *attr, |
616 | const char *buf, size_t count) |
617 | { |
618 | struct thermal_cooling_device *cdev = to_cooling_device(dev); |
619 | unsigned long state; |
620 | int result; |
621 | |
622 | if (sscanf(buf, "%ld\n" , &state) != 1) |
623 | return -EINVAL; |
624 | |
625 | if ((long)state < 0) |
626 | return -EINVAL; |
627 | |
628 | /* Requested state should be less than max_state + 1 */ |
629 | if (state > cdev->max_state) |
630 | return -EINVAL; |
631 | |
632 | mutex_lock(&cdev->lock); |
633 | |
634 | result = cdev->ops->set_cur_state(cdev, state); |
635 | if (!result) |
636 | thermal_cooling_device_stats_update(cdev, new_state: state); |
637 | |
638 | mutex_unlock(lock: &cdev->lock); |
639 | return result ? result : count; |
640 | } |
641 | |
642 | static struct device_attribute |
643 | dev_attr_cdev_type = __ATTR(type, 0444, cdev_type_show, NULL); |
644 | static DEVICE_ATTR_RO(max_state); |
645 | static DEVICE_ATTR_RW(cur_state); |
646 | |
647 | static struct attribute *cooling_device_attrs[] = { |
648 | &dev_attr_cdev_type.attr, |
649 | &dev_attr_max_state.attr, |
650 | &dev_attr_cur_state.attr, |
651 | NULL, |
652 | }; |
653 | |
654 | static const struct attribute_group cooling_device_attr_group = { |
655 | .attrs = cooling_device_attrs, |
656 | }; |
657 | |
658 | static const struct attribute_group *cooling_device_attr_groups[] = { |
659 | &cooling_device_attr_group, |
660 | NULL, /* Space allocated for cooling_device_stats_attr_group */ |
661 | NULL, |
662 | }; |
663 | |
664 | #ifdef CONFIG_THERMAL_STATISTICS |
665 | struct cooling_dev_stats { |
666 | spinlock_t lock; |
667 | unsigned int total_trans; |
668 | unsigned long state; |
669 | ktime_t last_time; |
670 | ktime_t *time_in_state; |
671 | unsigned int *trans_table; |
672 | }; |
673 | |
674 | static void update_time_in_state(struct cooling_dev_stats *stats) |
675 | { |
676 | ktime_t now = ktime_get(), delta; |
677 | |
678 | delta = ktime_sub(now, stats->last_time); |
679 | stats->time_in_state[stats->state] = |
680 | ktime_add(stats->time_in_state[stats->state], delta); |
681 | stats->last_time = now; |
682 | } |
683 | |
684 | void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev, |
685 | unsigned long new_state) |
686 | { |
687 | struct cooling_dev_stats *stats = cdev->stats; |
688 | |
689 | lockdep_assert_held(&cdev->lock); |
690 | |
691 | if (!stats) |
692 | return; |
693 | |
694 | spin_lock(lock: &stats->lock); |
695 | |
696 | if (stats->state == new_state) |
697 | goto unlock; |
698 | |
699 | update_time_in_state(stats); |
700 | stats->trans_table[stats->state * (cdev->max_state + 1) + new_state]++; |
701 | stats->state = new_state; |
702 | stats->total_trans++; |
703 | |
704 | unlock: |
705 | spin_unlock(lock: &stats->lock); |
706 | } |
707 | |
708 | static ssize_t total_trans_show(struct device *dev, |
709 | struct device_attribute *attr, char *buf) |
710 | { |
711 | struct thermal_cooling_device *cdev = to_cooling_device(dev); |
712 | struct cooling_dev_stats *stats; |
713 | int ret = 0; |
714 | |
715 | mutex_lock(&cdev->lock); |
716 | |
717 | stats = cdev->stats; |
718 | if (!stats) |
719 | goto unlock; |
720 | |
721 | spin_lock(lock: &stats->lock); |
722 | ret = sprintf(buf, fmt: "%u\n" , stats->total_trans); |
723 | spin_unlock(lock: &stats->lock); |
724 | |
725 | unlock: |
726 | mutex_unlock(lock: &cdev->lock); |
727 | |
728 | return ret; |
729 | } |
730 | |
731 | static ssize_t |
732 | time_in_state_ms_show(struct device *dev, struct device_attribute *attr, |
733 | char *buf) |
734 | { |
735 | struct thermal_cooling_device *cdev = to_cooling_device(dev); |
736 | struct cooling_dev_stats *stats; |
737 | ssize_t len = 0; |
738 | int i; |
739 | |
740 | mutex_lock(&cdev->lock); |
741 | |
742 | stats = cdev->stats; |
743 | if (!stats) |
744 | goto unlock; |
745 | |
746 | spin_lock(lock: &stats->lock); |
747 | |
748 | update_time_in_state(stats); |
749 | |
750 | for (i = 0; i <= cdev->max_state; i++) { |
751 | len += sprintf(buf: buf + len, fmt: "state%u\t%llu\n" , i, |
752 | ktime_to_ms(kt: stats->time_in_state[i])); |
753 | } |
754 | spin_unlock(lock: &stats->lock); |
755 | |
756 | unlock: |
757 | mutex_unlock(lock: &cdev->lock); |
758 | |
759 | return len; |
760 | } |
761 | |
762 | static ssize_t |
763 | reset_store(struct device *dev, struct device_attribute *attr, const char *buf, |
764 | size_t count) |
765 | { |
766 | struct thermal_cooling_device *cdev = to_cooling_device(dev); |
767 | struct cooling_dev_stats *stats; |
768 | int i, states; |
769 | |
770 | mutex_lock(&cdev->lock); |
771 | |
772 | stats = cdev->stats; |
773 | if (!stats) |
774 | goto unlock; |
775 | |
776 | states = cdev->max_state + 1; |
777 | |
778 | spin_lock(lock: &stats->lock); |
779 | |
780 | stats->total_trans = 0; |
781 | stats->last_time = ktime_get(); |
782 | memset(stats->trans_table, 0, |
783 | states * states * sizeof(*stats->trans_table)); |
784 | |
785 | for (i = 0; i < states; i++) |
786 | stats->time_in_state[i] = ktime_set(secs: 0, nsecs: 0); |
787 | |
788 | spin_unlock(lock: &stats->lock); |
789 | |
790 | unlock: |
791 | mutex_unlock(lock: &cdev->lock); |
792 | |
793 | return count; |
794 | } |
795 | |
796 | static ssize_t trans_table_show(struct device *dev, |
797 | struct device_attribute *attr, char *buf) |
798 | { |
799 | struct thermal_cooling_device *cdev = to_cooling_device(dev); |
800 | struct cooling_dev_stats *stats; |
801 | ssize_t len = 0; |
802 | int i, j; |
803 | |
804 | mutex_lock(&cdev->lock); |
805 | |
806 | stats = cdev->stats; |
807 | if (!stats) { |
808 | len = -ENODATA; |
809 | goto unlock; |
810 | } |
811 | |
812 | len += snprintf(buf: buf + len, PAGE_SIZE - len, fmt: " From : To\n" ); |
813 | len += snprintf(buf: buf + len, PAGE_SIZE - len, fmt: " : " ); |
814 | for (i = 0; i <= cdev->max_state; i++) { |
815 | if (len >= PAGE_SIZE) |
816 | break; |
817 | len += snprintf(buf: buf + len, PAGE_SIZE - len, fmt: "state%2u " , i); |
818 | } |
819 | if (len >= PAGE_SIZE) { |
820 | len = PAGE_SIZE; |
821 | goto unlock; |
822 | } |
823 | |
824 | len += snprintf(buf: buf + len, PAGE_SIZE - len, fmt: "\n" ); |
825 | |
826 | for (i = 0; i <= cdev->max_state; i++) { |
827 | if (len >= PAGE_SIZE) |
828 | break; |
829 | |
830 | len += snprintf(buf: buf + len, PAGE_SIZE - len, fmt: "state%2u:" , i); |
831 | |
832 | for (j = 0; j <= cdev->max_state; j++) { |
833 | if (len >= PAGE_SIZE) |
834 | break; |
835 | len += snprintf(buf: buf + len, PAGE_SIZE - len, fmt: "%8u " , |
836 | stats->trans_table[i * (cdev->max_state + 1) + j]); |
837 | } |
838 | if (len >= PAGE_SIZE) |
839 | break; |
840 | len += snprintf(buf: buf + len, PAGE_SIZE - len, fmt: "\n" ); |
841 | } |
842 | |
843 | if (len >= PAGE_SIZE) { |
844 | pr_warn_once("Thermal transition table exceeds PAGE_SIZE. Disabling\n" ); |
845 | len = -EFBIG; |
846 | } |
847 | |
848 | unlock: |
849 | mutex_unlock(lock: &cdev->lock); |
850 | |
851 | return len; |
852 | } |
853 | |
854 | static DEVICE_ATTR_RO(total_trans); |
855 | static DEVICE_ATTR_RO(time_in_state_ms); |
856 | static DEVICE_ATTR_WO(reset); |
857 | static DEVICE_ATTR_RO(trans_table); |
858 | |
859 | static struct attribute *cooling_device_stats_attrs[] = { |
860 | &dev_attr_total_trans.attr, |
861 | &dev_attr_time_in_state_ms.attr, |
862 | &dev_attr_reset.attr, |
863 | &dev_attr_trans_table.attr, |
864 | NULL |
865 | }; |
866 | |
867 | static const struct attribute_group cooling_device_stats_attr_group = { |
868 | .attrs = cooling_device_stats_attrs, |
869 | .name = "stats" |
870 | }; |
871 | |
872 | static void cooling_device_stats_setup(struct thermal_cooling_device *cdev) |
873 | { |
874 | const struct attribute_group *stats_attr_group = NULL; |
875 | struct cooling_dev_stats *stats; |
876 | /* Total number of states is highest state + 1 */ |
877 | unsigned long states = cdev->max_state + 1; |
878 | int var; |
879 | |
880 | var = sizeof(*stats); |
881 | var += sizeof(*stats->time_in_state) * states; |
882 | var += sizeof(*stats->trans_table) * states * states; |
883 | |
884 | stats = kzalloc(size: var, GFP_KERNEL); |
885 | if (!stats) |
886 | goto out; |
887 | |
888 | stats->time_in_state = (ktime_t *)(stats + 1); |
889 | stats->trans_table = (unsigned int *)(stats->time_in_state + states); |
890 | cdev->stats = stats; |
891 | stats->last_time = ktime_get(); |
892 | |
893 | spin_lock_init(&stats->lock); |
894 | |
895 | stats_attr_group = &cooling_device_stats_attr_group; |
896 | |
897 | out: |
898 | /* Fill the empty slot left in cooling_device_attr_groups */ |
899 | var = ARRAY_SIZE(cooling_device_attr_groups) - 2; |
900 | cooling_device_attr_groups[var] = stats_attr_group; |
901 | } |
902 | |
903 | static void cooling_device_stats_destroy(struct thermal_cooling_device *cdev) |
904 | { |
905 | kfree(objp: cdev->stats); |
906 | cdev->stats = NULL; |
907 | } |
908 | |
909 | #else |
910 | |
911 | static inline void |
912 | cooling_device_stats_setup(struct thermal_cooling_device *cdev) {} |
913 | static inline void |
914 | cooling_device_stats_destroy(struct thermal_cooling_device *cdev) {} |
915 | |
916 | #endif /* CONFIG_THERMAL_STATISTICS */ |
917 | |
918 | void thermal_cooling_device_setup_sysfs(struct thermal_cooling_device *cdev) |
919 | { |
920 | cooling_device_stats_setup(cdev); |
921 | cdev->device.groups = cooling_device_attr_groups; |
922 | } |
923 | |
924 | void thermal_cooling_device_destroy_sysfs(struct thermal_cooling_device *cdev) |
925 | { |
926 | cooling_device_stats_destroy(cdev); |
927 | } |
928 | |
929 | void thermal_cooling_device_stats_reinit(struct thermal_cooling_device *cdev) |
930 | { |
931 | lockdep_assert_held(&cdev->lock); |
932 | |
933 | cooling_device_stats_destroy(cdev); |
934 | cooling_device_stats_setup(cdev); |
935 | } |
936 | |
937 | /* these helper will be used only at the time of bindig */ |
938 | ssize_t |
939 | trip_point_show(struct device *dev, struct device_attribute *attr, char *buf) |
940 | { |
941 | struct thermal_instance *instance; |
942 | |
943 | instance = |
944 | container_of(attr, struct thermal_instance, attr); |
945 | |
946 | return sprintf(buf, fmt: "%d\n" , |
947 | thermal_zone_trip_id(tz: instance->tz, trip: instance->trip)); |
948 | } |
949 | |
950 | ssize_t |
951 | weight_show(struct device *dev, struct device_attribute *attr, char *buf) |
952 | { |
953 | struct thermal_instance *instance; |
954 | |
955 | instance = container_of(attr, struct thermal_instance, weight_attr); |
956 | |
957 | return sprintf(buf, fmt: "%d\n" , instance->weight); |
958 | } |
959 | |
960 | ssize_t weight_store(struct device *dev, struct device_attribute *attr, |
961 | const char *buf, size_t count) |
962 | { |
963 | struct thermal_instance *instance; |
964 | int ret, weight; |
965 | |
966 | ret = kstrtoint(s: buf, base: 0, res: &weight); |
967 | if (ret) |
968 | return ret; |
969 | |
970 | instance = container_of(attr, struct thermal_instance, weight_attr); |
971 | instance->weight = weight; |
972 | |
973 | return count; |
974 | } |
975 | |