[知其然不知其所以然-5] 为什么我的风扇温度很低却转不停/或者温度很高却根本不转?

band of brothers:

I'm not a kernel developer, but I served in a company of kernel developers.



大约2周以前,我开始接手bugzilla上的一系列跟风扇控制相关的故障。总结起来,就是两类故障:

1)在系统经历过一次Suspend/Resume (S3或者S4)之后,风扇就不再转动,直到温度升高到危险程度,风扇才被激发

2)在系统启动之后,风扇就一直以最高速度运行,就算温度降下来,风扇也不会停止。

这两类故障其实都已经被Kernel developer定位了,并且patch也已经有现成的,我主要负责把这两个补丁upstream。

内核是精妙的东西,稍有不慎就导致regression,我们需要对内核保持敬畏之心。因此,为

保险起见,我把这两故障的排查过程和patch都走查了一遍,其实更重要的是通过回放故障排查的过程,

学习kernel developer们的排查问题思路。


我们先来看第一个故障,https://bugzilla.kernel.org/show_bug.cgi?id=78201

首先,reporter抱怨说,风扇在不做Suspend前,都是正常工作的:温度升高一级,风扇转速就升高一级,

反之亦然。但当他做了Suspend到S4恢复之后,发现这个风扇不转了,并且就算当前温度比较高,风扇也无动于衷,

直到温度继续升高到一个比较危险的程度,风扇才开始转动。

好吧,排查这种问题的两招就是:一是查看各个风扇此时的状态,二是让reporter打开thermal management模块

的dynamic debug选项再复现。reporter照做了。

先检查系统里哪些cooling device是风扇:

# grep . /sys/class/thermal/cooling_device*/device/path
/sys/class/thermal/cooling_device0/device/path:\_TZ_.FAN0
/sys/class/thermal/cooling_device1/device/path:\_TZ_.FAN1
/sys/class/thermal/cooling_device2/device/path:\_TZ_.FAN2
/sys/class/thermal/cooling_device3/device/path:\_TZ_.FAN3
/sys/class/thermal/cooling_device4/device/path:\_TZ_.FAN4
/sys/class/thermal/cooling_device5/device/path:\_PR_.CPU0
/sys/class/thermal/cooling_device6/device/path:\_PR_.CPU1
/sys/class/thermal/cooling_device7/device/path:\_SB_.PCI0.GFX0.DD02
然后开启thermal的dynamic debug:

echo 'module thermal_sys +fp' > /sys/kernel/debug/dynamic_debug/control
再尝试做一下suspend/resume,获取到调试信息,这里截取从S4 resume回来的最早的一段信息:

[ 1823.743450] ACPI: Waking up from system sleep state S4
[ 1823.769486] update_temperature: thermal thermal_zone1: last_temperature=75000, current_temperature=66000
[ 1823.769490] thermal_zone_trip_update: thermal thermal_zone1: Trip1[type=1,temp=107000]:trend=2,throttle=0
[ 1823.769494] get_target_state: thermal cooling_device6: cur_state=0
[ 1823.769496] thermal_zone_trip_update: thermal cooling_device6: old_target=-1, target=-1
[ 1823.769498] get_target_state: thermal cooling_device5: cur_state=0
[ 1823.769500] thermal_zone_trip_update: thermal cooling_device5: old_target=-1, target=-1
[ 1823.769503] thermal_zone_trip_update: thermal thermal_zone1: Trip2[type=0,temp=90000]:trend=2,throttle=0
[ 1823.769569] get_target_state: thermal cooling_device0: cur_state=0
[ 1823.769571] thermal_zone_trip_update: thermal cooling_device0: old_target=-1, target=-1
[ 1823.769573] thermal_zone_trip_update: thermal thermal_zone1: Trip3[type=0,temp=67000]:trend=2,throttle=0
[ 1823.769634] get_target_state: thermal cooling_device1: cur_state=0
[ 1823.769636] thermal_zone_trip_update: thermal cooling_device1: old_target=1, target=-1
[ 1823.769638] thermal_cdev_update: thermal cooling_device1: zone1->target=18446744073709551615
[ 1823.769640] thermal_cdev_update: thermal cooling_device1: set to state 0
[ 1823.769642] thermal_zone_trip_update: thermal thermal_zone1: Trip4[type=0,temp=55000]:trend=1,throttle=1
[ 1823.769703] get_target_state: thermal cooling_device2: cur_state=0
[ 1823.769704] thermal_zone_trip_update: thermal cooling_device2: old_target=1, target=1
[ 1823.769707] thermal_zone_trip_update: thermal thermal_zone1: Trip5[type=0,temp=45000]:trend=1,throttle=1
[ 1823.769767] get_target_state: thermal cooling_device3: cur_state=0
[ 1823.769769] thermal_zone_trip_update: thermal cooling_device3: old_target=1, target=1
[ 1823.769771] thermal_zone_trip_update: thermal thermal_zone1: Trip6[type=0,temp=30000]:trend=1,throttle=1
[ 1823.769831] get_target_state: thermal cooling_device4: cur_state=0
[ 1823.769832] thermal_zone_trip_update: thermal cooling_device4: old_target=1, target=1
[ 1823.771496] update_temperature: thermal thermal_zone2: last_temperature=49000, current_temperature=55000
[ 1823.771499] thermal_zone_trip_update: thermal thermal_zone2: Trip1[type=1,temp=95000]:trend=2,throttle=0
[ 1823.771501] get_target_state: thermal cooling_device6: cur_state=0
[ 1823.771502] thermal_zone_trip_update: thermal cooling_device6: old_target=-1, target=-1
[ 1823.771504] get_target_state: thermal cooling_device5: cur_state=0
[ 1823.771506] thermal_zone_trip_update: thermal cooling_device5: old_target=-1, target=-1
[ 1823.778812] ACPI: \_SB_.SLPB: ACPI_NOTIFY_DEVICE_WAKE event

这么一大块调试信息容易看晕,比较好的方法是先看代码,理清思路,再来分析这些log对应着什么流程。


首先我们来说明,风扇控制是由什么因素决定的。一句话,温度。温度比较低时,风扇处于静止状态;当温度升高时,到达某个临界点1(trip 2),

触发一个中断,在中断处理函数里,通知风扇以速度1来开始运转;如果workload比较高,温度继续上升,到达临界点2(trip 2),

再次触发一个中断,通知风扇以速度2运转,依次类推。直到到达一个温度上限,将会触发系统紧急关闭以保护硬件。

上面提到的这个风扇,就是cooling device。另外,系统中可能不止存在一个风扇,还可能有多个风扇,于是就有

cooling_device1,cooling_device2,cooling_device3... cooling_deviceN,每个风扇基本就只对一个区域的温度散热

负责,这个抽象的区域,就是一个thermal_zone。cooling_device对thermal_zone是M:1的关系,而且一个cooling_device

只属于一个thermal_zone,而一个thermal_zone可以有多个cooling_device。比如:

$ pwd
/sys/class/thermal/thermal_zone0
$ ls -l
total 0
lrwxrwxrwx 1 root root    0  9月 18 16:02 cdev0 -> ../cooling_device4
-r--r--r-- 1 root root 4096  9月 18 16:02 cdev0_trip_point
-rw-r--r-- 1 root root 4096  9月 18 16:02 cdev0_weight
lrwxrwxrwx 1 root root    0  9月 18 16:02 cdev1 -> ../cooling_device3
-r--r--r-- 1 root root 4096  9月 18 16:02 cdev1_trip_point
-rw-r--r-- 1 root root 4096  9月 18 16:02 cdev1_weight
lrwxrwxrwx 1 root root    0  9月 18 16:02 cdev2 -> ../cooling_device2
-r--r--r-- 1 root root 4096  9月 18 16:02 cdev2_trip_point
-rw-r--r-- 1 root root 4096  9月 18 16:02 cdev2_weight
lrwxrwxrwx 1 root root    0  9月 18 16:02 cdev3 -> ../cooling_device1
-r--r--r-- 1 root root 4096  9月 18 16:02 cdev3_trip_point
-rw-r--r-- 1 root root 4096  9月 18 16:02 cdev3_weight
lrwxrwxrwx 1 root root    0  9月 18 16:02 device -> ../../../LNXSYSTM:00/LNXSYBUS:01/LNXTHERM:00
--w------- 1 root root 4096  9月 18 16:02 emul_temp
-rw-r--r-- 1 root root 4096  9月 18 16:02 integral_cutoff
-rw-r--r-- 1 root root 4096  9月 18 16:02 k_d
-rw-r--r-- 1 root root 4096  9月 18 16:02 k_i
-rw-r--r-- 1 root root 4096  9月 18 16:02 k_po
-rw-r--r-- 1 root root 4096  9月 18 16:02 k_pu
-rw-r--r-- 1 root root 4096  9月 18 16:02 mode
-rw-r--r-- 1 root root 4096  9月 18 16:02 offset
-rw-r--r-- 1 root root 4096  9月 18 16:02 passive
-rw-r--r-- 1 root root 4096  9月 18 16:02 policy
drwxr-xr-x 2 root root    0  9月 18 16:02 power
-rw-r--r-- 1 root root 4096  9月 18 16:02 slope
lrwxrwxrwx 1 root root    0  9月 18 16:02 subsystem -> ../../../../class/thermal
-rw-r--r-- 1 root root 4096  9月 18 16:02 sustainable_power
-r--r--r-- 1 root root 4096  9月 18 16:02 temp
-r--r--r-- 1 root root 4096  9月 18 16:02 trip_point_0_temp
-r--r--r-- 1 root root 4096  9月 18 16:02 trip_point_0_type
-r--r--r-- 1 root root 4096  9月 18 16:02 trip_point_1_temp
-r--r--r-- 1 root root 4096  9月 18 16:02 trip_point_1_type
-r--r--r-- 1 root root 4096  9月 18 16:02 trip_point_2_temp
-r--r--r-- 1 root root 4096  9月 18 16:02 trip_point_2_type
-r--r--r-- 1 root root 4096  9月 18 16:02 trip_point_3_temp
-r--r--r-- 1 root root 4096  9月 18 16:02 trip_point_3_type
-r--r--r-- 1 root root 4096  9月 18 16:02 trip_point_4_temp
-r--r--r-- 1 root root 4096  9月 18 16:02 trip_point_4_type
-r--r--r-- 1 root root 4096  9月 18 16:02 type
-rw-r--r-- 1 root root 4096  9月 18 16:02 uevent
可以看出thermal_zone0里有4个cooling device。分析代码,根据温度变化趋势来设置cooling device

状态(也就是设置风扇转速)的简化后代码在:

void thermal_zone_device_update(struct thermal_zone_device *tz)
{
	int count;

	update_temperature(tz); // step 0, update thermal zone's temperature

	for (count = 0; count < tz->trips; count++)
		handle_thermal_trip(tz, count);//step 1, checking the temperature with each trip point
}

上面的代码逻辑就是,每次系统到了一个采样点,需要根据温度变化趋势来设置各个散热器的工作速度。

其中,update_temperature函数主要做一个准备工作,首先备份上一次thermal_zone的温度,

接着根据寄存器值更新thermal zone的当前温度,这两个值主要用来判断thermal zone的温度是上升还是下降了,

从而为下一步调整cooling device的状态做准备,代码可以抽象为:

<span style="white-space:pre">	</span>mutex_lock(&tz->lock);
	tz->last_temperature = tz->temperature;
	tz->temperature = temp;
	mutex_unlock(&tz->lock);

注意这里的mutex锁,在文章末尾我们要讨论这把锁的合理性。

在更新完thermal zone的温度后,进入实际的调整逻辑代码。我们先来想一下,

如果是你来设计,你会怎么实现这个功能:随着温度的变化,动态的调整风扇的转速?
之前我们已经介绍过,在比较好的硬件系统里,每个风扇都支持多种转速,在温度达到越来越高

的临界点时,就触发更高的转速。因此,我们会想到,每个thermal_zone应该有一系列从低到高的

温度临界点,这就是trip_point,然后,每个cooling_device有一系列从低到高的state,那么,

trip point如何与state联系起来呢, 我们需要一个结构,来表达如下意思:但thermal zone的温度

达到trip_pointX时, 我们申请开启cooling device的stateY, 这就需要一个连接件,把trip point和state连接起来,

这就是thermal zone结构体里thermal_instance的来历。可以将这个结构简化成:

struct thermal_instance {
int trip;
unsigned long target;
}
thermal_instance可以理解成一个cooling device,在升温到trip point后,需要被设置成target这个状态。
温度调整的逻辑,就是围绕着thermal_instance来操作。接着回到之前的函数,thermal_zone_device_update,

这个函数会挨个检查每个trip point,看当前thermal_zone是否刚好超过某个trip point,如果是的话,

就开启这个trip point对应的设备状态,也就是激活对应的thermal_instance。(你觉得有没有更好的算法?)

先继续看针对每个trip point的逻辑判断:

static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)
{
    governor->throttle(tz, trip);
}
凡是涉及到策略的地方,Linux就喜欢用governor来完成,具体的策略thermal_core的框架不应该参与,

而应该让各个驱动来填充和实现,这也是Linux内核的开发原则。 这里的governor我们来考察常用的

step_wise,位于driver/thermal/step_wise.c中。这个函数名也很贴切,Throttling就是节流,

暗含流量过大(温度过高)的意思。

static int step_wise_throttle(struct thermal_zone_device *tz, int trip)
{
	struct thermal_instance *instance;

	thermal_zone_trip_update(tz, trip);

	mutex_lock(&tz->lock);

	list_for_each_entry(instance, &tz->thermal_instances, tz_node)
		thermal_cdev_update(instance->cdev);

	mutex_unlock(&tz->lock);

	return 0;
}

驱动实现的策略逻辑,分成了两步,一个是thermal_zone里所有thermal_instance的更新,

接着是根据调整后thermal_instance的结果,将对应的cooling device设置到想要的状态。

按照我们刚才的理解,调整thermal_instance的结果应该是给某一个thermal_instance设置

标识,表示温度变化范围刚好跨过了该thermal_instance代表的临界点,然后下一步更新

cooling device时,才会将该thermal_instance代表的风扇转速设置到cooling device的寄存器中。

是的,逻辑就是这样,不过有稍微一点改动,调整thermal_instance应该是找到多个被“跨越”的

thermal_instance,因为,一个thermal_zone里可能包含不止一个cooling device(风扇),所以

需要给多个cooling device调整转速,因而需要找出所有被跨越的thermal_instance来更新:


static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
{
	long trip_temp;
	enum thermal_trip_type trip_type;
	enum thermal_trend trend;
	struct thermal_instance *instance;
	bool throttle = false;
	int old_target;

	if (trip == THERMAL_TRIPS_NONE) {
		trip_temp = tz->forced_passive;
		trip_type = THERMAL_TRIPS_NONE;
	} else {
		tz->ops->get_trip_temp(tz, trip, &trip_temp);
		tz->ops->get_trip_type(tz, trip, &trip_type);
	}

	trend = get_tz_trend(tz, trip);

	if (tz->temperature >= trip_temp) {
		throttle = true;
		trace_thermal_zone_trip(tz, trip, trip_type);
	}

	dev_dbg(&tz->device, "Trip%d[type=%d,temp=%ld]:trend=%d,throttle=%d\n",
				trip, trip_type, trip_temp, trend, throttle);

	mutex_lock(&tz->lock);

	list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
		if (instance->trip != trip)
			continue;

		old_target = instance->target;
		instance->target = get_target_state(instance, trend, throttle);
		dev_dbg(&instance->cdev->device, "old_target=%d, target=%d\n",
					old_target, (int)instance->target);

		if (old_target == instance->target)
			continue;

		/* Activate a passive thermal instance */
		if (old_target == THERMAL_NO_TARGET &&
			instance->target != THERMAL_NO_TARGET)
			update_passive_instance(tz, trip_type, 1);
		/* Deactivate a passive thermal instance */
		else if (old_target != THERMAL_NO_TARGET &&
			instance->target == THERMAL_NO_TARGET)
			update_passive_instance(tz, trip_type, -1);


		instance->cdev->updated = false; /* cdev needs update */
	}

	mutex_unlock(&tz->lock);
}


实际的代码实现中,并没有考虑flag这个因素,而是针对每个thermal_instance,都无差别的更新了他们本次

应该被设置到的状态,也就是instance->target字段。说到thermal_instance的状态,之前我们曾经说过,

每个thermal_instance对应着一个cooling device的状态state(转速),其实代码这里还做了一个扩展,

每个thermal_instance对应着cooling device的一个状态范围instance->lower到instance->upper,最终

thermal_instance要设置到的状态target就在这个范围中选取,选取的依据是温度变化的趋势,与上次温度相比,是增加还是减少,

以及是否达到本thermal_instance表示的临界点。趋势用trend变量表示,而临界点是否达到用throttle变量表示。

我们先来看趋势的计算:

int get_tz_trend(struct thermal_zone_device *tz, int trip)
{
	enum thermal_trend trend;

	if (tz->emul_temperature || !tz->ops->get_trend ||
	    tz->ops->get_trend(tz, trip, &trend)) {
		if (tz->temperature > tz->last_temperature)
			trend = THERMAL_TREND_RAISING;
		else if (tz->temperature < tz->last_temperature)
			trend = THERMAL_TREND_DROPPING;
		else
			trend = THERMAL_TREND_STABLE;
	}

	return trend;
}

这个趋势是跟thermal_zone相关的,也就是每个thermal_zone会提供一套计算趋势的方法,比如在

x86平台上,这个回调是drivers/acpi/thermal.c下面的acpi_thermal_zone_ops->thermal_get_trend,

我们把这个函数抄过来:

static int thermal_get_trend(struct thermal_zone_device *thermal,
				int trip, enum thermal_trend *trend)
{
	struct acpi_thermal *tz = thermal->devdata;
	enum thermal_trip_type type;
	int i;

	if (thermal_get_trip_type(thermal, trip, &type))
		return -EINVAL;

	if (type == THERMAL_TRIP_ACTIVE) {
		unsigned long trip_temp;
		unsigned long temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(
					tz->temperature, tz->kelvin_offset);
		if (thermal_get_trip_temp(thermal, trip, &trip_temp))
			return -EINVAL;

		if (temp > trip_temp) {
			*trend = THERMAL_TREND_RAISING;
			return 0;
		} else {
			/* Fall back on default trend */
			return -EINVAL;
		}
	}

	/*
	 * tz->temperature has already been updated by generic thermal layer,
	 * before this callback being invoked
	 */
	i = (tz->trips.passive.tc1 * (tz->temperature - tz->last_temperature))
		+ (tz->trips.passive.tc2
		* (tz->temperature - tz->trips.passive.temperature));

	if (i > 0)
		*trend = THERMAL_TREND_RAISING;
	else if (i < 0)
		*trend = THERMAL_TREND_DROPPING;
	else
		*trend = THERMAL_TREND_STABLE;
	return 0;
}

首先,我们得判断这个trip是属于哪个属性,代表的是ACTIVE还是PASSIVE温度,

对于ACPI兼容平台下的thermal zone(特别FAN这个cooling device所在的thermal zone),

各个trip的属性是这样设计的:

trip=0: 是critical temperature,达到这个温度必须关机

trip=1:是hot temperature,达到这个温度需要风扇全速运转

trip=2:是passive temperature,达到这个温度需要device降频

trip>=3,<=10,去active temperature,表示每达到一个温度,就需要提升转速。

具体的根据trip获取属性的函数就是thermal_get_trip_type,他的实现就是

我们上面的描述。

static int thermal_get_trip_type(struct thermal_zone_device *thermal,
				 int trip, enum thermal_trip_type *type)
{
	struct acpi_thermal *tz = thermal->devdata;
	int i;

	if (tz->trips.critical.flags.valid) {
		if (!trip) {
			*type = THERMAL_TRIP_CRITICAL;
			return 0;
		}
		trip--;
	}

	if (tz->trips.hot.flags.valid) {
		if (!trip) {
			*type = THERMAL_TRIP_HOT;
			return 0;
		}
		trip--;
	}

	if (tz->trips.passive.flags.valid) {
		if (!trip) {
			*type = THERMAL_TRIP_PASSIVE;
			return 0;
		}
		trip--;
	}

	for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE &&
		tz->trips.active[i].flags.valid; i++) {
		if (!trip) {
			*type = THERMAL_TRIP_ACTIVE;
			return 0;
		}
		trip--;
	}

	return -EINVAL;
}


我们来看一个例子:

root@x-wsb88:/sys/class/thermal/thermal_zone0# grep . *trip*
trip_point_0_temp:111000
trip_point_0_type:critical
trip_point_1_temp:85000
trip_point_1_type:active
trip_point_2_temp:75000
trip_point_2_type:active
trip_point_3_temp:65000
trip_point_3_type:active
trip_point_4_temp:55000
trip_point_4_type:active
可以看出,thermal_zone0有1个critical温度,4个active温度,没有hot温度和passive温度,

当然这个thermal_zone0是acpi兼容的thermal_zone:

root@x-wsb88:/sys/class/thermal/thermal_zone0# cat type
acpitz
回到之前的thermal_get_trend函数,在通过thermal_get_trip_type获取到trip对应的属性后,

我们根据温度历史变化,来判断当前变化趋势是升温还是降温,从上面给出的thermal_zone0

的trip属性可以看出,我们只关注active的变化就可以了,也就是走thermal_get_trend的THERMAL_TRIP_ACTIVE

判断流程,这个流程判断的是,当前thermal_zone的温度,和trip对应的温度,如果达到了,这说明

是温度上升趋势,如果没有达到,怎么判断是上升还是下降了呢?我们就可以判断thermal_zone的last_temperature

和本次thermal_zone的temperature的大小来估算趋势。好吧,我承认这里的逻辑有点奇怪,为什么不直接

判断last_temperature和本次temperature来估算趋势呢。其实代码想达到一个功能就是,如果当前温度超过了

trip点,就算你在降温,我也让你返回升温的趋势给thermal的governor,这样就可以让温度调节器governor继续保持降温的动作,

直到温度降低到trip以下才算安全。

trend升/降温趋势算好后(get_tz_trend),继续回到thermal_zone_trip_update函数,接下来就是

针对该trip对应的thermal_instance做更新了,我们会用到之前计算出来的升温趋势,和是否达到trip

这个事件,来设定thermal_instance的下一个状态,即get_target_state要完成的工作,这也是温度调节器的

核心逻辑:

static unsigned long get_target_state(struct thermal_instance *instance,
				enum thermal_trend trend, bool throttle)
{
	cdev->ops->get_cur_state(cdev, &cur_state);
	next_target = instance->target;

	switch (trend) {
	case THERMAL_TREND_RAISING:
		if (throttle) {
			next_target = cur_state < instance->upper ?
				    (cur_state + 1) : instance->upper;
			if (next_target < instance->lower)
				next_target = instance->lower;
		}
		break;
	case THERMAL_TREND_RAISE_FULL:
		if (throttle)
			next_target = instance->upper;
		break;
	case THERMAL_TREND_DROPPING:
		if (cur_state <= instance->lower) {
			if (!throttle)
				next_target = THERMAL_NO_TARGET;
		} else {
			next_target = cur_state - 1;
			if (next_target > instance->upper)
				next_target = instance->upper;
		}
		break;
	case THERMAL_TREND_DROP_FULL:
		if (cur_state == instance->lower) {
			if (!throttle)
				next_target = THERMAL_NO_TARGET;
		} else
			next_target = instance->lower;
		break;
	default:
		break;
	}

	return next_target;
}
首先获取该thermal_zone对应的cooling device的当前状态,接着根据趋势是上升还是下降,

以及是否到达了trip,来设置下一个状态。这个函数前面的注释也说明了设置的策略是:

/*
 * If the temperature is higher than a trip point,
 *    a. if the trend is THERMAL_TREND_RAISING, use higher cooling
 *       state for this trip point
 *    b. if the trend is THERMAL_TREND_DROPPING, use lower cooling
 *       state for this trip point
 *    c. if the trend is THERMAL_TREND_RAISE_FULL, use upper limit
 *       for this trip point
 *    d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit
 *       for this trip point
 * If the temperature is lower than a trip point,
 *    a. if the trend is THERMAL_TREND_RAISING, do nothing
 *    b. if the trend is THERMAL_TREND_DROPPING, use lower cooling
 *       state for this trip point, if the cooling state already
 *       equals lower limit, deactivate the thermal instance
 *    c. if the trend is THERMAL_TREND_RAISE_FULL, do nothing
 *    d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit,
 *       if the cooling state already equals lower limit,
 *       deactivate the thermal instance
 */
大概的意思就是分两类,当前thermal_zone的温度达到thermal_zone的trip和没有达到trip。 

如果温度达到该thermal_zone的trip,那么我尽量提升cooling device的转速,也就是

增大thermal_instance->target的值。这还要分两种情况,如果是升温趋势,

说明很可能温度刚刚跨过这个trip,或者之前的散热力度不够,那么target+1,这个没有意见;

如果本身已经在降温了,那么就把thermal_instance的target状态稍微设置低一点,减去1,

不过还是需要他一直散热(不过对于acpi兼容的thermal_zone的某个active trip来说,如果趋势是降温,

那么不可能会是trip温度以上(这是之前thermal_get_trend保证的))

如果温度没有达到该thermal_zone的trip,如果是升温,无所谓我不做操作;如果是降温,这很有可能说明我

之前的散热卓有成效,将温度刚降低到trip以下了,稍微把target转速降低一点,继续散热观察后续情况。如果

已经是lower底线了,说明这个散热器不用转这么快了,干脆就把thermal_instance停掉。

这个target的变化范围,需要在thermal_instance的

upper和lower之间。我们可以看出,状态调整的步长是1,这就是step_wise温度governor命名的来历。


如果thermal_instance需要改动,就设置该thermal_zone对应的

cooling device的updated标志为false,表示后期需要更新device的状态。还记得前面我们

提到,thermal_instance被更新后会设置一个标识吗,这个updated就是了。


这就是thermal_instance的更新逻辑。在thermal_zone里针对该trip的所有thermal_instance都更新之后,

我们来设置cooling device针对该trip的最终状态。

这里会再次遍历thermal_zone里的所有thermal_instance,找出thermal_instance有变化的cooling device,

给他们设置最终状态:


void thermal_cdev_update(struct thermal_cooling_device *cdev)
{
	struct thermal_instance *instance;
	unsigned long target = 0;

	/* cooling device is updated*/
	if (cdev->updated)
		return;

	mutex_lock(&cdev->lock);
	/* Make sure cdev enters the deepest cooling state */
	list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
		dev_dbg(&cdev->device, "zone%d->target=%lu\n",
				instance->tz->id, instance->target);
		if (instance->target == THERMAL_NO_TARGET)
			continue;
		if (instance->target > target)
			target = instance->target;
	}
	mutex_unlock(&cdev->lock);
	cdev->ops->set_cur_state(cdev, target);
	cdev->updated = true;
	trace_cdev_update(cdev, target);
	dev_dbg(&cdev->device, "set to state %lu\n", target);
}


第一句就是判断本cooling device是否需要更新,不需要的话就返回。接着又是一把锁,这把锁的顺序我们后来会统一分析其合理性。

下一步是对需要更新的cooling device,找出他下属的所有thermal_instance里,转速最快的那个状态,设置为最终的状态。

至此为止,我们分析完毕一次温度变化调整风扇转速的全过程,再来回到最初的bug现场。我们再把调试信息贴过来:

[ 1823.743450] ACPI: Waking up from system sleep state S4
[ 1823.769486] update_temperature: thermal thermal_zone1: last_temperature=75000, current_temperature=66000
[ 1823.769490] thermal_zone_trip_update: thermal thermal_zone1: Trip1[type=1,temp=107000]:trend=2,throttle=0
[ 1823.769494] get_target_state: thermal cooling_device6: cur_state=0
[ 1823.769496] thermal_zone_trip_update: thermal cooling_device6: old_target=-1, target=-1
[ 1823.769498] get_target_state: thermal cooling_device5: cur_state=0
[ 1823.769500] thermal_zone_trip_update: thermal cooling_device5: old_target=-1, target=-1
[ 1823.769503] thermal_zone_trip_update: thermal thermal_zone1: Trip2[type=0,temp=90000]:trend=2,throttle=0
[ 1823.769569] get_target_state: thermal cooling_device0: cur_state=0
[ 1823.769571] thermal_zone_trip_update: thermal cooling_device0: old_target=-1, target=-1
[ 1823.769573] thermal_zone_trip_update: thermal thermal_zone1: Trip3[type=0,temp=67000]:trend=2,throttle=0
[ 1823.769634] get_target_state: thermal cooling_device1: cur_state=0
[ 1823.769636] thermal_zone_trip_update: thermal cooling_device1: old_target=1, target=-1
[ 1823.769638] thermal_cdev_update: thermal cooling_device1: zone1->target=18446744073709551615
[ 1823.769640] thermal_cdev_update: thermal cooling_device1: set to state 0
[ 1823.769642] thermal_zone_trip_update: thermal thermal_zone1: Trip4[type=0,temp=55000]:trend=1,throttle=1
[ 1823.769703] get_target_state: thermal cooling_device2: cur_state=0
[ 1823.769704] thermal_zone_trip_update: thermal cooling_device2: old_target=1, target=1
[ 1823.769707] thermal_zone_trip_update: thermal thermal_zone1: Trip5[type=0,temp=45000]:trend=1,throttle=1
[ 1823.769767] get_target_state: thermal cooling_device3: cur_state=0
[ 1823.769769] thermal_zone_trip_update: thermal cooling_device3: old_target=1, target=1
[ 1823.769771] thermal_zone_trip_update: thermal thermal_zone1: Trip6[type=0,temp=30000]:trend=1,throttle=1
[ 1823.769831] get_target_state: thermal cooling_device4: cur_state=0
[ 1823.769832] thermal_zone_trip_update: thermal cooling_device4: old_target=1, target=1
[ 1823.778812] ACPI: \_SB_.SLPB: ACPI_NOTIFY_DEVICE_WAKE event

这段截取的这段信息,就是我们刚才分析的函数thermal_zone_device_update执行一次的流程,

也就是针对thermal_zone1里trip1到trip6,都做一次cooling device更新。

根据reporter报告的问题描述,但系统从S4回来后,风扇不再转动,直到温度升到很多,才再次转动。

根据上面的log,我们主要关注cooling_device0到cooling_device4,因为这5个cooling_device对应着风扇。

另外从上面log可以看出,这个termal_zone1有两种trip,一种是trip1,type为1,表示是THERMAL_TRIP_PASSIVE,

对应的cooling_device有5和6,根据最早的log,这两个设备是CPU0和CPU1,这里我们不关注。

trip2到trip6是0,表示是THERMAL_TRIP_ACTIVE,表示的设备是cooling device0到cooling_device4,也就是

FAN0到FAN4。

从log可以看出,从休眠恢复回来的那一刻,last_temperatue是75度(由thermal_zone的temp字段赋值而来),

当前实际温度是66度(通过寄存器读出)。请大家想一下这两个数据有没有问题,后面会解释出问题的根源之一来自这里。

我们看到,这段log显示,经过一次thermal_zone的更新后,所有cooling device的state仍然是0-->

所有cooling device的cur_state都是0,唯一的一句修改cooling device状态的是

thermal_cdev_update: thermal cooling_device1: set to state 0
 也就是说,风扇没有被打开。但从我们读到的当前温度来看,当前温度是66度,已经超过trip 4,5,6了,无论怎样都应该

开启风扇了。这到底是怎么一回事呢?我们来套用之前分析的cooling device获取状态趋势,和设置下一次cooling device

状态的流程来看看,以trip4来看,tend是1,表示是升温,throttle是1,表示是超过了trip,那么根据当前状态是0,

下一个应该设置的状态就应该是1。那么到底是什么阻止cooling device修改状态呢?原因就在这句:

[ 1823.769704] thermal_zone_trip_update: thermal cooling_device2: old_target=1, target=1

由于old_target和target是一样的值,cooling device不会给更新状态。所以问题根源在于,函数使用了

休眠前的thermal_instance->target作为old_target =1 ,这样就算算出来下一个要设置的状态是1,由于原来的old_target

也是1,代码逻辑就不会再设置cooling device的状态了。wow, 这就是故障的最初根源。

不过再想一下,代码里还有没有其他地方用到了休眠前失效的数据呢?很明显,还有一个值,就是thermal_zone的温度,

由于这个值会被用来算温度变化趋势(但没有达到trip时),所以我们也必须处理一下这个值。


好,知道故障原因了,我们应该怎么改呢,请读者思考一下。




想好没?继续看 Kernel Developer是怎么解决这个问题的。

第一版,加了一段代码,让thermal_instance的temperature和target在suspend的最后阶段失效:

#ifdef PM_SLEEP
static void thermal_zone_device_reset(struct thermal_zone_device *tz);
	
static int thermal_class_suspend(struct device *dev, pm_message_t state)
{
	struct thermal_zone_device *tz = container_of(dev,
	struct thermal_zone_device, device);
	if (tz)
		thermal_zone_device_reset(tz);
	return 0;
}
#else
#define thermal_class_suspend NULL
#endif

static struct class thermal_class = {
	.name = "thermal",
	.dev_release = thermal_release,
	.suspend = thermal_class_suspend,
};
上面的代码是在suspend的时候,把cache值重置。thermal_zone_device_reset的实现如下:

+/* Invalid/uninitialized temperature */
+#define THERMAL_TEMP_INVALID	-27400
+
+static void thermal_zone_device_reset(struct thermal_zone_device *tz)
+{
+	struct thermal_instance *pos;
+
+	tz->temperature = THERMAL_TEMP_INVALID;
+	list_for_each_entry(pos, &tz->thermal_instances, tz_node)
+		pos->target = THERMAL_NO_TARGET;
+}
+
 /**
  * thermal_zone_device_register() - register a new thermal zone device
  * @type:	the thermal zone device type
@@ -1561,6 +1574,8 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
 	/* Bind cooling devices for this zone */
 	bind_tz(tz);
 
+	thermal_zone_device_reset(tz);
+
 	INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check);

这里失效的实质就是,从休眠恢复回来后,我们认为thermal_zone的温度是非常冷的,冷到了绝对零度以下-274,

这样的结果是,计算的温度趋势无论如何都是升温,然后把所有thermal_instance的状态都置THERMAL_NO_TARGET,

表示从休眠回来后,所有的风扇都没有转动。这样改的出发点主要是bug reporter报告说,风扇不转,我们要想方设法

让风扇转起来!

这个补丁打上去后,bu reporter报告说,不管用!根据log发现,回调重置thermal_instance的位置还比较靠前,

在reset被执行后,其他内核流程(driver)又更改了thermal_instance的值。所以我们还需要把重置thermal_instance

的位置延后。于是第二版这么改:

static const struct dev_pm_ops thermal_class_pm_ops = {
	SET_LATE_SYSTEM_SLEEP_PM_OPS(thermal_class_suspend_late, NULL)
};

static struct class thermal_class = {
	.name = "thermal",
	.dev_release = thermal_release,
	.pm = &thermal_class_pm_ops,
};
于是,第二版修改后,经bug reporter验证成功了。
不过正如之前所说的那样,就算是把reset的时机延后,也不能保证没有其他driver在后期更新

thermal_instance的值,导致功亏一篑。所以kernel developer决定想一种可靠的方案来完成。

于是有了第三版,实现的原理是,如果系统已经开始suspend了,那么就置一个禁止更新thermal_instance

的标志,后期无论谁走到更新thermal_instance的流程时发现这个标志就直接返回,然后在resume回来后,

再把这个标志恢复,另外,也顺便对get_trend趋势函数做一点小手脚(虽然对于acpi兼容的thermal_zone来说是画蛇添足,

因为对于acpitz来说,上次温度是-274,本次温度再怎么也比绝对温度高,自然返回上升。但对于其他

thermal_zone,计算趋势的方法这不一定了,所以还是需要加上这么一句):即当

第一次获取趋势时,强行返回温度上升,这样为了安全起见,会启动风扇的运转。

int get_tz_trend(struct thermal_zone_device *tz, int trip)	Link Here 
{
	enum thermal_trend trend;
/*
 * If it is the first time to read the temperature,
 * pretend the temperature is raising for thermal safety reason.
 */
	if (tz->last_temperature == THERMAL_TEMP_INVALID)
		return THERMAL_TREND_RAISING;

void thermal_zone_device_update(struct thermal_zone_device *tz)
{
	int count;
	if (no_thermal_update)
		return;

static int thermal_notify(struct notifier_block *nb,
	unsigned long mode, void *_unused)
{
	struct thermal_zone_device *tz;

	switch (mode) {
		case PM_HIBERNATION_PREPARE:
		case PM_SUSPEND_PREPARE:
			no_thermal_update = true;
		case PM_POST_HIBERNATION:
		case PM_POST_SUSPEND:
		case PM_POST_RESTORE:
			list_for_each_entry(tz, &thermal_tz_list, node)
				thermal_zone_device_reset(tz);
			no_thermal_update = false;
			break;
	}
	return 0;
}

static int __init thermal_init(void)
{
	thermal_pm_nb.notifier_call = thermal_notify;
	register_pm_notifier(&thermal_pm_nb);

打上补丁后,reporter报告说,风扇确实是起来了,不过随着温度变化,风扇速度不再变化。

抓取dynamic debug如下:

[  570.202269] ACPI: Waking up from system sleep state S4
[  570.227742] update_temperature: thermal thermal_zone1: last_temperature N/A, current_temperature=65000
[  570.227746] thermal_zone_trip_update: thermal thermal_zone1: Trip1[type=1,temp=107000]:trend=2,throttle=0
[  570.227750] get_target_state: thermal cooling_device6: cur_state=0
[  570.227751] thermal_zone_trip_update: thermal cooling_device6: old_target=-1, target=-1
[  570.227754] get_target_state: thermal cooling_device5: cur_state=0
[  570.227755] thermal_zone_trip_update: thermal cooling_device5: old_target=-1, target=-1
[  570.227758] thermal_zone_trip_update: thermal thermal_zone1: Trip2[type=0,temp=90000]:trend=1,throttle=0
[  570.227822] get_target_state: thermal cooling_device0: cur_state=0
[  570.227823] thermal_zone_trip_update: thermal cooling_device0: old_target=-1, target=-1
[  570.227825] thermal_zone_trip_update: thermal thermal_zone1: Trip3[type=0,temp=75000]:trend=1,throttle=0
[  570.227883] get_target_state: thermal cooling_device1: cur_state=0
[  570.227884] thermal_zone_trip_update: thermal cooling_device1: old_target=-1, target=-1
[  570.227887] thermal_zone_trip_update: thermal thermal_zone1: Trip4[type=0,temp=55000]:trend=1,throttle=1
[  570.227944] get_target_state: thermal cooling_device2: cur_state=0
[  570.227945] thermal_zone_trip_update: thermal cooling_device2: old_target=-1, target=1
[  570.227947] thermal_cdev_update: thermal cooling_device2: zone1->target=1
[  570.232714] thermal_cdev_update: thermal cooling_device2: set to state 1
[  570.232717] thermal_zone_trip_update: thermal thermal_zone1: Trip5[type=0,temp=45000]:trend=1,throttle=1
[  570.232783] get_target_state: thermal cooling_device3: cur_state=0
[  570.232784] thermal_zone_trip_update: thermal cooling_device3: old_target=-1, target=1
[  570.232786] thermal_cdev_update: thermal cooling_device3: zone1->target=1
[  570.239810] thermal_cdev_update: thermal cooling_device3: set to state 1
[  570.239814] thermal_zone_trip_update: thermal thermal_zone1: Trip6[type=0,temp=30000]:trend=1,throttle=1
[  570.239926] get_target_state: thermal cooling_device4: cur_state=0
[  570.239928] thermal_zone_trip_update: thermal cooling_device4: old_target=-1, target=1
[  570.239931] thermal_cdev_update: thermal cooling_device4: zone1->target=1
[  570.248708] thermal_cdev_update: thermal cooling_device4: set to state 1
从log可以看出,休眠回来后,温度是65度,并且系统也确实把cooling_device2,3,4打开了。

不过问题是,休眠回来后,风扇确实是打开了,但一直以100%的速度狂转。

然后bug reporter做了一些修改后风扇正常了,他把get_trend默认第一次返回升温的趋势,改成了降温趋势:

	if (tz->last_temperature == THERMAL_TEMP_INVALID)
		return THERMAL_TREND_DROPPING;

那到底是应该默认成升温模式,还是降温模式?我们应该仿效原版对普通升降温的判断,

如果当前温度达到了trip,就开启该thermal_instance的upper状态;否则,就关闭该thermal_instance。
这真是一个简单粗暴的方法。我们来对比不加这个patch前的行为,当前温度如果达到trip,
那么如果是升温,则加大一级target(转速),如果是降温,则降低一级转速;
如果没有达到trip,如果是升温,这不管它,如果是降温,这说明本次散热卓有成效,
降低一级转速。对于这个patch来说,由于重置了temperature是-274,如果不加修正target的
这段代码,则趋势一定是升温,就默认了可能是会开启风扇这件事,因此我们还是应该按照实际
温度的情况来设置下一级状态。于是补丁再次修改,成了这个样子:
static unsigned long get_target_state(struct thermal_instance *instance,
				enum thermal_trend trend, bool throttle)
{
			} else
				next_target = instance->lower;
			break;
		case THERMAL_TREND_NONE:
			if (throttle) {
				next_target = instance->upper;
			else
				next_target = THERMAL_NO_TARGET;
			break;
		default:
			break;
		}
修改后的log记录如下:
[  262.858198] Restarting tasks ... done.
[  262.907577] update_temperature: thermal thermal_zone1: last_temperature N/A, current_temperature=60000

[  262.907583] thermal_zone_trip_update: thermal thermal_zone1: Trip1[type=1,temp=107000]:trend=5,throttle=0
[  262.907588] get_target_state: thermal cooling_device6: cur_state=0
[  262.907590] thermal_zone_trip_update: thermal cooling_device6: old_target=-1, target=-1
[  262.907594] get_target_state: thermal cooling_device5: cur_state=0
[  262.907596] thermal_zone_trip_update: thermal cooling_device5: old_target=-1, target=-1

[  262.907601] thermal_zone_trip_update: thermal thermal_zone1: Trip2[type=0,temp=90000]:trend=5,throttle=0
[  262.907632] get_target_state: thermal cooling_device0: cur_state=1
[  262.907635] thermal_zone_trip_update: thermal cooling_device0: old_target=-1, target=-1

[  262.907639] thermal_zone_trip_update: thermal thermal_zone1: Trip3[type=0,temp=75000]:trend=5,throttle=0
[  262.907668] get_target_state: thermal cooling_device1: cur_state=1
[  262.907670] thermal_zone_trip_update: thermal cooling_device1: old_target=-1, target=-1

[  262.907674] thermal_zone_trip_update: thermal thermal_zone1: Trip4[type=0,temp=55000]:trend=5,throttle=1
[  262.907701] get_target_state: thermal cooling_device2: cur_state=1
[  262.907704] thermal_zone_trip_update: thermal cooling_device2: old_target=-1, target=1
[  262.907707] thermal_cdev_update: thermal cooling_device2: zone1->target=1
[  262.907710] thermal_cdev_update: thermal cooling_device2: set to state 1

[  262.907713] thermal_zone_trip_update: thermal thermal_zone1: Trip5[type=0,temp=45000]:trend=5,throttle=1
[  262.907741] get_target_state: thermal cooling_device3: cur_state=1
[  262.907743] thermal_zone_trip_update: thermal cooling_device3: old_target=-1, target=1
[  262.907746] thermal_cdev_update: thermal cooling_device3: zone1->target=1
[  262.907748] thermal_cdev_update: thermal cooling_device3: set to state 1

[  262.907752] thermal_zone_trip_update: thermal thermal_zone1: Trip6[type=0,temp=30000]:trend=5,throttle=1
[  262.907779] get_target_state: thermal cooling_device4: cur_state=1
[  262.907782] thermal_zone_trip_update: thermal cooling_device4: old_target=-1, target=1
[  262.907785] thermal_cdev_update: thermal cooling_device4: zone1->target=1
[  262.907787] thermal_cdev_update: thermal cooling_device4: set to state 1
看出为什么风扇关不了的原因了吗?既然是想让风扇关闭,我们就来观察log里设置target为-1的这几个操作,
为什么没有成功,也就是cooling_devie0和1,他们当前的状态是1,然后想让他们被设置到的状态是-1,但最后
却因为old_target和target都是-1,从而没有去设置。
于是补丁又做了加强,在判断old_target和target时,还加了一个判断,如果当前target是初始值,则算出来的
target是多少,就要让他设置成多少:

static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
{
	...
	if (trend != THERMAL_TREND_UNKNOWN &&
		old_target == instance->target)
		continue;
这样改之后,就算old_target是-1,一旦target是-1,并且是thermal_instance初次走到这里,
那么就强行把target设置成关闭,也就解决了风扇无法关闭的问题。

这就是休眠回来后,风扇out of control解决的全过程。对应的最新patch在:
https://patchwork.kernel.org/patch/6166681/
https://patchwork.kernel.org/patch/6166691/

在解决这个问题的过程中,他们还遇到另外一个问题,就是系统启动后,就算温度很低,风扇也一直转,
thermal zone无法自动将其关闭。这个问题,其实在刚才我们排查休眠风扇问题的最后一个log里,
已经表现出来了。还记得我们最早提出休眠后风扇问题,说的是:休眠恢复后,即使温度很高风扇不转动;
而在最后一个log里,我们看到了cooling device0到4,状态全部是1,一直转。
这其实是在我们解决休眠风扇问题的过程中,内核里引入了一个regression导致的。这个regression其实
也不是严格意义上的regression,他是由于把fan driver从acpi driver改成了跟通用的platform driver后,
fan device也成了platform device,从而被power domain接管。而这个power domain,无论是在
系统启动,还是休眠恢复时,都会显示的开启他下属的platform device的电源,以便后期对这些device
进行操作,所以风扇也就顺带被打开了。但这就有一个问题,风扇在启动阶段被打开后,如果温度很低,
达不到trip,就不会触发中断来调整风扇转速,因此在没有负载时风扇将长期转动,就算温度很低。
说到这里,其实我有一丝遗憾,因为这个故障我当时也发现了的,只是投入到其他问题去没有深究。
这个故障的解决方案就是,如果cooling device在thermal_zone的后面注册,也尝试对cooling device
所在的thermal_zone的所有thermal_instance进行更新,从而根据thermal_instance的情况对cooling device
风扇转速进行修正。对应的patch在
https://patchwork.kernel.org/patch/6166701/


在打上两个patch后,bug reporter说是没问题了。不过在另外一个类似的风扇故障里
https://bugzilla.kernel.org/show_bug.cgi?id=91411
bug reporter报告说,还是不顶用!他打了上面两个patch后,风扇从S3回来后,还是
一直100%的转,没有办法降低转速!于是让他又提供了log信息,为了说明问题的根源,
我故意对log做了处理,将一些关键log多换一行:
[ 5200.909462] Restarting tasks ... 
[ 5200.968630] update_temperature: thermal thermal_zone0: last_temperature N/A, current_temperature=25000

[ 5200.968641] thermal_zone_trip_update: thermal thermal_zone0: Trip1[type=0,temp=105000]:trend=5,throttle=0
[ 5200.971833] get_target_state: thermal cooling_device7: cur_state=1
[ 5200.971842] thermal_zone_trip_update: thermal cooling_device7: old_target=-1, target=-1

[ 5200.972860] update_temperature: thermal thermal_zone0: last_temperature=25000, current_temperature=25000
[ 5200.972870] thermal_zone_trip_update: thermal thermal_zone0: Trip1[type=0,temp=105000]:trend=0,throttle=0
[ 5200.972896] get_target_state: thermal cooling_device7: cur_state=1
[ 5200.972902] thermal_zone_trip_update: thermal cooling_device7: old_target=-1, target=-1
[ 5200.972908] thermal_cdev_update: thermal cooling_device7: zone0->target=18446744073709551615
[ 5200.972938] thermal_cdev_update: thermal cooling_device7: set to state 0
[ 5200.972946] thermal_zone_trip_update: thermal thermal_zone0: Trip2[type=0,temp=70000]:trend=0,throttle=0
[ 5200.973020] get_target_state: thermal cooling_device5: cur_state=1
[ 5200.973025] thermal_zone_trip_update: thermal cooling_device5: old_target=-1, target=-1
[ 5200.973033] thermal_zone_trip_update: thermal thermal_zone0: Trip3[type=0,temp=60000]:trend=0,throttle=0
[ 5200.973090] get_target_state: thermal cooling_device6: cur_state=1
[ 5200.973096] thermal_zone_trip_update: thermal cooling_device6: old_target=-1, target=-1
[ 5200.973118] thermal_zone_trip_update: thermal thermal_zone0: Trip2[type=0,temp=70000]:trend=0,throttle=0
[ 5200.973175] get_target_state: thermal cooling_device5: cur_state=1
[ 5200.973181] thermal_zone_trip_update: thermal cooling_device5: old_target=-1, target=-1
[ 5200.973187] thermal_zone_trip_update: thermal thermal_zone0: Trip3[type=0,temp=60000]:trend=0,throttle=0
[ 5200.973241] get_target_state: thermal cooling_device6: cur_state=1
[ 5200.973247] thermal_zone_trip_update: thermal cooling_device6: old_target=-1, target=-1
[ 5200.975380] lpc_ich 0000:00:1f.0: BAR 13: [io  0x1000-0x107f] has bogus alignment
[ 5200.978035] pci 0000:00:1e.0: PCI bridge to [bus 02]

首先,在这个bug系统中,cooling_device0到7是风扇,既然报告说风扇一直转,不受控制,那么我们就看上面
log里,哪些cooling_device的cur_state一直都是1。经过检查,发现cooling_device5在温度更新后,保持不变为1,
就算target要设置成-1也没成功。
我们发现cooling_device5不合理的一处数据:
[ 5200.972946] thermal_zone_trip_update: thermal thermal_zone0: Trip2[type=0,temp=70000]:trend=0,throttle=0
[ 5200.973020] get_target_state: thermal cooling_device5: cur_state=1
[ 5200.973025] thermal_zone_trip_update: thermal cooling_device5: old_target=-1, target=-1

当前状态是1,是因为从休眠恢复后,power domain会绕过thermal模块,将下属的
platform device使能(也就是风扇被强行打开了),于是根据计算,需要把target设置成-1,
但由于old_target已经是-1了,所以不会修改状态。还记得之前我们补丁的最后一次改动,
专门为了解决这种情况,会在old_target和target检查之前,加入一句判断,如果是第一次设置thermal_instance,
这强行设置为target的值吗?这里我们的cooling_device5也是第一次进去设置thermal_instance的状态,
为什么没有强行设置target为-1,而是返回呢?我们看cooling_device5本次的温度趋势,是0,表示的
是STABLE,而不是我们引入的5,即THERMAL_TREND_UNKNOW,这个值表示thermal_instance是第一次
进入设置这个流程。那为什么cooling_device5的trend不是5而是0呢?我们来看log最早的几句记录,
照例说,更新每个thermal_zone的时候,需要把每个trip都计算完毕,才进入下一个thermal_zone的更新。
每次打印一句update temperature,就表示进入了一次thermal_zone_device_update总入口。
但实际的情况是什么呢,在更新thermal_zone0的trip1之后,突然又开始从头开始更新trip1,trip2,trip3,
也就是说,在执行thermal_zone_device_update的过程中,又被其他流程的thermal_zone_device_update打断了,
而目前补丁的逻辑是,thermal_zone如果是第一次进入thermal_instance的更新(通过判断thermal_zone全局的
last_temperature是否为-274),则设置升温趋势为unknow,即5,而last_temperature的更新是在thermal_zone_device_update
入口,所以,但thermal_zone0在更新各个trip 的过程中,如果被其他的流程竞争,被重入thermal_zone_device_update
后,则thermal_zone0的last_temperature就被设置为了正常的温度,而不是-274,于是再次更新thermal_zone0的各个
trip时,就不会再返回trend趋势为未知(5)了,导致如果target=-1且old_target也为-1时,就不会强行设置target为-1而是
直接返回了。这就造成cooling_device5不能被设置为关闭,导致我们看到的问题。 问题的根源在于,
thermal_zone_device_update整体不是原子的,按照bugzilla里排查该问题时kernel developer的原话:
1. thermal core updates all thermal zones after resume
2. ACPI thermal driver also schedules a thermal_zone_device_update() work during resume, which is invoked while thermal core is updating the thermal zones.
3. the second thermal zone update pollutes the data used by the first one, and causes some problem.

到目前为止,只解决thermal_zone_device_update的重入问题的话,就不应该以thermal_zone的last_temperature作为标准,
而是应该以thermal_instance为单位,如果是第一次设置thermal_instance,则应该不管old_target与target是否一致,都
设置为target。于是我们就废弃之前通过get_trend返回UNKNOW的方法,而是直接在get_target_state获取thermal_instance的
下一个target后,如果是第一次设置thermal_instance的状态,就不管三七二十一,强行设置他的状态为target。
这就是最终补丁https://patchwork.kernel.org/patch/6166681/的来历。其实就是把判断的粒度减小到了thermal_instance。

不过,对于thermal_zone_device_update的互斥来说,问题并没有完全解决,
acpi_thermal_resume会启动一个work放到workqueue里来并发的更新thermal_zone的温度,这个竞争导致了
问题。我们当然可以删除acpi driver的这个work来规避这种情况,但是你是删除不完的!因为有很多驱动
可能会显示的调用thermal_zone_device_update。而且,就算不是在resume过程中,这个thermal_zone_device_update
函数的互斥和同步是有问题的!比较好的方法是找到让thermal_zone_device_update原子执行的方案。




你可能感兴趣的:([知其然不知其所以然-5] 为什么我的风扇温度很低却转不停/或者温度很高却根本不转?)