959 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright 2023 Linaro Limited4 *5 * Author: Daniel Lezcano <daniel.lezcano@linaro.org>6 *7 * Thermal subsystem debug support8 */9#include <linux/debugfs.h>10#include <linux/ktime.h>11#include <linux/list.h>12#include <linux/minmax.h>13#include <linux/mutex.h>14#include <linux/thermal.h>15 16#include "thermal_core.h"17 18static struct dentry *d_root;19static struct dentry *d_cdev;20static struct dentry *d_tz;21 22/*23 * Length of the string containing the thermal zone id or the cooling24 * device id, including the ending nul character. We can reasonably25 * assume there won't be more than 256 thermal zones as the maximum26 * observed today is around 32.27 */28#define IDSLENGTH 429 30/*31 * The cooling device transition list is stored in a hash table where32 * the size is CDEVSTATS_HASH_SIZE. The majority of cooling devices33 * have dozen of states but some can have much more, so a hash table34 * is more adequate in this case, because the cost of browsing the entire35 * list when storing the transitions may not be negligible.36 */37#define CDEVSTATS_HASH_SIZE 1638 39/**40 * struct cdev_debugfs - per cooling device statistics structure41 * A cooling device can have a high number of states. Showing the42 * transitions on a matrix based representation can be overkill given43 * most of the transitions won't happen and we end up with a matrix44 * filled with zero. Instead, we show the transitions which actually45 * happened.46 *47 * Every transition updates the current_state and the timestamp. The48 * transitions and the durations are stored in lists.49 *50 * @total: the number of transitions for this cooling device51 * @current_state: the current cooling device state52 * @timestamp: the state change timestamp53 * @transitions: an array of lists containing the state transitions54 * @durations: an array of lists containing the residencies of each state55 */56struct cdev_debugfs {57 u32 total;58 int current_state;59 ktime_t timestamp;60 struct list_head transitions[CDEVSTATS_HASH_SIZE];61 struct list_head durations[CDEVSTATS_HASH_SIZE];62};63 64/**65 * struct cdev_record - Common structure for cooling device entry66 *67 * The following common structure allows to store the information68 * related to the transitions and to the state residencies. They are69 * identified with a id which is associated to a value. It is used as70 * nodes for the "transitions" and "durations" above.71 *72 * @node: node to insert the structure in a list73 * @id: identifier of the value which can be a state or a transition74 * @residency: a ktime_t representing a state residency duration75 * @count: a number of occurrences76 */77struct cdev_record {78 struct list_head node;79 int id;80 union {81 ktime_t residency;82 u64 count;83 };84};85 86/**87 * struct trip_stats - Thermal trip statistics88 *89 * The trip_stats structure has the relevant information to show the90 * statistics related to temperature going above a trip point.91 *92 * @timestamp: the trip crossing timestamp93 * @duration: total time when the zone temperature was above the trip point94 * @trip_temp: trip temperature at mitigation start95 * @trip_hyst: trip hysteresis at mitigation start96 * @count: the number of times the zone temperature was above the trip point97 * @min: minimum recorded temperature above the trip point98 * @avg: average temperature above the trip point99 */100struct trip_stats {101 ktime_t timestamp;102 ktime_t duration;103 int trip_temp;104 int trip_hyst;105 int count;106 int min;107 int avg;108};109 110/**111 * struct tz_episode - A mitigation episode information112 *113 * The tz_episode structure describes a mitigation episode. A114 * mitigation episode begins the trip point with the lower temperature115 * is crossed the way up and ends when it is crossed the way116 * down. During this episode we can have multiple trip points crossed117 * the way up and down if there are multiple trip described in the118 * firmware after the lowest temperature trip point.119 *120 * @timestamp: first trip point crossed the way up121 * @duration: total duration of the mitigation episode122 * @node: a list element to be added to the list of tz events123 * @max_temp: maximum zone temperature during this episode124 * @trip_stats: per trip point statistics, flexible array125 */126struct tz_episode {127 ktime_t timestamp;128 ktime_t duration;129 struct list_head node;130 int max_temp;131 struct trip_stats trip_stats[];132};133 134/**135 * struct tz_debugfs - Store all mitigation episodes for a thermal zone136 *137 * The tz_debugfs structure contains the list of the mitigation138 * episodes and has to track which trip point has been crossed in139 * order to handle correctly nested trip point mitigation episodes.140 *141 * We keep the history of the trip point crossed in an array and as we142 * can go back and forth inside this history, eg. trip 0,1,2,1,2,1,0,143 * we keep track of the current position in the history array.144 *145 * @tz_episodes: a list of thermal mitigation episodes146 * @tz: thermal zone this object belongs to147 * @trips_crossed: an array of trip points crossed by id148 * @nr_trips: the number of trip points currently being crossed149 */150struct tz_debugfs {151 struct list_head tz_episodes;152 struct thermal_zone_device *tz;153 int *trips_crossed;154 int nr_trips;155};156 157/**158 * struct thermal_debugfs - High level structure for a thermal object in debugfs159 *160 * The thermal_debugfs structure is the common structure used by the161 * cooling device or the thermal zone to store the statistics.162 *163 * @d_top: top directory of the thermal object directory164 * @lock: per object lock to protect the internals165 *166 * @cdev_dbg: a cooling device debug structure167 * @tz_dbg: a thermal zone debug structure168 */169struct thermal_debugfs {170 struct dentry *d_top;171 struct mutex lock;172 union {173 struct cdev_debugfs cdev_dbg;174 struct tz_debugfs tz_dbg;175 };176};177 178void thermal_debug_init(void)179{180 d_root = debugfs_create_dir("thermal", NULL);181 if (IS_ERR(d_root))182 return;183 184 d_cdev = debugfs_create_dir("cooling_devices", d_root);185 if (IS_ERR(d_cdev))186 return;187 188 d_tz = debugfs_create_dir("thermal_zones", d_root);189}190 191static struct thermal_debugfs *thermal_debugfs_add_id(struct dentry *d, int id)192{193 struct thermal_debugfs *thermal_dbg;194 char ids[IDSLENGTH];195 196 thermal_dbg = kzalloc(sizeof(*thermal_dbg), GFP_KERNEL);197 if (!thermal_dbg)198 return NULL;199 200 mutex_init(&thermal_dbg->lock);201 202 snprintf(ids, IDSLENGTH, "%d", id);203 204 thermal_dbg->d_top = debugfs_create_dir(ids, d);205 if (IS_ERR(thermal_dbg->d_top)) {206 kfree(thermal_dbg);207 return NULL;208 }209 210 return thermal_dbg;211}212 213static void thermal_debugfs_remove_id(struct thermal_debugfs *thermal_dbg)214{215 if (!thermal_dbg)216 return;217 218 debugfs_remove(thermal_dbg->d_top);219 220 kfree(thermal_dbg);221}222 223static struct cdev_record *224thermal_debugfs_cdev_record_alloc(struct thermal_debugfs *thermal_dbg,225 struct list_head *lists, int id)226{227 struct cdev_record *cdev_record;228 229 cdev_record = kzalloc(sizeof(*cdev_record), GFP_KERNEL);230 if (!cdev_record)231 return NULL;232 233 cdev_record->id = id;234 INIT_LIST_HEAD(&cdev_record->node);235 list_add_tail(&cdev_record->node,236 &lists[cdev_record->id % CDEVSTATS_HASH_SIZE]);237 238 return cdev_record;239}240 241static struct cdev_record *242thermal_debugfs_cdev_record_find(struct thermal_debugfs *thermal_dbg,243 struct list_head *lists, int id)244{245 struct cdev_record *entry;246 247 list_for_each_entry(entry, &lists[id % CDEVSTATS_HASH_SIZE], node)248 if (entry->id == id)249 return entry;250 251 return NULL;252}253 254static struct cdev_record *255thermal_debugfs_cdev_record_get(struct thermal_debugfs *thermal_dbg,256 struct list_head *lists, int id)257{258 struct cdev_record *cdev_record;259 260 cdev_record = thermal_debugfs_cdev_record_find(thermal_dbg, lists, id);261 if (cdev_record)262 return cdev_record;263 264 return thermal_debugfs_cdev_record_alloc(thermal_dbg, lists, id);265}266 267static void thermal_debugfs_cdev_clear(struct cdev_debugfs *cdev_dbg)268{269 int i;270 struct cdev_record *entry, *tmp;271 272 for (i = 0; i < CDEVSTATS_HASH_SIZE; i++) {273 274 list_for_each_entry_safe(entry, tmp,275 &cdev_dbg->transitions[i], node) {276 list_del(&entry->node);277 kfree(entry);278 }279 280 list_for_each_entry_safe(entry, tmp,281 &cdev_dbg->durations[i], node) {282 list_del(&entry->node);283 kfree(entry);284 }285 }286 287 cdev_dbg->total = 0;288}289 290static void *cdev_seq_start(struct seq_file *s, loff_t *pos)291{292 struct thermal_debugfs *thermal_dbg = s->private;293 294 mutex_lock(&thermal_dbg->lock);295 296 return (*pos < CDEVSTATS_HASH_SIZE) ? pos : NULL;297}298 299static void *cdev_seq_next(struct seq_file *s, void *v, loff_t *pos)300{301 (*pos)++;302 303 return (*pos < CDEVSTATS_HASH_SIZE) ? pos : NULL;304}305 306static void cdev_seq_stop(struct seq_file *s, void *v)307{308 struct thermal_debugfs *thermal_dbg = s->private;309 310 mutex_unlock(&thermal_dbg->lock);311}312 313static int cdev_tt_seq_show(struct seq_file *s, void *v)314{315 struct thermal_debugfs *thermal_dbg = s->private;316 struct cdev_debugfs *cdev_dbg = &thermal_dbg->cdev_dbg;317 struct list_head *transitions = cdev_dbg->transitions;318 struct cdev_record *entry;319 int i = *(loff_t *)v;320 321 if (!i)322 seq_puts(s, "Transition\tOccurences\n");323 324 list_for_each_entry(entry, &transitions[i], node) {325 /*326 * Assuming maximum cdev states is 1024, the longer327 * string for a transition would be "1024->1024\0"328 */329 char buffer[11];330 331 snprintf(buffer, ARRAY_SIZE(buffer), "%d->%d",332 entry->id >> 16, entry->id & 0xFFFF);333 334 seq_printf(s, "%-10s\t%-10llu\n", buffer, entry->count);335 }336 337 return 0;338}339 340static const struct seq_operations tt_sops = {341 .start = cdev_seq_start,342 .next = cdev_seq_next,343 .stop = cdev_seq_stop,344 .show = cdev_tt_seq_show,345};346 347DEFINE_SEQ_ATTRIBUTE(tt);348 349static int cdev_dt_seq_show(struct seq_file *s, void *v)350{351 struct thermal_debugfs *thermal_dbg = s->private;352 struct cdev_debugfs *cdev_dbg = &thermal_dbg->cdev_dbg;353 struct list_head *durations = cdev_dbg->durations;354 struct cdev_record *entry;355 int i = *(loff_t *)v;356 357 if (!i)358 seq_puts(s, "State\tResidency\n");359 360 list_for_each_entry(entry, &durations[i], node) {361 s64 duration = ktime_to_ms(entry->residency);362 363 if (entry->id == cdev_dbg->current_state)364 duration += ktime_ms_delta(ktime_get(),365 cdev_dbg->timestamp);366 367 seq_printf(s, "%-5d\t%-10llu\n", entry->id, duration);368 }369 370 return 0;371}372 373static const struct seq_operations dt_sops = {374 .start = cdev_seq_start,375 .next = cdev_seq_next,376 .stop = cdev_seq_stop,377 .show = cdev_dt_seq_show,378};379 380DEFINE_SEQ_ATTRIBUTE(dt);381 382static int cdev_clear_set(void *data, u64 val)383{384 struct thermal_debugfs *thermal_dbg = data;385 386 if (!val)387 return -EINVAL;388 389 mutex_lock(&thermal_dbg->lock);390 391 thermal_debugfs_cdev_clear(&thermal_dbg->cdev_dbg);392 393 mutex_unlock(&thermal_dbg->lock);394 395 return 0;396}397 398DEFINE_DEBUGFS_ATTRIBUTE(cdev_clear_fops, NULL, cdev_clear_set, "%llu\n");399 400/**401 * thermal_debug_cdev_state_update - Update a cooling device state change402 *403 * Computes a transition and the duration of the previous state residency.404 *405 * @cdev : a pointer to a cooling device406 * @new_state: an integer corresponding to the new cooling device state407 */408void thermal_debug_cdev_state_update(const struct thermal_cooling_device *cdev,409 int new_state)410{411 struct thermal_debugfs *thermal_dbg = cdev->debugfs;412 struct cdev_debugfs *cdev_dbg;413 struct cdev_record *cdev_record;414 int transition, old_state;415 416 if (!thermal_dbg || (thermal_dbg->cdev_dbg.current_state == new_state))417 return;418 419 mutex_lock(&thermal_dbg->lock);420 421 cdev_dbg = &thermal_dbg->cdev_dbg;422 423 old_state = cdev_dbg->current_state;424 425 /*426 * Get the old state information in the durations list. If427 * this one does not exist, a new allocated one will be428 * returned. Recompute the total duration in the old state and429 * get a new timestamp for the new state.430 */431 cdev_record = thermal_debugfs_cdev_record_get(thermal_dbg,432 cdev_dbg->durations,433 old_state);434 if (cdev_record) {435 ktime_t now = ktime_get();436 ktime_t delta = ktime_sub(now, cdev_dbg->timestamp);437 cdev_record->residency = ktime_add(cdev_record->residency, delta);438 cdev_dbg->timestamp = now;439 }440 441 cdev_dbg->current_state = new_state;442 443 /*444 * Create a record for the new state if it is not there, so its445 * duration will be printed by cdev_dt_seq_show() as expected if it446 * runs before the next state transition.447 */448 thermal_debugfs_cdev_record_get(thermal_dbg, cdev_dbg->durations, new_state);449 450 transition = (old_state << 16) | new_state;451 452 /*453 * Get the transition in the transitions list. If this one454 * does not exist, a new allocated one will be returned.455 * Increment the occurrence of this transition which is stored456 * in the value field.457 */458 cdev_record = thermal_debugfs_cdev_record_get(thermal_dbg,459 cdev_dbg->transitions,460 transition);461 if (cdev_record)462 cdev_record->count++;463 464 cdev_dbg->total++;465 466 mutex_unlock(&thermal_dbg->lock);467}468 469/**470 * thermal_debug_cdev_add - Add a cooling device debugfs entry471 *472 * Allocates a cooling device object for debug, initializes the473 * statistics and create the entries in sysfs.474 * @cdev: a pointer to a cooling device475 * @state: current state of the cooling device476 */477void thermal_debug_cdev_add(struct thermal_cooling_device *cdev, int state)478{479 struct thermal_debugfs *thermal_dbg;480 struct cdev_debugfs *cdev_dbg;481 int i;482 483 thermal_dbg = thermal_debugfs_add_id(d_cdev, cdev->id);484 if (!thermal_dbg)485 return;486 487 cdev_dbg = &thermal_dbg->cdev_dbg;488 489 for (i = 0; i < CDEVSTATS_HASH_SIZE; i++) {490 INIT_LIST_HEAD(&cdev_dbg->transitions[i]);491 INIT_LIST_HEAD(&cdev_dbg->durations[i]);492 }493 494 cdev_dbg->current_state = state;495 cdev_dbg->timestamp = ktime_get();496 497 /*498 * Create a record for the initial cooling device state, so its499 * duration will be printed by cdev_dt_seq_show() as expected if it500 * runs before the first state transition.501 */502 thermal_debugfs_cdev_record_get(thermal_dbg, cdev_dbg->durations, state);503 504 debugfs_create_file("trans_table", 0400, thermal_dbg->d_top,505 thermal_dbg, &tt_fops);506 507 debugfs_create_file("time_in_state_ms", 0400, thermal_dbg->d_top,508 thermal_dbg, &dt_fops);509 510 debugfs_create_file("clear", 0200, thermal_dbg->d_top,511 thermal_dbg, &cdev_clear_fops);512 513 debugfs_create_u32("total_trans", 0400, thermal_dbg->d_top,514 &cdev_dbg->total);515 516 cdev->debugfs = thermal_dbg;517}518 519/**520 * thermal_debug_cdev_remove - Remove a cooling device debugfs entry521 *522 * Frees the statistics memory data and remove the debugfs entry523 *524 * @cdev: a pointer to a cooling device525 */526void thermal_debug_cdev_remove(struct thermal_cooling_device *cdev)527{528 struct thermal_debugfs *thermal_dbg;529 530 mutex_lock(&cdev->lock);531 532 thermal_dbg = cdev->debugfs;533 if (!thermal_dbg) {534 mutex_unlock(&cdev->lock);535 return;536 }537 538 cdev->debugfs = NULL;539 540 mutex_unlock(&cdev->lock);541 542 mutex_lock(&thermal_dbg->lock);543 544 thermal_debugfs_cdev_clear(&thermal_dbg->cdev_dbg);545 546 mutex_unlock(&thermal_dbg->lock);547 548 thermal_debugfs_remove_id(thermal_dbg);549}550 551static struct tz_episode *thermal_debugfs_tz_event_alloc(struct thermal_zone_device *tz,552 ktime_t now)553{554 struct tz_episode *tze;555 int i;556 557 tze = kzalloc(struct_size(tze, trip_stats, tz->num_trips), GFP_KERNEL);558 if (!tze)559 return NULL;560 561 INIT_LIST_HEAD(&tze->node);562 tze->timestamp = now;563 tze->duration = KTIME_MIN;564 tze->max_temp = INT_MIN;565 566 for (i = 0; i < tz->num_trips; i++) {567 tze->trip_stats[i].trip_temp = THERMAL_TEMP_INVALID;568 tze->trip_stats[i].min = INT_MAX;569 }570 571 return tze;572}573 574void thermal_debug_tz_trip_up(struct thermal_zone_device *tz,575 const struct thermal_trip *trip)576{577 struct thermal_debugfs *thermal_dbg = tz->debugfs;578 int trip_id = thermal_zone_trip_id(tz, trip);579 ktime_t now = ktime_get();580 struct trip_stats *trip_stats;581 struct tz_debugfs *tz_dbg;582 struct tz_episode *tze;583 584 if (!thermal_dbg)585 return;586 587 tz_dbg = &thermal_dbg->tz_dbg;588 589 mutex_lock(&thermal_dbg->lock);590 591 /*592 * The mitigation is starting. A mitigation can contain593 * several episodes where each of them is related to a594 * temperature crossing a trip point. The episodes are595 * nested. That means when the temperature is crossing the596 * first trip point, the duration begins to be measured. If597 * the temperature continues to increase and reaches the598 * second trip point, the duration of the first trip must be599 * also accumulated.600 *601 * eg.602 *603 * temp604 * ^605 * | --------606 * trip 2 / \ ------607 * | /| |\ /| |\608 * trip 1 / | | `---- | | \609 * | /| | | | | |\610 * trip 0 / | | | | | | \611 * | /| | | | | | | |\612 * | / | | | | | | | | `--613 * | / | | | | | | | |614 * |----- | | | | | | | |615 * | | | | | | | | |616 * --------|-|-|--------|--------|------|-|-|------------------> time617 * | | |<--t2-->| |<-t2'>| | |618 * | | | |619 * | |<------------t1------------>| |620 * | |621 * |<-------------t0--------------->|622 *623 */624 if (!tz_dbg->nr_trips) {625 tze = thermal_debugfs_tz_event_alloc(tz, now);626 if (!tze)627 goto unlock;628 629 list_add(&tze->node, &tz_dbg->tz_episodes);630 }631 632 /*633 * Each time a trip point is crossed the way up, the trip_id634 * is stored in the trip_crossed array and the nr_trips is635 * incremented. A nr_trips equal to zero means we are entering636 * a mitigation episode.637 *638 * The trip ids may not be in the ascending order but the639 * result in the array trips_crossed will be in the ascending640 * temperature order. The function detecting when a trip point641 * is crossed the way down will handle the very rare case when642 * the trip points may have been reordered during this643 * mitigation episode.644 */645 tz_dbg->trips_crossed[tz_dbg->nr_trips++] = trip_id;646 647 tze = list_first_entry(&tz_dbg->tz_episodes, struct tz_episode, node);648 trip_stats = &tze->trip_stats[trip_id];649 trip_stats->trip_temp = trip->temperature;650 trip_stats->trip_hyst = trip->hysteresis;651 trip_stats->timestamp = now;652 653unlock:654 mutex_unlock(&thermal_dbg->lock);655}656 657static void tz_episode_close_trip(struct tz_episode *tze, int trip_id, ktime_t now)658{659 struct trip_stats *trip_stats = &tze->trip_stats[trip_id];660 ktime_t delta = ktime_sub(now, trip_stats->timestamp);661 662 trip_stats->duration = ktime_add(delta, trip_stats->duration);663 /* Mark the end of mitigation for this trip point. */664 trip_stats->timestamp = KTIME_MAX;665}666 667void thermal_debug_tz_trip_down(struct thermal_zone_device *tz,668 const struct thermal_trip *trip)669{670 struct thermal_debugfs *thermal_dbg = tz->debugfs;671 int trip_id = thermal_zone_trip_id(tz, trip);672 ktime_t now = ktime_get();673 struct tz_episode *tze;674 struct tz_debugfs *tz_dbg;675 int i;676 677 if (!thermal_dbg)678 return;679 680 tz_dbg = &thermal_dbg->tz_dbg;681 682 mutex_lock(&thermal_dbg->lock);683 684 /*685 * The temperature crosses the way down but there was not686 * mitigation detected before. That may happen when the687 * temperature is greater than a trip point when registering a688 * thermal zone, which is a common use case as the kernel has689 * no mitigation mechanism yet at boot time.690 */691 if (!tz_dbg->nr_trips)692 goto out;693 694 for (i = tz_dbg->nr_trips - 1; i >= 0; i--) {695 if (tz_dbg->trips_crossed[i] == trip_id)696 break;697 }698 699 if (i < 0)700 goto out;701 702 tz_dbg->nr_trips--;703 704 if (i < tz_dbg->nr_trips)705 tz_dbg->trips_crossed[i] = tz_dbg->trips_crossed[tz_dbg->nr_trips];706 707 tze = list_first_entry(&tz_dbg->tz_episodes, struct tz_episode, node);708 709 tz_episode_close_trip(tze, trip_id, now);710 711 /*712 * This event closes the mitigation as we are crossing the713 * last trip point the way down.714 */715 if (!tz_dbg->nr_trips)716 tze->duration = ktime_sub(now, tze->timestamp);717 718out:719 mutex_unlock(&thermal_dbg->lock);720}721 722void thermal_debug_update_trip_stats(struct thermal_zone_device *tz)723{724 struct thermal_debugfs *thermal_dbg = tz->debugfs;725 struct tz_debugfs *tz_dbg;726 struct tz_episode *tze;727 int i;728 729 if (!thermal_dbg)730 return;731 732 tz_dbg = &thermal_dbg->tz_dbg;733 734 mutex_lock(&thermal_dbg->lock);735 736 if (!tz_dbg->nr_trips)737 goto out;738 739 tze = list_first_entry(&tz_dbg->tz_episodes, struct tz_episode, node);740 741 if (tz->temperature > tze->max_temp)742 tze->max_temp = tz->temperature;743 744 for (i = 0; i < tz_dbg->nr_trips; i++) {745 int trip_id = tz_dbg->trips_crossed[i];746 struct trip_stats *trip_stats = &tze->trip_stats[trip_id];747 748 trip_stats->min = min(trip_stats->min, tz->temperature);749 trip_stats->avg += (tz->temperature - trip_stats->avg) /750 ++trip_stats->count;751 }752out:753 mutex_unlock(&thermal_dbg->lock);754}755 756static void *tze_seq_start(struct seq_file *s, loff_t *pos)757{758 struct thermal_debugfs *thermal_dbg = s->private;759 struct tz_debugfs *tz_dbg = &thermal_dbg->tz_dbg;760 761 mutex_lock(&thermal_dbg->lock);762 763 return seq_list_start(&tz_dbg->tz_episodes, *pos);764}765 766static void *tze_seq_next(struct seq_file *s, void *v, loff_t *pos)767{768 struct thermal_debugfs *thermal_dbg = s->private;769 struct tz_debugfs *tz_dbg = &thermal_dbg->tz_dbg;770 771 return seq_list_next(v, &tz_dbg->tz_episodes, pos);772}773 774static void tze_seq_stop(struct seq_file *s, void *v)775{776 struct thermal_debugfs *thermal_dbg = s->private;777 778 mutex_unlock(&thermal_dbg->lock);779}780 781static int tze_seq_show(struct seq_file *s, void *v)782{783 struct thermal_debugfs *thermal_dbg = s->private;784 struct thermal_zone_device *tz = thermal_dbg->tz_dbg.tz;785 struct thermal_trip_desc *td;786 struct tz_episode *tze;787 u64 duration_ms;788 int trip_id;789 char c;790 791 tze = list_entry((struct list_head *)v, struct tz_episode, node);792 793 if (tze->duration == KTIME_MIN) {794 /* Mitigation in progress. */795 duration_ms = ktime_to_ms(ktime_sub(ktime_get(), tze->timestamp));796 c = '>';797 } else {798 duration_ms = ktime_to_ms(tze->duration);799 c = '=';800 }801 802 seq_printf(s, ",-Mitigation at %llums, duration%c%llums, max. temp=%dm°C\n",803 ktime_to_ms(tze->timestamp), c, duration_ms, tze->max_temp);804 805 seq_printf(s, "| trip | type | temp(m°C) | hyst(m°C) | duration(ms) | avg(m°C) | min(m°C) |\n");806 807 for_each_trip_desc(tz, td) {808 const struct thermal_trip *trip = &td->trip;809 struct trip_stats *trip_stats;810 811 /*812 * There is no possible mitigation happening at the813 * critical trip point, so the stats will be always814 * zero, skip this trip point815 */816 if (trip->type == THERMAL_TRIP_CRITICAL)817 continue;818 819 trip_id = thermal_zone_trip_id(tz, trip);820 trip_stats = &tze->trip_stats[trip_id];821 822 /* Skip trips without any stats. */823 if (trip_stats->trip_temp == THERMAL_TEMP_INVALID)824 continue;825 826 if (trip_stats->timestamp != KTIME_MAX) {827 /* Mitigation in progress. */828 ktime_t delta = ktime_sub(ktime_get(),829 trip_stats->timestamp);830 831 delta = ktime_add(delta, trip_stats->duration);832 duration_ms = ktime_to_ms(delta);833 c = '>';834 } else {835 duration_ms = ktime_to_ms(trip_stats->duration);836 c = ' ';837 }838 839 seq_printf(s, "| %*d | %*s | %*d | %*d | %c%*lld | %*d | %*d |\n",840 4 , trip_id,841 8, thermal_trip_type_name(trip->type),842 9, trip_stats->trip_temp,843 9, trip_stats->trip_hyst,844 c, 11, duration_ms,845 9, trip_stats->avg,846 9, trip_stats->min);847 }848 849 return 0;850}851 852static const struct seq_operations tze_sops = {853 .start = tze_seq_start,854 .next = tze_seq_next,855 .stop = tze_seq_stop,856 .show = tze_seq_show,857};858 859DEFINE_SEQ_ATTRIBUTE(tze);860 861void thermal_debug_tz_add(struct thermal_zone_device *tz)862{863 struct thermal_debugfs *thermal_dbg;864 struct tz_debugfs *tz_dbg;865 866 thermal_dbg = thermal_debugfs_add_id(d_tz, tz->id);867 if (!thermal_dbg)868 return;869 870 tz_dbg = &thermal_dbg->tz_dbg;871 872 tz_dbg->tz = tz;873 874 tz_dbg->trips_crossed = kzalloc(sizeof(int) * tz->num_trips, GFP_KERNEL);875 if (!tz_dbg->trips_crossed) {876 thermal_debugfs_remove_id(thermal_dbg);877 return;878 }879 880 INIT_LIST_HEAD(&tz_dbg->tz_episodes);881 882 debugfs_create_file("mitigations", 0400, thermal_dbg->d_top,883 thermal_dbg, &tze_fops);884 885 tz->debugfs = thermal_dbg;886}887 888void thermal_debug_tz_remove(struct thermal_zone_device *tz)889{890 struct thermal_debugfs *thermal_dbg;891 struct tz_episode *tze, *tmp;892 struct tz_debugfs *tz_dbg;893 int *trips_crossed;894 895 mutex_lock(&tz->lock);896 897 thermal_dbg = tz->debugfs;898 if (!thermal_dbg) {899 mutex_unlock(&tz->lock);900 return;901 }902 903 tz->debugfs = NULL;904 905 mutex_unlock(&tz->lock);906 907 tz_dbg = &thermal_dbg->tz_dbg;908 909 mutex_lock(&thermal_dbg->lock);910 911 trips_crossed = tz_dbg->trips_crossed;912 913 list_for_each_entry_safe(tze, tmp, &tz_dbg->tz_episodes, node) {914 list_del(&tze->node);915 kfree(tze);916 }917 918 mutex_unlock(&thermal_dbg->lock);919 920 thermal_debugfs_remove_id(thermal_dbg);921 kfree(trips_crossed);922}923 924void thermal_debug_tz_resume(struct thermal_zone_device *tz)925{926 struct thermal_debugfs *thermal_dbg = tz->debugfs;927 ktime_t now = ktime_get();928 struct tz_debugfs *tz_dbg;929 struct tz_episode *tze;930 int i;931 932 if (!thermal_dbg)933 return;934 935 mutex_lock(&thermal_dbg->lock);936 937 tz_dbg = &thermal_dbg->tz_dbg;938 939 if (!tz_dbg->nr_trips)940 goto out;941 942 /*943 * A mitigation episode was in progress before the preceding system944 * suspend transition, so close it because the zone handling is starting945 * over from scratch.946 */947 tze = list_first_entry(&tz_dbg->tz_episodes, struct tz_episode, node);948 949 for (i = 0; i < tz_dbg->nr_trips; i++)950 tz_episode_close_trip(tze, tz_dbg->trips_crossed[i], now);951 952 tze->duration = ktime_sub(now, tze->timestamp);953 954 tz_dbg->nr_trips = 0;955 956out:957 mutex_unlock(&thermal_dbg->lock);958}959