首页 » 技术分享 » cpu 调频 温度 ondemand

cpu 调频 温度 ondemand

 

三星 exynos 

5410硬件结构体

下图是5410内部TMU(Thermal Management Unit)单元,图中TermalSensor是温度传感器,负责检测CPU的温度,该温度会和设定的温度报警门限进行比较,内核设计者设置的触发门限被存储在对应的ThresholdRise LevelXX0-4),当温度发生从一个门限跨到另一个门限时会触发中断,温度上升和下降都将产生中断,产生中断后会执行中断服务函数。

一个非常重要的结构体

arch/arm/mach-exynos/board-tf4-power.c

这个结构体非常重要,温度和频率策略就在这里确定了,后面会无数次提到该结构体。该结构体定义于platform 设备侧。

1162 static struct exynos_tmu_platform_data exynos5_tmu_data = { 
1163     .trigger_levels[0] = 70, 
1164     .trigger_levels[1] = 80, 
1165     .trigger_levels[2] = 90, 
1166     .trigger_levels[3] = 100, 
1167     .trigger_level0_en = 1, 
1168     .trigger_level1_en = 1, 
1169     .trigger_level2_en = 1, 
1170     .trigger_level3_en = 1, 
1171     .gain = 5, 
1172     .reference_voltage = 16, 
1173     .noise_cancel_mode = 4, 
1174     .cal_type = TYPE_ONE_POINT_TRIMMING, 
1175     .efuse_value = 55, 
1176     .freq_tab[0] = { 
1177         .freq_clip_max = 1600 * 1000, 
1178         .temp_level = 70, 
1179     }, 
1180     .freq_tab[1] = { 
1181         .freq_clip_max = 1200 * 1000, 
1182         .temp_level = 80, 
1183     }, 
1184     .freq_tab[2] = { 
1185         .freq_clip_max = 1000 * 1000, 
1186         .temp_level = 90, 
1187     }, 
1188     .freq_tab[3] = { 
1189         .freq_clip_max = 900 * 1000, 
1190         .temp_level = 100, 
1191     }, 
1192     .freq_tab[4] = { 
1193         .freq_clip_max = 400 * 1000, 
1194         .temp_level = 115, 
1195     }, 
1196     .size[THERMAL_TRIP_ACTIVE] = 1, 
1197     .size[THERMAL_TRIP_PASSIVE] = 3, 
1198     .size[THERMAL_TRIP_HOT] = 1, 
1199     .freq_tab_count = 5, 
1200     .type = SOC_ARCH_EXYNOS5, 
1201 }; 

该结构体的相关字段非常重要的,trigger_levels[4]是一个具有四个整数的数组,该数组对应的物理器件是ThresholdRise Level XThresholdfail Level X

trigger_level0_en表明是否起用对应的trigger_levels,等于1表明启用。

gain是温度产生模块的放大倍数。

reference_voltage是温度产生模块的参考电压。

noise_cancel_mode:噪声消除模式。

typeSOC的类型

efuse_value平台定义的保险丝值,熔断值。

cal_type温度校准的方法

freq_tab表示频率值

TMU驱动的注册

要理解5410平台的TMU机制,得找一个切入口,这个切入口从TMU驱动的注册流程开始。

drivers/thermal/exynos_thermal.c文件

1542 static struct platform_driver exynos_tmu_driver = { 
1543     .driver = { 
1544         .name   = "exynos-tmu", 
1545         .owner  = THIS_MODULE, 
1546         .of_match_table = exynos_tmu_match, 
1547     }, 
1548     .probe = exynos_tmu_probe, 
1549     .remove = __devexit_p(exynos_tmu_remove), 
1550     .suspend = exynos_tmu_suspend, 
1551     .resume = exynos_tmu_resume, 
1552     .id_table = exynos_tmu_driver_ids, 
1553 }; 
1554 
1555 module_platform_driver(exynos_tmu_driver); 

上面的代码对于用于注册一个platform驱动,由于linux内核驱动程序会挂载到相应的总线上,bus的名字有很多种,如pciusbplatform,这里注册的驱动会在/sys/bus/platform/drivers目录下有其信息。

linux下驱动,有platformdriver就要有platformdeviceplatformdevice后面再看,这里先看driverprobe过程。

1544指定了drivername,该name将会在/sys/bus/platform/drivers/目录下显示。.probe=exynos_tmu_probe用于注册驱动。从module_platform_driverprobe的调用,会经过如下的函数;driver_register->bus_add_driver->driver_attach->bus_for_each_dev->__driver_attach->driver_probe_device->platform_drv_probe;上面的过程一个重要的功能是找注册的platform_device,该设备将会作为参数传递给exynos_tmu_probe函数

exynos_tmu_probe函数有点长,这里将其按照功能进行分段讲解。

1315行用于获取开篇介绍的staticstruct exynos_tmu_platform_dataexynos5_tmu_data结构体。该行非常重要,调频策略就是依靠该结构体里的信息的。

1333 INIT_WORK(&data->irq_work, exynos_tmu_work);

1333初始化一个工作队列,回调函数是exynos_tmu_work。该workqueue会被源于TMU送过来的温度中断回调。该workqueue的调用对应与一次温度调节的开始。

1335     for (i = 0; i < EXYNOS_TMU_COUNT; i++) { 
1336         data->irq[i] = platform_get_irq(pdev, i); 
1337         if (data->irq[i] < 0) { 
1338             ret = data->irq[i]; 
1339             dev_err(&pdev->dev, "Failed to get platform irq\n"); 
1340             goto err_free; 
1341         } 
1342 
1343         data->mem[i] = platform_get_resource(pdev, IORESOURCE_MEM, i); 
1344         if (!data->mem[i]) { 
1345             ret = -ENOENT; 
1346             dev_err(&pdev->dev, "Failed to get platform resource\n"); 
1347             goto err_free; 
1348         } 
1349 
1350         data->mem[i] = request_mem_region(data->mem[i]->start, 
1351                 resource_size(data->mem[i]), pdev->name); 
1352         if (!data->mem[i]) { 
1353             ret = -ENODEV; 
1354             dev_err(&pdev->dev, "Failed to request memory region\n"); 
1355             goto err_free; 
1356         } 
1357 
1358         data->base[i] = ioremap(data->mem[i]->start, resource_size(data->mem[i])); 
1359         if (!data->base[i]) { 
1360             ret = -ENODEV; 
1361             dev_err(&pdev->dev, "Failed to ioremap memory\n"); 
1362             goto err_mem_region; 
1363         } 
1364 
1365         ret = request_irq(data->irq[i], exynos_tmu_irq, 
1366                 IRQF_TRIGGER_RISING, "exynos-tmu", data); 
1367         if (ret) { 
1368             dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq[i]); 
1369             goto err_io_remap; 
1370         } 
1371     } 

13351371行,该文件的开始定义EXYNOS_TMU_COUNT等于4,对应4TMUThermalManagementUnit),这四个TMU分别用于监控CPU的温度。获取其对应的中断号/内存资源,内存范围/io地址空间,并根据获得的中断号,注册中断服务函数exynos_tmu_irq。该中断函数会调用1333行初始workqueue

//获取对应的时钟资源,该资源定义于clock-exynos5410.c文件。

1373     data->clk = clk_get(NULL, "tmu_apbif"); 
1374     if (IS_ERR(data->clk)) { 
1375         ret = PTR_ERR(data->clk); 
1376         dev_err(&pdev->dev, "Failed to get clock\n"); 
1377         goto err_irq; 
1378     } 
1379 
1380     if (pdata->type == SOC_ARCH_EXYNOS5 ||   
1381                 pdata->type == SOC_ARCH_EXYNOS4) 
1382         data->soc = pdata->type; 
1383     else { 
1384         ret = -EINVAL; 
1385         dev_err(&pdev->dev, "Platform not supported\n"); 
1386         goto err_clk; 
1387     } 

1380行的 SOC_ARCH_EXYNOS5在platform设备里确实注册了。

1389     data->pdata = pdata; 
1390     platform_set_drvdata(pdev, data); 

1389~1390行这里将platform设备传递来的信息存储在platform_device里。

1393     for (i = 0; i < EXYNOS_TMU_COUNT; i++) { 
1394         ret = exynos_tmu_initialize(pdev, i); 
1395         if (ret) { 
1396             dev_err(&pdev->dev, "Failed to initialize TMU[%d]\n", i); 
1397             goto err_clk; 
1398         } 
1399 
1400         exynos_tmu_control(pdev, i, true); 
1401     } 

1393~1401行设置TMU相关的寄存器,这两个函数和平台硬件是息息相关的。这里只要知道设置完了以后TMU就可以根据platformdevice里要求的温度,CPU当前的温度正确产生中断了。

1403 /*Register the sensor with thermal management interface*/

1404 (&exynos_sensor_conf)->private_data = data;

1405 exynos_sensor_conf.trip_data.trip_count =pdata->trigger_level0_en +

1406 pdata->trigger_level1_en + pdata->trigger_level2_en+

1407 pdata->trigger_level3_en;

1408

1405~1407行,根据平台设备传递的信息初始化conf.trip_data.trip_count,根据开篇给出的结构体,其值将被初始化为4

1409     for (i = 0; i < exynos_sensor_conf.trip_data.trip_count; i++) { 
1410         exynos_sensor_conf.trip_data.trip_val[i] = 
1411             pdata->threshold + pdata->trigger_levels[i]; 
1412         exynos_sensor_conf.trip_data.boost_trip_val[i] = 
1413             pdata->threshold + pdata->boost_trigger_levels[i]; 
1414     } 

14091414行仍然是根据开篇给出的结构体,初始化trip_val

1415 
1416     exynos_sensor_conf.cooling_data.freq_clip_count = 
1417                         pdata->freq_tab_count; 
1418     exynos_sensor_conf.cooling_data.boost_mode = 0; 
1419 
1420     for (i = 0; i < pdata->freq_tab_count; i++) { 
1421         exynos_sensor_conf.cooling_data.freq_data[i].freq_clip_max = 
1422                     pdata->freq_tab[i].freq_clip_max; 
1423         exynos_sensor_conf.cooling_data.freq_data[i].temp_level = 
1424                     pdata->freq_tab[i].temp_level; 
1425         exynos_sensor_conf.cooling_data.freq_data[i].mask_val = cpu_all_mask; 
1426 
1427         exynos_sensor_conf.cooling_data.boost_freq_data[i].freq_clip_max = 
1428                     pdata->boost_freq_tab[i].freq_clip_max; 
1429         exynos_sensor_conf.cooling_data.boost_freq_data[i].temp_level = 
1430                     pdata->boost_freq_tab[i].temp_level; 
1431         exynos_sensor_conf.cooling_data.boost_freq_data[i].mask_val = cpu_all_mask; 
1432 
1433         exynos_sensor_conf.cooling_data.size[i] = 
1434                     pdata->size[i]; 
1435     } 
1436 
1437 #ifdef THERMAL_WINDOWS 
1438     exynos_sensor_conf.cooling_data.WinAttr[0] = WIN_NORMAL; // For active mode (105) 
1439     exynos_sensor_conf.cooling_data.WinAttr[1] = WIN_WARN; // For passive - i mode (110) 
1440     exynos_sensor_conf.cooling_data.WinAttr[2] = WIN_WARN;   // For passive - ii mode (112) 
1441     exynos_sensor_conf.cooling_data.WinAttr[3] = WIN_CRIT;   // For passive - iii mode (114) 
1442     exynos_sensor_conf.cooling_data.WinAttr[4] = WIN_CRIT;   // For hot mode (115) 
1443 #endif // endif THERMAL_WINDOWS 
1444 

1403~1443,将exynos5_tmu_data结构体里的内容始化到TMI里,exynos_sensor_confthermal_cooling_conf结构体成员里存放了exynos5_tmu_data结构体的大部分信息。这个结构体还是很重要的,重要性提现在后文的1448行。其各个字段的初始化可以看成对开篇给出的那个结构体的拷贝。

14371443WinAttr[0]是针对105度时的调节策略,即105度使用四个核中最小核作为判断的依据(WIN_NORMAL),WIN_WARN指平均温度,WIN_CRIT指最大温度。

1448     ret = exynos_register_thermal(&exynos_sensor_conf); 
1449     if (ret) { 
1450         dev_err(&pdev->dev, "Failed to register thermal interface\n"); 
1451         goto err_clk; 
1452     } 

1448行函数非常重要,注册内核thermal管理。

1454     th_zone->exynos4_dev = pdev; 
1455 
1456     ret = sysfs_create_group(&pdev->dev.kobj, &exynos_thermal_sensor_attr_group);
1457     if (ret) 
1458         dev_err(&pdev->dev, "cannot create thermal sensor attributes\n"); 
1459 
...
1481 
1482     return ret; 
1483 } 

1454~1483exynos_tmu_probe一些扫尾和出错处理,直接跳过了。


接着
exynos_register_thermal函数继续。

该函数依然在exynos_thermal.c文件。

 629 /* Register with the in-kernel thermal management */ 
 630 static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) 
 631 { 
 632     int ret, count, tab_size, pos = 0; 
 633     struct freq_clip_table *tab_ptr, *clip_data; 

 640     th_zone = kzalloc(sizeof(struct exynos_thermal_zone), GFP_KERNEL); 
 641     if (!th_zone) 
 642         return -ENOMEM; 

640exynos_thermal_zone结构体在这里申请了。后文会有一张数据结构拓扑图。

644 th_zone->sensor_conf = sensor_conf;

644行,将th_zonesensor_conf成员和传递进来的成员相关联。

646 tab_ptr = (struct freq_clip_table*)sensor_conf->cooling_data.freq_data;

 649     for (count = 0; count < EXYNOS_ZONE_COUNT; count++) { 
 650         tab_size = sensor_conf->cooling_data.size[count]; 
 651         if (tab_size == 0) 
 652             continue; 
 653 
 654         clip_data = (struct freq_clip_table *)&(tab_ptr[pos]); 
 655 
 656 #ifdef CONFIG_CPU_THERMAL 
 657         th_zone->cool_dev[count] = cpufreq_cooling_register( 
 658                         clip_data, tab_size); 
 659 #endif 
 660         pos += tab_size; 
 661 
 662         if (IS_ERR(th_zone->cool_dev[count])) { 
 663             pr_err("Failed to register cpufreq cooling device\n"); 
 664             ret = -EINVAL; 
 665             th_zone->cool_dev_size = count; 
 666             goto err_unregister; 
 667         } 
 668     } 

649行的循环会进行三次,ZONE被分成三个,分别是ACTIVEPASSIVEHOT,这三者的区别如下:

如果初始主频是1.6G,在ACTIVE模式下初始主频1.6GHz是不变,调频只受ondemand或者其它调频策略影响,这种状态下温度可以说是比较良好的。

PASSIVE模式下,温度上升已经不能忽视了,需要进行限制了,限制的方法就是将最高主频降低,由1.6G向下降,至于降多少是由一开始的数据结构决定的,根据一开始的那个数据结构可知,在该模式下系统最高主频只能到1.2GHz,不论何种负载这是最高的主频了。

对于HOT,这时温度已经比较高了,需要进一步限制,在向下上将是CRITICAL,如果幸运没有宕机,这时5410会永久将主频永久限制在某一频率上,默认是800MHz

回到650行,根据一开始的1196~1198行,可以知道这三。次的迭代tab_size的值将是1,3,1。这正好对应了五个freq_tab表。

1196     .size[THERMAL_TRIP_ACTIVE] = 1, 
1197     .size[THERMAL_TRIP_PASSIVE] = 3, 
1198     .size[THERMAL_TRIP_HOT] = 1, 

645660行,针对platform设备里的几张来注册cooling设备。660行将freq_tab[0]freq_tab[1]freq_tab[3]freq_tab[4]分三次注册。

1176     .freq_tab[0] = { 
1177         .freq_clip_max = 1600 * 1000, 
1178         .temp_level = 70, 
1179     }, 
1180     .freq_tab[1] = { 
1181         .freq_clip_max = 1200 * 1000, 
1182         .temp_level = 80, 
1183     }, 
1184     .freq_tab[2] = { 
1185         .freq_clip_max = 1000 * 1000, 
1186         .temp_level = 90, 
1187     }, 
1188     .freq_tab[3] = { 
1189         .freq_clip_max = 900 * 1000, 
1190         .temp_level = 100, 
1191     }, 
1192     .freq_tab[4] = { 
1193         .freq_clip_max = 400 * 1000, 
1194         .temp_level = 115, 
1195     }, 

657行注册cpufreq_cooling_register是比较重要的,前面反复提到的降频,就和这里的有关系。这个函数后面再看,先把这个函数看完。

 671     th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name, 
 672             EXYNOS_ZONE_COUNT, 7, NULL, &exynos_dev_ops, 1, 1, PASSIVE_INTERVAL, 
 673             IDLE_INTERVAL);

671行注册EXYNOS_ZONE_COUNTthermalzone,其第一个name参数是‘exynos-therm’,参数7是一个bitmask,指示可写的trip温度点。

后面的两个1passive模式计算温度的系数。PASSIVE_INTERVALpassive模式调节频率的两次轮训的间隔,默认是100毫秒。IDLE_INTERVAL指示两次轮询温度设置点是否被跨越的时间间隔,单位也是毫秒,如果等于0,则表示使用中断工作方式。

这些是用于更新如下的一些字段的。

/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq
/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq

再回到cpufreq_cooling_register这一比较重要的函数。

386 struct thermal_cooling_device *cpufreq_cooling_register( 
387     struct freq_clip_table *tab_ptr, unsigned int tab_size) 
388 { 
389     struct thermal_cooling_device *cool_dev; 
390     struct cpufreq_cooling_device *cpufreq_dev = NULL; 
391     struct freq_clip_table *clip_tab; 
392     unsigned int cpufreq_dev_count = 0; 
393     char dev_name[THERMAL_NAME_LENGTH]; 
394     int ret = 0, id = 0, i; 

399     list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) 
400         cpufreq_dev_count++; 
401 
402     cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device), 
403             GFP_KERNEL); 
404     if (!cpufreq_dev) 
405         return ERR_PTR(-ENOMEM); 
406 
...
424 
425     cpufreq_dev->tab_ptr = tab_ptr; 
426     cpufreq_dev->tab_size = tab_size; 
427 
428     ret = get_idr(&cpufreq_idr, &cpufreq_dev->id); 
429     if (ret) { 
430         kfree(cpufreq_dev); 
431         return ERR_PTR(-EINVAL); 
432     } 
433 
434     sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id); 

386~434行,这里省去了一些参数的合法性检查。这个函数总共被调用三次,每个zone会调用该函数注册该zone里的cooling设备。所以400行对应这三次的调用的值将分别是0,1,2.

428行,434行,为设备选择名称,由于这之前没有注册过,所以三个设备名将一次是thermal-cpufreq-0thermal-cpufreq-1thermal-cpufreq-2cpufreq_dev在三次调用时的情况如下:

cpufreq_dev->tab_ptr = tab_ptr;//指向1176
cpufreq_dev->tab_size = 1;
1176     .freq_tab[0] = { 
1177         .freq_clip_max = 1600 * 1000, 
1178         .temp_level = 70, 
1179     }, 
cpufreq_dev->tab_ptr = tab_ptr;//指向1180
cpufreq_dev->tab_size = 3;
1180     .freq_tab[1] = { 
1181         .freq_clip_max = 1200 * 1000, 
1182         .temp_level = 80, 
1183     }, 
1184     .freq_tab[2] = { 
1185         .freq_clip_max = 1000 * 1000, 
1186         .temp_level = 90, 
1187     }, 
1188     .freq_tab[3] = { 
1189         .freq_clip_max = 900 * 1000, 
1190         .temp_level = 100, 
1191     }, 
cpufreq_dev->tab_ptr = tab_ptr;//指向1192行
cpufreq_dev->tab_size = 1;
1192     .freq_tab[4] = { 
1193         .freq_clip_max = 400 * 1000, 
1194         .temp_level = 115, 
1195     }, 

第二个参数是cooling方法,该方法也定义在cpu_cooling.c文件中。

进入cooling 策略

static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
	.get_max_state = cpufreq_get_max_state,
	.get_cur_state = cpufreq_get_cur_state,
	.set_cur_state = cpufreq_set_cur_state,
}; 

443     cpufreq_dev->id = id; 
444     cpufreq_dev->cool_dev = cool_dev; 
445     mutex_lock(&cooling_cpufreq_lock); 
446     list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list); 
447 
448     /*Register the notifier for first cpufreq cooling device*/ 
449     if (cpufreq_dev_count == 0) 
450         cpufreq_register_notifier(&thermal_cpufreq_notifier_block, 
451                         CPUFREQ_POLICY_NOTIFIER); 

446行将设备添加到cooling_cpufreq_list链表,这里才提现400++的意义。

450行为第一个cpufreqcooling设备注册一个notifier,该函数是ACTIVE模式下的notifer

thermal_cooling_device_register定义于drivers/thermal/thermal_sys.c。通过该文件名大概可以猜测到该函数的意义。

/sys/class/thermal/cooling_device0 
/sys/class/thermal/cooling_device1 
/sys/class/thermal/cooling_device2 

/sys/class/thermal/cooling_device0/cur_state 
/sys/class/thermal/cooling_device0/max_state 
/sys/class/thermal/cooling_device1/cur_state 
/sys/class/thermal/cooling_device1/max_state 
/sys/class/thermal/cooling_device2/cur_state 
/sys/class/thermal/cooling_device2/max_state 

thermal_zone_device_register,需要注意的是该函数1422行注册了一个workqueue,该workqueue会被周期性调用以更新thermalzone里的设备。

1422     INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check); 
1423 
1424     thermal_zone_device_update(tz); 

thermal_zone_device_update会被周期性调用。

至此注册的流程完成了。

exynos_tmu_probe里注册一个exynos_tmu_irq中断服务函数,并且也初始化了一个workqueue,即exynos_tmu_work

1092 static void exynos_tmu_work(struct work_struct *work) 
1093 { 
1094     struct exynos_tmu_data *data = container_of(work, 
1095             struct exynos_tmu_data, irq_work); 
1096 
1097     int i; 
1098 
1099     mutex_lock(&data->lock); 
1100     clk_enable(data->clk); 
1101 
1102 
1103     if (data->soc == SOC_ARCH_EXYNOS5) { 
1104         for (i = 0; i < EXYNOS_TMU_COUNT; i++) { 
1105             writel(EXYNOS5_TMU_CLEAR_RISE_INT|EXYNOS5_TMU_CLEAR_FALL_INT, 
1106                     data->base[i] + EXYNOS_TMU_REG_INTCLEAR); 
1107         } 
1108     } else { 
1109         writel(EXYNOS4_TMU_INTCLEAR_VAL, 
1110                 data->base + EXYNOS_TMU_REG_INTCLEAR); 
1111     } 
1112 
1113     clk_disable(data->clk); 
1114     mutex_unlock(&data->lock); 
1115     exynos_report_trigger(); 
1116     for (i = 0; i < EXYNOS_TMU_COUNT; i++) 
1117         enable_irq(data->irq[i]); 
1118 } 

1103~1110行关中断

115行调用exynos_report_trigger处理。

 246 static void exynos_report_trigger(void) 
 247 { 
 248     unsigned int i; 
 249     char data[10]; 
 250     char *envp[] = { data, NULL }; 
 251 
 252     if (!th_zone || !th_zone->therm_dev) 
 253         return; 
 254 
 255     thermal_zone_device_update(th_zone->therm_dev); 
 256 
 257     mutex_lock(&th_zone->therm_dev->lock); 
 258     /* Find the level for which trip happened */ 
 259     for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) { 
 260         if (th_zone->therm_dev->last_temperature < 
 261             th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS) 
 262             break; 
 263     } 
 264 
 265     pr_debug("[TMU-IRQ] IRQ mode=%d\n",i); 
 266     if (th_zone->mode == THERMAL_DEVICE_ENABLED) { 
 267         if (i > 0) 
 268             th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL; 
 269         else 
 270             th_zone->therm_dev->polling_delay = IDLE_INTERVAL; 
 271     } 
 272 
 273     snprintf(data, sizeof(data), "%u", i); 
 274     kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp); 
 275     mutex_unlock(&th_zone->therm_dev->lock); 
 276 } 

255thermal_zone_device_update是一个关键的函数,就算irq没有发生,该函数也将由thermal_zone_device_check周期性调度。274行使用uevent方法向用户空间报告此事。

thermal_zone_device_update函数中将会遇到若干函数指针,这里先将这些函数指针列出来。

定义于drivers/thermal/exynos_thermal.c里的指针。

/* Operation callback functions for thermal zone */ 
static struct thermal_zone_device_ops const exynos_dev_ops = { 
    .bind = exynos_bind, 
    .unbind = exynos_unbind, 
    .get_temp = exynos_get_temp, 
    .get_mode = exynos_get_mode, 
    .set_mode = exynos_set_mode, 
    .get_trip_type = exynos_get_trip_type, 
    .get_trip_temp = exynos_get_trip_temp, 
    .set_trip_temp = exynos_set_trip_temp, 
    .get_trip_temp_level = exynos_get_trip_temp_level, 
    .set_trip_temp_level = exynos_set_trip_temp_level, 
    .get_trip_freq = exynos_get_trip_freq, 
    .set_trip_freq = exynos_set_trip_freq, 
    .get_boost_mode = exynos_get_boost_mode, 
    .set_boost_mode = exynos_set_boost_mode, 
    .get_crit_temp = exynos_get_crit_temp, 
    .notify = exynos_notify, 
};

定义于drivers/thermal/cpu_cooling.c里的指针。

/*Bind cpufreq callbacks to thermal cooling device ops*/ 
static struct thermal_cooling_device_ops const cpufreq_cooling_ops = { 
    .get_max_state = cpufreq_get_max_state, 
    .get_cur_state = cpufreq_get_cur_state, 
    .set_cur_state = cpufreq_set_cur_state, 
}; 

定义与drivers/thermal/thermal_sys.c,该函数还是属于thermal框架的,所以针对平台的一些操作就使用传递进来的函数指针进行操作。这样跟换平台时,只需要替换前面的函数指针集,而不需要修改框架。

1124 void thermal_zone_device_update(struct thermal_zone_device *tz) 
1125 { 
1126     int count, ret = 0; 
1127     long temp, trip_temp; 
1128     enum thermal_trip_type trip_type; 
1129     struct thermal_cooling_device_instance *instance; 
1130     struct thermal_cooling_device *cdev; 
1131     mutex_lock(&tz->lock); 
1132     
1133     if (tz->ops->get_temp(tz, &temp)) { 
1134         /* get_temp failed - retry it later */ 
1135         pr_warn("failed to read out thermal zone %d\n", tz->id); 
1136         goto leave; 
1137     } 

1133行用于读取当前的温度,对应使用的函数是上面的函数指针指向的函数exynos_get_temp,该函数返回的温度和前面设置的温度窗口是有关系的,根据zone类型返回最低,平均以及最高三种温度。

1139     for (count = 0; count < tz->trips; count++) { 
1140         tz->ops->get_trip_type(tz, count, &trip_type); 
1141         tz->ops->get_trip_temp(tz, count, &trip_temp); 
1142         
1143         switch (trip_type) { 
1144         case THERMAL_TRIP_CRITICAL: 
1145             if (temp >= trip_temp) { 
1146                 pr_info("[TMU] CRITICAL: Need tripping\n"); 
1147                 if (tz->ops->notify) 
1148                     ret = tz->ops->notify(tz, count, 
1149                                   trip_type); 
1150                 if (ret) {        
1151                     pr_emerg("Critical temperature reached (%ld C), shutting down\n", 
1152                          temp/1000); 
1153                     orderly_poweroff(true); 
1154                 } 
1155             } 
1156             break; 
1157         case THERMAL_TRIP_HOT: 
1158             if (temp >= trip_temp) 
1159                 if (tz->ops->notify) 
1160                     tz->ops->notify(tz, count, trip_type); 
1161             break; 
1162         case THERMAL_TRIP_ACTIVE: 
1163             list_for_each_entry(instance, &tz->cooling_devices, 
1164                         node) { 
1165                 if (instance->trip != count) 
1166                     continue; 
1167                     
1168                 cdev = instance->cdev; 
1169                 
1170                 if (temp >= trip_temp) 
1171                     cdev->ops->set_cur_state(cdev, 1); 
1172                 else 
1173                     cdev->ops->set_cur_state(cdev, 0); 
1174 #ifdef PERCORE_LOGGING 
1175                 gMaxfreq = cdev->ops->get_max_state(cdev, &dummy_maxstate); 
1176 #endif 
1177             } 
1178             break; 
1179         case THERMAL_TRIP_PASSIVE: 
1180             if (temp >= trip_temp || tz->passive) 
1181                 thermal_zone_device_passive(tz, temp, 
1182                                 trip_temp, count); 
1183             break; 
1184         } 
1185     }

其核心处理集中在这个for循环里,1139行,tz->trips的值是3,将迭代三次,这对应于开篇那个数据结构体的三个trip温度,分别是70,80,90

140~141行根据count的值将有三种情况:

count= 0对应于MONITOR_ZONE,类型将是THERMAL_TRIP_ACTIVEtrip_temp70

count= 1对应于WARN_ZONE,类型将是THERMAL_TRIP_PASSIVEtrip_temp80

count= 2对应于PANIC_ZONE,类型将是THERMAL_TRIP_CRITICALtrip_temp90

还有一个THERMAL_TRIP_HOT类型,这里暂时没有用到。

对应1143行的switch语句,首先进入的是1162~1168行的case语句,如果temp(调用exynos_get_temp获得的温度),如果该温度大于开篇结构体中设置的70度,则会调用cpufreq_set_cur_state将当前的state设置成1,具体完成设置的工作留到了cpufreq_apply_cooling(cpufreq_device,state);函数完成。如果温度低于70度,则说明非常安全,直接将state计数清零。

第二次进入THERMAL_TRIP_PASSIVE,即1179~1183行。if于君判断温度是否大于设定的第二个门限80,,如果大于说明,则表明不能在放任ondemand频率调节了,需要限制最高主频。thermal_zone_device_passive(tz,temp, trip_temp, count);

第三次进入THERMAL_TRIP_CRITICAL,说明这时温度非常高,如果if语句仍然成立,则需要紧急处理,这里其会判断是否到119度,如果到了,会down掉电源,进行保护。

现在有两条路了,一条是THERMAL_TRIP_PASSIVE调用thermal_zone_device_passive,一条是THERMAL_TRIP_ACTIVE下调用cpufreq_set_cur_state设置状态,这两种最终都将汇集到pufreq_apply_cooling(cpufreq_device,state);函数,先从THERMAL_TRIP_PASSIVE情况开始。

763 static void thermal_zone_device_passive(struct thermal_zone_device *tz, 
 764                     int temp, int trip_temp, int trip) 
 765 { 
...
 778     if (temp >= trip_temp) { 
 779         tz->passive = true; 
 780 
 781         trend = (tz->tc1 * (temp - tz->last_temperature)) + 
 782             (tz->tc2 * (temp - trip_temp)); 
 783 
 784         /* Heating up? */ 
 785         if (trend > 0) { 
 786             pr_debug("[TMU] PASSIVE: Trend up, temp=%d\n", temp); 
 787             list_for_each_entry(instance, &tz->cooling_devices, 
 788                         node) { 
 789                 if (instance->trip != trip) 
 790                     continue; 
 791                 cdev = instance->cdev; 
 792                 cdev->ops->get_cur_state(cdev, &state); 
 793                 cdev->ops->get_max_state(cdev, &max_state); 
 794                 if (state++ < max_state){ 
 795                     cdev->ops->set_cur_state(cdev, state); 
...
 799                 } 
 800             } 
 801         } else if (trend < 0) { /* Cooling off? */ 
 802             pr_debug("[TMU] PASSIVE: Trend down, temp=%d\n", temp); 
 803             list_for_each_entry(instance, &tz->cooling_devices, 
 804                         node) { 
 805                 if (instance->trip != trip) 
 806                     continue; 
 807                 cdev = instance->cdev; 
 808                 cdev->ops->get_cur_state(cdev, &state); 
 809                 cdev->ops->get_max_state(cdev, &max_state); 
 810                 if (state > 0) { 
 811                     pr_info("[TMU] PASSIVE: Below passive trip temp=%d\n", temp); 
 812                     cdev->ops->set_cur_state(cdev, --state); 
...
 816                 } 
 817             } 
 818         } 
 819         return; 
 820     } 

778~820行的代码处理的是当读取的温度高于设置的温度门限的处理方法,注意一旦该if语句成立,到819行将会返回,后面的将不会再被处理到。

781行的trend表示的是温度有上升还是下降趋势,在前面的初始化时将tc1tc2初始化成了1。这里需要举个例子说明trend的意义。

假如门限是80度,当前温度是83度,上一次进入该函数时温度是85度,则tz->last_temperature,则trend将等于-1,表示温度有下降的趋势,从83度小于85度可以看出这一趋势。

但是都大于80度,都是在passive情况下。这也是784行和801行注释的意义。

787~795找到对应的cooling设备,如果当前的state还没有大于最大的state,则将state1,并调用cpufreq_set_cur_state设置state,这和ACTIVE方式调用的接口一致。

如果是Coolingoff,则将state减一,并调用cpufreq_set_cur_state设置state

话说回来如果温度小于WARN门限值,则直接将state的状态减一后在设置就可以了。state对应于开篇的那张表的门限值的个数,到2时主频只能到1G了。

 829     list_for_each_entry(instance, &tz->cooling_devices, node) { 
 830         if (instance->trip != trip) 
 831             continue; 
 832         cdev = instance->cdev; 
 833         cdev->ops->get_cur_state(cdev, &state); 
 834         cdev->ops->get_max_state(cdev, &max_state); 
 835         if (state > 0) 
 836             cdev->ops->set_cur_state(cdev, --state); 
 837         if (state == 0) 
 838             tz->passive = false; 
 839 
...
 844     } 
 845 } 

不论TMU处于何种情况,总会调用cpufreq_set_cur_state函数,该函数简单对cpufreq_apply_cooling的封装。

343 static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, 
344                  unsigned long state) 
345 { 
346     int ret = -EINVAL; 
347     struct cpufreq_cooling_device *cpufreq_device; 
348 
349     mutex_lock(&cooling_cpufreq_lock); 
350     list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { 
351         if (cpufreq_device && cpufreq_device->cool_dev == cdev) { 
352             ret = 0; 
353             break; 
354         } 
355     } 
356     if (!ret) 
357         ret = cpufreq_apply_cooling(cpufreq_device, state); 
358 
359     mutex_unlock(&cooling_cpufreq_lock); 
360 
361     return ret; 
362 }

250~355行,用于寻找cooling设备,找到cooling设备,然后对该设备调用cpufreq_apply_cooling调节state

181 static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device, 
182                 unsigned long cooling_state) 
183 { 
184     unsigned int event, cpuid, state; 
185     struct freq_clip_table *th_table, *table_ptr; 
186     const struct cpumask *maskPtr = &cpufreq_device->allowed_cpus; 
187     struct cpufreq_cooling_device *cpufreq_ptr; 
188 
189     if (cooling_state > cpufreq_device->tab_size) 
190         return -EINVAL; 
191 
192     /*Check if the old cooling action is same as new cooling action*/ 
193     if (cpufreq_device->cpufreq_state == cooling_state) 
194         return 0; 
195         
196     /*pass cooling table info to the cpufreq_thermal_notifier callback*/ 
197     notify_table = NOTIFY_INVALID; 
198     
199     if (cooling_state > 0) { 
200         th_table = &(cpufreq_device->tab_ptr[cooling_state - 1]); 
201         notify_table = th_table; 
202     }   

193~194如果所处的coolingstate和像调节状态是一样,则不需要再调节,直接返回。

197~202根据cooling_state获得对应的开篇那张表,表中有两个关键的成员,freq_clip_maxtemp_level

204     /*check if any lower clip frequency active in other cpufreq_device's*/ 
205     list_for_each_entry(cpufreq_ptr, &cooling_cpufreq_list, node) { 
206 
207         state = cpufreq_ptr->cpufreq_state; 
208         if (state == 0 || cpufreq_ptr == cpufreq_device) 
209             continue; 
210 
211         if (!cpumask_equal(&cpufreq_ptr->allowed_cpus, 
212                 &cpufreq_device->allowed_cpus)) 
213             continue; 
214 
215         table_ptr = &(cpufreq_ptr->tab_ptr[state - 1]); 
216         if (notify_table == NULL || 
217                 (table_ptr->freq_clip_max < 
218                 notify_table->freq_clip_max)) 
219             notify_table =  table_ptr; 
220     }

204~220检查cooling_cpufreq_list链表上的cooling设备和notify设备的freq_clip_max之间大小,以最小的为基准。

222     cpufreq_device->cpufreq_state = cooling_state; 

保存coolingstate

224     if (notify_table != NOTIFY_INVALID) { 
225         event = CPUFREQ_COOLING_START; 
226         maskPtr = notify_table->mask_val; 
227     } else { 
228         event = CPUFREQ_COOLING_STOP; 
229     } 

初始化event类型和maskPtrmaskPtr是降频掩码,这里设置的是4,表示对5410四个核都进行相同的操作。

237    <span style="color:#FF6666;"> blocking_notifier_call_chain(</span>&cputherm_state_notifier_list, 
238                         event, notify_table); 

这里调用了通知链,通知CPUFREQ_COOLING_START事件。传递的参数是notify_table,扫描的链表是cputherm_state_notifier_list。不过并没有发现其通知了什么其它内核子系统。但是当系统除了降频降低温度外还采取其它措施来降低系统的温度,这一行的意义就非常的重要了。在系统上加一个转速可调的风扇,使用这里的notification就不需要额外注册thermalzone来实现风扇转速随温度调节。

240     for_each_cpu(cpuid, maskPtr) { 
241         if (is_cpufreq_valid(cpuid)) 
242             cpufreq_update_policy(cpuid); 
243     } 
244 
245     notify_table = NOTIFY_INVALID; 
246 
247     return 0; 
248 } 

240~243跟新cpu调频策略,对每一个CPU执行相同的调频策略更新,总共四个核。

插曲,编写风扇驱动,根据温度调节转速

对于驱动编写而言,下面使用237notifier的例子还是非常有参考意义的。

#include <linux/cpu_cooling.h>
#define CPUFREQ_COOLING_START		0

static struct notifier_block fan_notifer_block = {
    .notifier_call = s3c_fan_change_event,
};
static int __init s3c_fan_init(void)
{
	int err = 0;

	/* May malloc pwm deivce dynamically? */
	s3c_pwm0_dev.pwm = pwm_request(0, "pwm.motor");
	if (IS_ERR_VALUE((unsigned long)s3c_pwm0_dev.pwm))
		return -ENODEV;
	
	cputherm_register_notifier(&fan_notifer_block, CPUFREQ_COOLING_START);
	
	err =  misc_register(&s3c_fan_driver);
	if (err < 0) {
		return -ENODEV;
	}	
	return 0;
}

static void __exit s3c_fan_exit(void)
{
	misc_deregister(&s3c_fan_driver);
	return;
}

module_init(s3c_fan_init);
module_exit(s3c_fan_exit);

注意头文件以及cputherm_register_notifier的调用,其回调函数的第三个参数是启动structfreq_clip_table类型的变量,定义于cpu_cooling.h文件中。

cputherm_register_notifier(&fan_notifer_block, CPUFREQ_COOLING_START);

定义于cpufreq.c文件。

1735 int cpufreq_update_policy(unsigned int cpu) 
1736 { 
1737     struct cpufreq_policy *data = cpufreq_cpu_get(cpu); 
1738     struct cpufreq_policy policy; 
…
1752     memcpy(&policy, data, sizeof(struct cpufreq_policy)); 
1753     policy.min = data->user_policy.min; 
1754     policy.max = data->user_policy.max; 
1755     policy.policy = data->user_policy.policy; 
1756     policy.governor = data->user_policy.governor; 

1737行获得percpu变量,policy.min值对应开篇的结构体的900Mpolicy.max对应于开篇的1.6Gpolicy.governorondemand策略。

1759     if (cpufreq_driver->get) { 
1760         policy.cur = cpufreq_driver->get(cpu); 
1761         if (!data->cur) { 
1762             pr_debug("Driver did not initialize current freq"); 
1763             data->cur = policy.cur; 
1764         } else { 
1765             if (data->cur != policy.cur) 
1766                 cpufreq_out_of_sync(cpu, data->cur, 
1767                                 policy.cur); 
1768         } 
1769     } 

exynos框架里,第1759行的get函数是exynos_getspeed函数,该函数用于获取该策略下的主频,该主频将和percpu变量得到的频率进行对比(第1765行),如果不相等,则1766行函数会被调用,该函数用于异常处理,即实际的频率和保存的CPU频率不一致,则需要做些处理。该函数用于完成此功能。

1760行调用了一个get函数指针,该函数定义于如下结构体。

static struct cpufreq_driver exynos_driver = {
	.flags		= CPUFREQ_STICKY,
	.verify		= exynos_verify_speed,
	.target		= exynos_target,
	.get		= exynos_getspeed,
	.init		= exynos_cpufreq_cpu_init,
	.name		= "exynos_cpufreq",
#ifdef CONFIG_PM
	.suspend	= exynos_cpufreq_suspend,
	.resume		= exynos_cpufreq_resume,
#endif

};

1771     ret = __cpufreq_set_policy(data, &policy); 
1772 
1773     unlock_policy_rwsem_write(cpu); 
1774 
1775 fail: 
1776     cpufreq_cpu_put(data); 
1777 no_policy: 
1778     return ret; 
1779 } 

1771行设置cpufreq的策略。

1644 static int __cpufreq_set_policy(struct cpufreq_policy *data, 
1645                 struct cpufreq_policy *policy) 
1646 { 
…
1665     /* adjust if necessary - all reasons */ 
1666     blocking_notifier_call_chain(&cpufreq_policy_notifier_list, 
1667             CPUFREQ_ADJUST, policy); 
1668 
1669     /* adjust if necessary - hardware incompatibility*/ 
1670     blocking_notifier_call_chain(&cpufreq_policy_notifier_list, 
1671             CPUFREQ_INCOMPATIBLE, policy); 
…



1679     /* notification of the new policy */ 
1680     blocking_notifier_call_chain(&cpufreq_policy_notifier_list, 
1681             CPUFREQ_NOTIFY, policy); 


1684     data->min = policy->min; 
1685     data->max = policy->max; 

data对应的是percpu变量,可以看成是CPU的当前设置,policy针对的是这次调整的设置,所以data总是跟踪policy设置

1689     if (cpufreq_driver->setpolicy) { 
1690         data->policy = policy->policy; 
1691         pr_debug("setting range\n"); 
1692         ret = cpufreq_driver->setpolicy(policy); 
1693     } else { 
1694         if (policy->governor != data->governor) { 
1695             /* save old, working values */ 
1696             struct cpufreq_governor *old_gov = data->governor; 
1697 
1698             pr_debug("governor switch\n"); 
1699 
1700             /* end old governor */ 
1701             if (data->governor) 
1702                 __cpufreq_governor(data, CPUFREQ_GOV_STOP); 
1703                 
1704             /* start new governor */ 
1705             data->governor = policy->governor; 
1706             if (__cpufreq_governor(data, CPUFREQ_GOV_START)) { 
1707                 /* new governor failed, so re-start old one */ 
1708                 pr_debug("starting governor %s failed\n", 
1709                             data->governor->name); 
1710                 if (old_gov) { 
1711                     data->governor = old_gov; 
1712                     __cpufreq_governor(data, 
1713                                CPUFREQ_GOV_START); 
1714                 }              
1715                 ret = -EINVAL; 
1716                 goto error_out; 
1717             } 
1718             /* might be a policy change, too, so fall through */ 
1719         } 
1720         pr_debug("governor: change or update limits\n"); 
1721         __cpufreq_governor(data, CPUFREQ_GOV_LIMITS); 
1722     } 
1723 
1724 error_out: 
1725     return ret; 
1726 }   

1689行的cpufreq_driver->setpolicystaticstruct cpufreq_driverexynos_driver结构体中并没有定义,所以执行else分支语句。由于governor一直都是ondemand策略,所以1694~1720是切换governor的代码将不会得到执行。所以核心代码就到1721行了。

1509 static int __cpufreq_governor(struct cpufreq_policy *policy, 
1510                     unsigned int event) 
1511 { 
1512     int ret; 
1513 
1514     /* Only must be defined when default governor is known to have latency 
1515        restrictions, like e.g. conservative or ondemand. 
1516        That this is the case is already ensured in Kconfig 
1517     */ 
1518 #ifdef CONFIG_CPU_FREQ_GOV_PERFORMANCE 
1519     struct cpufreq_governor *gov = &cpufreq_gov_performance; 
1520 #else 
1521     struct cpufreq_governor *gov = NULL; 
1522 #endif 
1523 
1524     if (policy->governor->max_transition_latency && 
1525         policy->cpuinfo.transition_latency > 
1526         policy->governor->max_transition_latency) { 
1527         if (!gov) 
1528             return -EINVAL; 
1529         else { 
1530             printk(KERN_WARNING "%s governor failed, too long" 
1531                    " transition latency of HW, fallback" 
1532                    " to %s governor\n", 
1533                    policy->governor->name, 
1534                    gov->name); 
1535             policy->governor = gov; 
1536         } 
1537     } 
1538 
1539     if (!try_module_get(policy->governor->owner)) 
1540         return -EINVAL; 
1541 
1542     pr_debug("__cpufreq_governor for CPU %u, event %u\n", 
1543                         policy->cpu, event); 
1544     ret = policy->governor->governor(policy, event); 
1545 
1546     /* we keep one module reference alive for 
1547             each CPU governed by this CPU */ 
1548     if ((event != CPUFREQ_GOV_START) || ret) 
1549         module_put(policy->governor->owner); 
1550     if ((event == CPUFREQ_GOV_STOP) && !ret) 
1551         module_put(policy->governor->owner); 
1552 
1553     return ret; 
1554 } 

1518~1522行将gov赋值成performancegovernor1524~1537判断硬件的latency5410平台的policy->governor->max_transition_latency10000000policy->cpuinfo.transition_latency设置的值是100000。即hw的延迟要小于policy要求的延迟,所以这时并不需要跟换governor1535行)。

1544行调用governor处理事件。

governor定义于cpufreq_ondemand.c文件。

struct cpufreq_governor cpufreq_gov_ondemand = {
       .name                   = "ondemand",
       .governor               = cpufreq_governor_dbs,
       .max_transition_latency = TRANSITION_LATENCY_LIMIT,
       .owner                  = THIS_MODULE,
};

传递进来的event参数是CPUFREQ_GOV_LIMITS

1258 static int cpufreq_governor_dbs(struct cpufreq_policy *policy, 
1259                    unsigned int event) 
1260 { 
1261     unsigned int cpu = policy->cpu; 
1262     struct cpu_dbs_info_s *this_dbs_info; 
1263     unsigned int j; 
1264     int rc; 
1265 
1266     this_dbs_info = &per_cpu(od_cpu_dbs_info, cpu); 
1268     switch (event) { 
...
1352     case CPUFREQ_GOV_LIMITS: 
1353         mutex_lock(&this_dbs_info->timer_mutex); 
1354         if (policy->max < this_dbs_info->cur_policy->cur) 
1355             __cpufreq_driver_target(this_dbs_info->cur_policy, 
1356                 policy->max, CPUFREQ_RELATION_H); 
1357         else if (policy->min > this_dbs_info->cur_policy->cur) 
1358             __cpufreq_driver_target(this_dbs_info->cur_policy, 
1359                 policy->min, CPUFREQ_RELATION_L); 
1360         mutex_unlock(&this_dbs_info->timer_mutex); 
1361         break; 
1362     } 
1363     return 0; 
1364 } 

case语句的逻辑也比较好理解,如果设置的策略最频率度小于当前CPU频率,说明这时候需要调整CPU频率了。如果策略频率最小值还大于CPU当前频率,说明CPU处于频率合理状态,不需要调节。

这里并不着急去看频率调节的细节末枝,TMU的调频也是依赖ondemand策略来调节的。

ondemand策略中,注册一个work,间隔19微妙调用该函数一次。

INIT_DELAYED_WORK(&dbs_info->work, do_dbs_timer);

1175 static void do_dbs_timer(struct work_struct *work) 
1176 { 
1177     struct cpu_dbs_info_s *dbs_info = 
1178         container_of(work, struct cpu_dbs_info_s, work.work); 
1179     unsigned int cpu = dbs_info->cpu; 
1180     int sample_type = dbs_info->sample_type; 
1181 
1182     int delay; 
1183 
1184     mutex_lock(&dbs_info->timer_mutex); 
1185 
1186     /* Common NORMAL_SAMPLE setup */ 
1187     dbs_info->sample_type = DBS_NORMAL_SAMPLE; 
1188     if (!dbs_tuners_ins.powersave_bias || 
1189         sample_type == DBS_NORMAL_SAMPLE) { 
1190         dbs_check_cpu(dbs_info); 
1191         if (dbs_info->freq_lo) { 
1192             /* Setup timer for SUB_SAMPLE */ 
1193             dbs_info->sample_type = DBS_SUB_SAMPLE; 
1194             delay = dbs_info->freq_hi_jiffies; 
1195         } else { 
1196             /* We want all CPUs to do sampling nearly on 
1197              * same jiffy 
1198              */ 
1199             struct cpufreq_policy *policy = dbs_info->cur_policy; 
1200             dbs_info->rate_mult = 1; 
1201 
1202             delay = usecs_to_jiffies(dbs_tuners_ins.sampling_rate 
1203                 / dbs_info->rate_mult); 
1204 
1205             if (num_online_cpus() > 1) 
1206                 delay -= jiffies % delay; 
1207             } 
1208     } else { 
1209         __cpufreq_driver_target(dbs_info->cur_policy, 
1210             dbs_info->freq_lo, CPUFREQ_RELATION_H); 
1211         delay = dbs_info->freq_lo_jiffies; 
1212     } 
1213     schedule_delayed_work_on(cpu, &dbs_info->work, delay); 
1214     mutex_unlock(&dbs_info->timer_mutex); 
1215 } 
1187~1208行,由于三星平台dbs_tuners_ins.powersave_bias的值是0,所以该判断条件成立,else语句不成立。1190行之后的if语句判断了dbs_info成员值情况,下面的逻辑依赖该值,所以其更新是非常重要的, dbs_check_cpu该函数将计算CPU的负载,ondemand频率调节策略就是根据负载情况来调节CPU主频的。ondemand调节方法是,对每一次取样(dbs_check_cpu一次调用),如果idle时间小于20%,则会尝试提高CPU主频,如果某个频率可以有30%idle,则会采取降频措施。

1213~1214再次调度该work在delay时间后运行。

关于dbs_check_cpu细节就不展开了,这里主要讲讲计算负载后调节CPU主频的方法。这里提醒一下,CPU的主频并不仅限于开篇那个数据结构,而是以900M为下限,1.6G为上限,递增100M的频率可选结构。
主频增加会调用dbs_freq_increase函数,然而没用对称的降低主频的函数,不过不论是降低主频还是提高主频都将调用 __cpufreq_driver_target函数完成的。
Drivers/cpufreq/cpufreq.c
1445 int __cpufreq_driver_target(struct cpufreq_policy *policy,
1446                 unsigned int target_freq,
1447                 unsigned int relation)
1448 {
1449     int retval = -EINVAL;
1450     if (cpufreq_disabled())
1451         return -ENODEV;
1452 
1453     pr_debug("target for CPU %u: %u kHz, relation %u\n", policy->cpu,
1454         target_freq, relation);
1455     if (cpu_online(policy->cpu) && cpufreq_driver->target)
1456         retval = cpufreq_driver->target(policy, target_freq, relation);
1457 
1458     return retval;
1459 }
1460 EXPORT_SYMBOL_GPL(__cpufreq_driver_target);

1456行,调用exynos_target完成接下来的任务,其共有三个参数,第一个参数指向ondemand策略,第二个参数是期望的目标频率,第三个参数是关系,关系有两层,降低频率或者提高频率,定义于cpufreq.h文件。

#define CPUFREQ_RELATION_L 0  /* lowest frequency at or above target */
#define CPUFREQ_RELATION_H 1  /* highest frequency below or at target */

从这个函数名可以看出该函数和exynos平台是息息相关的,该函数

467 /* Set clock frequency */
 468 static int exynos_target(struct cpufreq_policy *policy,
 469               unsigned int target_freq,
 470               unsigned int relation)
 471 {
 472     cluster_type cur, old_cur;
 473     unsigned int index;
 474     int count, ret = 0;
 475     bool do_switch = false;
...
 491 
 492     /* get current frequency */
 493     freqs[cur]->old = exynos_getspeed(policy->cpu);
 494 
 495     target_freq = max((unsigned int)pm_qos_request(PM_QOS_CPU_FREQ_MIN), target_freq);
 496     target_freq = min((unsigned int)pm_qos_request(PM_QOS_CPU_FREQ_MAX), target_freq);

495~496行的最大和最小频率定义于drivers/cpufreq/exynos5410-cpufreq.c文件。

pm_qos_update_request(&exynos5_cpu_int_qos,160000);

这里我将其设置为了1.6G

 498     count = num_online_cpus();
 499     target_freq = min(target_freq, exynos_info[cur]->max_op_freqs[count]);
 500 
 501     if (cpufreq_frequency_table_target(policy, merge_freq_table,
 502                 target_freq, relation, &index)) {
 503         ret = -EINVAL;
 504         goto out;
 505     }
 506 
 507     target_freq = merge_freq_table[index].frequency;
 508 
 509     if (cur == CA15 && target_freq < STEP_LEVEL_CA15_MIN)
 510         do_switch = true;
 511     else if (cur == CA7 && target_freq > STEP_LEVEL_CA7_MAX)
 512         do_switch = true;
509~512四大核四小核的调频处理,大小核不能同时工作。

 514     /* Current T/F - ARM Core Voltage Down */
 515     if((do_switch == false) && (cur == CA7) && (lp_volt_need == 1)){
 516 
 517         volt = get_match_volt(ID_ARM, ACTUAL_FREQ(freq_min[CA15], CA15));
 518         volt = get_limit_voltage(volt);
 519         regulator_set_voltage(arm_regulator, volt, volt);
 520         lp_volt_need = 0;
 521     }
 522     else if(do_switch && (cur == CA15)){
 523         prev_cur_freq = target_freq;
 524         lp_volt_need = 1;
 525     }
 526     else if(do_switch && (cur == CA7)){
 527         volt = get_match_volt(ID_ARM, ACTUAL_FREQ(prev_cur_freq, CA15));
 528         volt = get_limit_voltage(volt);
 529         regulator_set_voltage(arm_regulator, volt, volt);
 530     }
 531     /* Current T/F - ARM Core Voltage Down */

515~530行,根据大小核情况进行处理,

 532 
 533 #ifdef CONFIG_BL_SWITCHER
 534     if (do_switch) {
 535         cur = exynos_switch(policy, old_cur);
 536         if (old_cur == cur)
 537             goto out;   /* Switching failed, No operation */
 538 
 539         freqs[cur]->old = exynos_getspeed_cluster(cur);
 540         policy->cur = freqs[cur]->old;
 541     }
 542 #endif
 543     /* frequency and volt scaling */
 544     ret = exynos_cpufreq_scale(target_freq, freqs[cur]->old, policy->cpu);
 545 
 546 out:
 547     mutex_unlock(&cpufreq_lock);
 548 
 549     return ret;
 550 }

533~544行,更底层的频率调节exynos5410_set_frequency_CA15exynos5410_set_clkdiv_CA15设置CPU主频

这里再给出两个命令行下调节的有用命令

cat /sys/bus/platform/drivers/exynos-tmu/exynos5-tmu/temp
 cat /sys/devices/system/cpu/cpu3/cpufreq/cpuinfo_cur_freq 

echo 1600000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
echo 600000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq  

cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq

 while true; do cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq;cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq;
cat /sys/devices/system/cpu/cpu1/cpufreq/cpuinfo_cur_freq; cat /sys/devices/system/cpu/cpu2/cpufreq/cpuinfo_cur_freq; 
cat /sys/devices/system/cpu/cpu3/cpufreq/cpuinfo_cur_freq; sleep 1; cat /sys/bus/platform/drivers/exynos-tmu/exynos5-tmu/temp; 
sleep 1;done

最后来一张数据结构图:

高通

查看CPU温度

 while true; do cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq;
cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq;
cat /sys/devices/system/cpu/cpu1/cpufreq/cpuinfo_cur_freq; 
cat /sys/devices/system/cpu/cpu2/cpufreq/cpuinfo_cur_freq; 
cat /sys/devices/system/cpu/cpu3/cpufreq/cpuinfo_cur_freq;

cat /sys/class/thermal/thermal_zone5/temp;cat /sys/class/thermal/thermal_zone6/temp;  
cat /sys/class/thermal/thermal_zone7/temp;cat /sys/class/thermal/thermal_zone8/temp;
echo '\n'; 
sleep 2;done

转载自原文链接, 如需删除请联系管理员。

原文链接:cpu 调频 温度 ondemand,转载请注明来源!

0