brintos

brintos / linux-shallow public Read only

0
0
Text · 24.8 KiB · 939d3e5 Raw
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