band of brothers:
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);
在更新完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这个状态。
这个函数会挨个检查每个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_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); }
应该被设置到的状态,也就是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; }
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; }
对于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就是了。
我们来设置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,找出他下属的所有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_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验证成功了。
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;
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的这几个操作,
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初次走到这里,
[ 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]
[ 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. 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.