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