0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Thermal框架源碼剖析

Linux閱碼場(chǎng) ? 來源:Linux閱碼場(chǎng) ? 2023-03-13 17:09 ? 次閱讀

1. 框架結(jié)構(gòu)

thermal core:thermal主要的程序,驅(qū)動(dòng)初始化程序,維系thermal zone、governor、cooling device三者的關(guān)系。

thermal zone device:創(chuàng)建thermal zone節(jié)點(diǎn)和連接thermal sensor,在/sys/class/thermal/目錄下的thermal_zone*,通過dtsi文件進(jìn)行配置生成。thermal sensor是溫度傳感器(即熱敏電阻NTC),主要是給thermal提供溫度感知。

thermal govnernor:溫度控制算法,解決溫控發(fā)生時(shí)(即throttle),cooling device如何選擇cooling state的問題。

step_wise

power_allocator

user_space

bang_bang

fair_share

thermal cooling device:系統(tǒng)溫控的執(zhí)行者,實(shí)施冷卻措施的驅(qū)動(dòng)(cpufreq_cooling、cpuidle_cooling、 devfreq_cooling等)。cooling device根據(jù)governor計(jì)算出來的state,實(shí)施冷卻操作,一般情況下,state越高表示系統(tǒng)的冷卻需求越高。cooling device需要與trip point進(jìn)行綁定,當(dāng) trip point 觸發(fā)后,由相應(yīng)的cooling device 去實(shí)施冷卻措施。

10153162-bfd4-11ed-bfe3-dac502259ad0.png

2. 代碼結(jié)構(gòu)

10225cfc-bfd4-11ed-bfe3-dac502259ad0.png

2.1 thermal core

thermal_core.c主要是初始化driver,注冊(cè)governor,解析dtsi文件,創(chuàng)建thermal zone和初始通信

2.1.1 thermal結(jié)構(gòu)體定義

thermal.h,公開接口給其他驅(qū)動(dòng)程序調(diào)用

// thermal_zone_device ops配置
struct thermal_zone_device_ops {
int (*bind) (structthermal_zone_device *,
structthermal_cooling_device *);
int (*unbind)(struct thermal_zone_device *,
structthermal_cooling_device *);
int (*get_temp)(struct thermal_zone_device *, int *);
int (*set_trips)(struct thermal_zone_device *, int, int);
int (*change_mode)(struct thermal_zone_device *,
enumthermal_device_mode);
int (*get_trip_type)(struct thermal_zone_device *, int,
enumthermal_trip_type *);
int(*get_trip_temp) (struct thermal_zone_device *, int, int *);
int(*set_trip_temp) (struct thermal_zone_device *, int, int);
int(*get_trip_hyst) (struct thermal_zone_device *, int, int *);
int(*set_trip_hyst) (struct thermal_zone_device *, int, int);
int(*get_crit_temp) (struct thermal_zone_device *, int *);
int (*set_emul_temp)(struct thermal_zone_device *, int);
int (*get_trend)(struct thermal_zone_device *, int,
enum thermal_trend*);
int (*notify)(struct thermal_zone_device *, int,
enumthermal_trip_type);
};

// thermal_cooling_device ops配置
struct thermal_cooling_device_ops {
int(*get_max_state) (struct thermal_cooling_device *, unsigned long *);
int(*get_cur_state) (struct thermal_cooling_device *, unsigned long *);
int (*set_cur_state)(struct thermal_cooling_device *, unsigned long);
int(*get_requested_power)(struct thermal_cooling_device *, u32 *);
int(*state2power)(struct thermal_cooling_device *, unsigned long, u32 *);
int(*power2state)(struct thermal_cooling_device *, u32, unsigned long *);
};

thermal_core.c

//thermal驅(qū)動(dòng)的init入口函數(shù)

static int __init thermal_init(void)

{

 int result;

 // generic netlink初始化

 result =thermal_netlink_init();

 if (result)

 goto error;

 // 注冊(cè)thermal governor

 result =thermal_register_governors();

 if (result)

 goto error;

 // 注冊(cè)/sys/class/thermal節(jié)點(diǎn)

 result =class_register(&thermal_class);

 if (result)

 goto unregister_governors;

 // 解析dtsi配置文件中的thermal-zones字段,并注冊(cè)thermal_zone_device

 result =of_parse_thermal_zones();

 if (result)

 goto unregister_class;

 // 注冊(cè)notifier

 result =register_pm_notifier(&thermal_pm_nb);

 if (result)

pr_warn("Thermal: Can not register suspend notifier, return %d
",

 result);

 return 0;

unregister_class:

class_unregister(&thermal_class);

unregister_governors:

thermal_unregister_governors();

error:

ida_destroy(&thermal_tz_ida);

ida_destroy(&thermal_cdev_ida);

mutex_destroy(&thermal_list_lock);

mutex_destroy(&thermal_governor_lock);

mutex_destroy(&poweroff_lock);

 return result;

}

/* 將所有的governor策略(step_wise、power_allocator、user_space、fair_share)默認(rèn)都注冊(cè)給kernel */

static int __init thermal_register_governors(void)

{

 int ret = 0;

 structthermal_governor **governor;

 //遍歷注冊(cè)所有的governor策略

 for_each_governor_table(governor) {

 //注冊(cè)governor策略接口

 ret =thermal_register_governor(*governor);

 if (ret) {

 pr_err("Failed to register governor: '%s'",

 (*governor)->name);

 break;

 }

 pr_info("Registered thermal governor '%s'",

 (*governor)->name);

 }

 if (ret) {

 structthermal_governor **gov;

 for_each_governor_table(gov) {

 if (gov ==governor)

 break;

 thermal_unregister_governor(*gov);

 }

 }

 return ret;

}

// 將新governor添加到全局governor_list,設(shè)置默認(rèn)的governor

int thermal_register_governor(struct thermal_governor *governor)

{

 int err;

 const char *name;

 struct thermal_zone_device *pos;

 if (!governor)

 return -EINVAL;

 mutex_lock(&thermal_governor_lock);

 err = -EBUSY;

 if (!__find_governor(governor->name)) {

 bool match_default;

 err = 0;

 // 將新governor添加到全局governor_list

 list_add(&governor->governor_list, &thermal_governor_list);

 // 查找匹配默認(rèn)的governor,并設(shè)置默認(rèn)值

 match_default = !strncmp(governor->name,

 DEFAULT_THERMAL_GOVERNOR,

 THERMAL_NAME_LENGTH);

 // Kconfig中配置默認(rèn)的governor

if (!def_governor && match_default)

 def_governor = governor;

 }

 mutex_lock(&thermal_list_lock);

 list_for_each_entry(pos, &thermal_tz_list, node) {

 /*

 * only thermal zones with specified tz->tzp->governor_name

 * may run with tz->govenor unset

 */

 if (pos->governor)

 continue;

 name = pos->tzp->governor_name;

 if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH)) {

 int ret;

 ret = thermal_set_governor(pos, governor);

 if (ret)

 dev_err(&pos->device,

 "Failed to set governor %s for thermal zone %s: %d
",

 governor->name, pos->type, ret);

 }

 }

 mutex_unlock(&thermal_list_lock);

 mutex_unlock(&thermal_governor_lock);

 return err;

}

// 注冊(cè)一個(gè)thermal_zone_device

struct thermal_zone_device *

thermal_zone_device_register(const char *type, int trips, int mask,

 void *devdata, struct thermal_zone_device_ops *ops,

 struct thermal_zone_params *tzp, int passive_delay,

 int polling_delay)

{

 struct thermal_zone_device *tz;

 enum thermal_trip_type trip_type;

 int trip_temp;

 int id;

 int result;

 int count;

 struct thermal_governor *governor;

 if (!type || strlen(type) == 0) {

 pr_err("Error: No thermal zone type defined
");

 return ERR_PTR(-EINVAL);

 }

 if (type && strlen(type) >= THERMAL_NAME_LENGTH) {

 pr_err("Error: Thermal zone name (%s) too long, should be under %d chars
",

 type, THERMAL_NAME_LENGTH);

 return ERR_PTR(-EINVAL);

 }

 if (trips > THERMAL_MAX_TRIPS || trips < 0 || mask >> trips) {

 pr_err("Error: Incorrect number of thermal trips
");

 return ERR_PTR(-EINVAL);

 }

 if (!ops) {

 pr_err("Error: Thermal zone device ops not defined
");

 return ERR_PTR(-EINVAL);

 }

 if (trips > 0 && (!ops->get_trip_type || !ops->get_trip_temp))

 return ERR_PTR(-EINVAL);

 tz = kzalloc(sizeof(*tz), GFP_KERNEL);

 if (!tz)

 return ERR_PTR(-ENOMEM);

 // 初始化一個(gè)鏈表thermal_instances

 INIT_LIST_HEAD(&tz->thermal_instances);

 ida_init(&tz->ida);

 mutex_init(&tz->lock);

 // 自動(dòng)分配id

 id = ida_simple_get(&thermal_tz_ida, 0, 0, GFP_KERNEL);

 if (id < 0) {

 result = id;

 goto free_tz;

 }

 tz->id = id;

 strlcpy(tz->type, type, sizeof(tz->type));

 tz->ops = ops;

 tz->tzp = tzp;

 tz->device.class = &thermal_class;

 tz->devdata = devdata;

 tz->trips = trips;

 tz->passive_delay = passive_delay;

 tz->polling_delay = polling_delay;

 /* sys I/F */

 /* Add nodes that are always present via .groups */

 result = thermal_zone_create_device_groups(tz, mask);

 if (result)

 goto remove_id;

 /* A new thermal zone needs to be updated anyway. */

 atomic_set(&tz->need_update, 1);

 // 設(shè)置thermal_zone節(jié)點(diǎn)名稱

 dev_set_name(&tz->device, "thermal_zone%d", tz->id);

 result = device_register(&tz->device);

 if (result)

 goto release_device;

 for (count = 0; count < trips; count++) {

 if (tz->ops->get_trip_type(tz, count, &trip_type))

 set_bit(count, &tz->trips_disabled);

 if (tz->ops->get_trip_temp(tz, count, &trip_temp))

 set_bit(count, &tz->trips_disabled);

 /* Check for bogus trip points */

 if (trip_temp == 0)

 set_bit(count, &tz->trips_disabled);

 }

 /* Update 'this' zone's governor information */

 mutex_lock(&thermal_governor_lock);

 // thermal_zone設(shè)置governor,否則默認(rèn)governor

 if (tz->tzp)

 governor = __find_governor(tz->tzp->governor_name);

 else

 governor = def_governor;

 result = thermal_set_governor(tz, governor);

 if (result) {

 mutex_unlock(&thermal_governor_lock);

 goto unregister;

 }

 mutex_unlock(&thermal_governor_lock);

 if (!tz->tzp || !tz->tzp->no_hwmon) {

 result = thermal_add_hwmon_sysfs(tz);

 if (result)

 goto unregister;

 }

 mutex_lock(&thermal_list_lock);

 // 將thermal zone加入到thermal_tz_list

 list_add_tail(&tz->node, &thermal_tz_list);

 mutex_unlock(&thermal_list_lock);

 // 將thermal_cdev_list上的cooling設(shè)備綁定到thermal_zone_device上

 bind_tz(tz);

 // 初始化work queue下半部分,處理中斷需要響應(yīng)的操作,定時(shí)去調(diào)用thermal_zone_device_update函數(shù)

 // 設(shè)置polling_delay值為輪詢周期

 INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);

 // 對(duì)thermal zone的溫度等復(fù)位。

 thermal_zone_device_reset(tz);

 /* Update the new thermal zone and mark it as already updated. */

 if (atomic_cmpxchg(&tz->need_update, 1, 0))

 thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);

 thermal_notify_tz_create(tz->id, tz->type);

 return tz;

unregister:

 device_del(&tz->device);

release_device:

 put_device(&tz->device);

 tz = NULL;

remove_id:

 ida_simple_remove(&thermal_tz_ida, id);

free_tz:

 kfree(tz);

 return ERR_PTR(result);

}

// 將thermal_cdev_list上的cooling設(shè)備綁定到thermal_zone_device上

static void bind_tz(struct thermal_zone_device *tz)

{

 int i, ret;

 struct thermal_cooling_device *pos = NULL;

 const struct thermal_zone_params *tzp = tz->tzp;

 if (!tzp && !tz->ops->bind)

 return;

 mutex_lock(&thermal_list_lock);

 /* If there is ops->bind, try to use ops->bind */

 if (tz->ops->bind) {

 // 遍歷thermal_cdev_list,綁定cooling設(shè)備

 list_for_each_entry(pos, &thermal_cdev_list, node) {

 ret = tz->ops->bind(tz, pos);

 if (ret)

 print_bind_err_msg(tz, pos, ret);

 }

 goto exit;

 }

 if (!tzp || !tzp->tbp)

 goto exit;

 list_for_each_entry(pos, &thermal_cdev_list, node) {

 for (i = 0; i < tzp->num_tbps; i++) {

 if (tzp->tbp[i].cdev || !tzp->tbp[i].match)

 continue;

 if (tzp->tbp[i].match(tz, pos))

 continue;

 tzp->tbp[i].cdev = pos;

 __bind(tz, tzp->tbp[i].trip_mask, pos,

 tzp->tbp[i].binding_limits,

 tzp->tbp[i].weight);

 }

 }

exit:

 mutex_unlock(&thermal_list_lock);

}

// 檢查thermal_zone_device

static void thermal_zone_device_check(struct work_struct *work)

{

 //通過結(jié)構(gòu)體成員變量地址來獲取這個(gè)thermal_zone_device結(jié)構(gòu)體的地址

 struct thermal_zone_device *tz = container_of(work, struct

 thermal_zone_device,

 poll_queue.work);

 //更新thermal_zone_device

 thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);

}

void thermal_zone_device_update(struct thermal_zone_device *tz,

 enum thermal_notify_event event)

{

 int count;


//判斷是否需要輪詢的方式

 if (should_stop_polling(tz))

 return;

 if (atomic_read(&in_suspend))

 return;

 // 判斷sensor是否實(shí)現(xiàn)get_temp函數(shù)

 if (!tz->ops->get_temp)

 return;

 // 更新sensor溫度,也就是thermal_zone的溫度

 update_temperature(tz);

 // 更新trip值

 thermal_zone_set_trips(tz);

 tz->notify_event = event;

 for (count = 0; count < tz->trips; count++)

 handle_thermal_trip(tz, count);

}

// 更新thermal zone溫度

static void update_temperature(struct thermal_zone_device *tz)

{

 int temp, ret;

 // 獲取thermal_zone的溫度

 ret = thermal_zone_get_temp(tz, &temp);

 if (ret) {

 if (ret != -EAGAIN)

 dev_warn(&tz->device,

 "failed to read out thermal zone (%d)
",

 ret);

 return;

 }

 mutex_lock(&tz->lock);

 tz->last_temperature = tz->temperature;

 tz->temperature = temp;

 mutex_unlock(&tz->lock);

 trace_thermal_temperature(tz);

 thermal_genl_sampling_temp(tz->id, temp);

}

// 遍歷處理符合條件的trips

static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)

{

 enum thermal_trip_type type;

 int trip_temp, hyst = 0;

 /* Ignore disabled trip points */

 if (test_bit(trip, &tz->trips_disabled))

 return;

 // 獲取trip_temp、trip_type、get_trip_hyst

 tz->ops->get_trip_temp(tz, trip, &trip_temp);

 tz->ops->get_trip_type(tz, trip, &type);

 if (tz->ops->get_trip_hyst)

 tz->ops->get_trip_hyst(tz, trip, &hyst);

 if (tz->last_temperature != THERMAL_TEMP_INVALID) {

 // 觸發(fā)trip

 if (tz->last_temperature < trip_temp &&

 tz->temperature >= trip_temp)

 thermal_notify_tz_trip_up(tz->id, trip);

 // 觸發(fā)hysteresis

 if (tz->last_temperature >= trip_temp &&

 tz->temperature < (trip_temp - hyst))

 thermal_notify_tz_trip_down(tz->id, trip);

 }

 // 處理critical||hot的trips

 if (type == THERMAL_TRIP_CRITICAL || type == THERMAL_TRIP_HOT)

 handle_critical_trips(tz, trip, type);

 // 如果設(shè)置了governor,調(diào)用governor->throttle函數(shù)

 else

 handle_non_critical_trips(tz, trip);

 /*

 * Alright, we handled this trip successfully.

 * So, start monitoring again.

 */

 monitor_thermal_zone(tz);

}

// 處理type為critical||hot的trips

static void handle_critical_trips(struct thermal_zone_device *tz,

 int trip, enum thermal_trip_type trip_type)

{

 int trip_temp;

 tz->ops->get_trip_temp(tz, trip, &trip_temp);

 /* If we have not crossed the trip_temp, we do not care. */

 if (trip_temp <= 0 || tz->temperature < trip_temp)

 return;

 trace_thermal_zone_trip(tz, trip, trip_type);

 if (tz->ops->notify)

 tz->ops->notify(tz, trip, trip_type);

 // 如果是critical,準(zhǔn)備關(guān)機(jī)

 if (trip_type == THERMAL_TRIP_CRITICAL) {

 dev_emerg(&tz->device,

 "critical temperature reached (%d C), shutting down
",

 tz->temperature / 1000);

 mutex_lock(&poweroff_lock);

 if (!power_off_triggered) {

 /*

 * Queue a backup emergency shutdown in the event of

 * orderly_poweroff failure

 */

 // 調(diào)用thermal_emergency_poweroff準(zhǔn)備關(guān)機(jī)操作

 thermal_emergency_poweroff();

 orderly_poweroff(true);

 power_off_triggered = true;

 }

 mutex_unlock(&poweroff_lock);

 }

}

//處理其他的trips

//一般情況都是這個(gè)

static void handle_non_critical_trips(struct thermal_zone_device *tz, int trip)

{

 //如果設(shè)置了governor,調(diào)用governor->throttle函數(shù)

 //否則調(diào)用默認(rèn)的

 tz->governor ? tz->governor->throttle(tz, trip) :

 def_governor->throttle(tz, trip);

}

// 監(jiān)控delay時(shí)間進(jìn)行延時(shí)后工作

static void monitor_thermal_zone(struct thermal_zone_device *tz)

{

 bool stop;

 stop = should_stop_polling(tz);

 mutex_lock(&tz->lock);


// 超過閥值輪詢時(shí)間

 if (!stop && tz->passive)

 thermal_zone_device_set_polling(tz, tz->passive_delay);

 // 未超過閥值輪詢時(shí)間

 else if (!stop && tz->polling_delay)

 thermal_zone_device_set_polling(tz, tz->polling_delay);


// 取消調(diào)用,不輪詢

 else

 thermal_zone_device_set_polling(tz, 0);

 mutex_unlock(&tz->lock);

}

static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,

 int delay)

{

 //需延時(shí)后再使用system_freezable_power_efficient_wq的工作隊(duì)列進(jìn)行工作

 if (delay > 1000)

 mod_delayed_work(system_freezable_power_efficient_wq,

 &tz->poll_queue,

 round_jiffies(msecs_to_jiffies(delay)));

 else if (delay)

 mod_delayed_work(system_freezable_power_efficient_wq,

 &tz->poll_queue,

 msecs_to_jiffies(delay));

 // 刪除提交到工作隊(duì)列的任務(wù),不輪詢

 else

 cancel_delayed_work(&tz->poll_queue);

}

在thermal_helps.c定義thermal_zone_get_temp函數(shù)

int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)

{

 int ret = -EINVAL;

 int count;

 int crit_temp = INT_MAX;

 enum thermal_trip_type type;

 if (!tz || IS_ERR(tz) || !tz->ops->get_temp)

 goto exit;

 mutex_lock(&tz->lock);

 // 獲取當(dāng)前sensor的溫度,sensor里面實(shí)現(xiàn)

 ret = tz->ops->get_temp(tz, temp);

 // thermal debug開關(guān)

 if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {

 for (count = 0; count < tz->trips; count++) {

 ret = tz->ops->get_trip_type(tz, count, &type);

 if (!ret && type == THERMAL_TRIP_CRITICAL) {

 ret = tz->ops->get_trip_temp(tz, count,

 &crit_temp);

 break;

 }

 }

 /*

 * Only allow emulating a temperature when the real temperature

 * is below the critical temperature so that the emulation code

 * cannot hide critical conditions.

 */

 if (!ret && *temp < crit_temp)

 *temp = tz->emul_temperature;

 }

 mutex_unlock(&tz->lock);

exit:

 return ret;

}

// 獲取當(dāng)前溫度下的thermal zone下一次的trip點(diǎn)

void thermal_zone_set_trips(struct thermal_zone_device *tz)

{

 int low = -INT_MAX;

 int high = INT_MAX;

 int trip_temp, hysteresis;

 int i, ret;

 mutex_lock(&tz->lock);

 if (!tz->ops->set_trips || !tz->ops->get_trip_hyst)

 goto exit;

 for (i = 0; i < tz->trips; i++) {

 int trip_low;


// 獲取sensor的觸發(fā)溫度

 tz->ops->get_trip_temp(tz, i, &trip_temp);


// 獲取sensor下降溫度值恢復(fù)狀態(tài)

 tz->ops->get_trip_hyst(tz, i, &hysteresis);

 trip_low = trip_temp - hysteresis;

 if (trip_low < tz->temperature && trip_low > low)

 low = trip_low;

 if (trip_temp > tz->temperature && trip_temp < high)

 high = trip_temp;

 }

 /* No need to change trip points */

 // 與上次對(duì)比相同,不需要進(jìn)行更新

 if (tz->prev_low_trip == low && tz->prev_high_trip == high)

 goto exit;

 tz->prev_low_trip = low;

 tz->prev_high_trip = high;

 dev_dbg(&tz->device,

 "new temperature boundaries: %d < x < %d
", low, high);

 /*

 * Set a temperature window. When this window is left the driver

 * must inform the thermal core via thermal_zone_device_update.

 */

 // 設(shè)置新的溫度trip

 ret = tz->ops->set_trips(tz, low, high);

 if (ret)

 dev_err(&tz->device, "Failed to set trips: %d
", ret);

exit:

 mutex_unlock(&tz->lock);

}

2.2 dtsi文件解析

thermal_of.c解析dtsi文件,主要函數(shù)是of_parse_thermal_zones,創(chuàng)建解析生成thermal zone節(jié)點(diǎn)。

//解析dtsi中的&thermal_zones

int __init of_parse_thermal_zones(void)

{

 struct device_node *np, *child;

 struct __thermal_zone *tz;

 struct thermal_zone_device_ops *ops;

 //查找到thermal-zones

 np = of_find_node_by_name(NULL, "thermal-zones");

 if (!np) {

 pr_debug("unable to find thermal zones
");

 return 0; /* Run successfully on systems without thermal DT */

 }

 for_each_available_child_of_node(np, child) {

 struct thermal_zone_device *zone;

 struct thermal_zone_params *tzp;

 int i, mask = 0;

 u32 prop;

 //創(chuàng)建一個(gè)thermal zone節(jié)點(diǎn)

 tz = thermal_of_build_thermal_zone(child);

 if (IS_ERR(tz)) {

 pr_err("failed to build thermal zone %pOFn: %ld
",

 child,

 PTR_ERR(tz));

 continue;

 }

 //申請(qǐng)一段新內(nèi)存,并將of_thermal_ops中的內(nèi)容復(fù)制到新申請(qǐng)的這段內(nèi)存中

 ops = kmemdup(&of_thermal_ops, sizeof(*ops), GFP_KERNEL);

 if (!ops)

 goto exit_free;

 tzp = kzalloc(sizeof(*tzp), GFP_KERNEL);

 if (!tzp) {

 kfree(ops);

 goto exit_free;

 }

 /* No hwmon because there might be hwmon drivers registering */

 tzp->no_hwmon = true;

 // 解析sustainable-power字段

 if (!of_property_read_u32(child, "sustainable-power", &prop))

 tzp->sustainable_power = prop;

 for (i = 0; i < tz->ntrips; i++)

 mask |= 1 << i;

 /* these two are left for temperature drivers to use */

 tzp->slope = tz->slope;

 tzp->offset = tz->offset;

 // 向thermal_core注冊(cè)thermal_zone_device

 zone = thermal_zone_device_register(child->name, tz->ntrips,

 mask, tz,

 ops, tzp,

 tz->passive_delay,

 tz->polling_delay);

 if (IS_ERR(zone)) {

 pr_err("Failed to build %pOFn zone %ld
", child,

 PTR_ERR(zone));

 kfree(tzp);

 kfree(ops);

 of_thermal_free_zone(tz);

 /* attempting to build remaining zones still */

 }

 }

 of_node_put(np);

 return 0;

exit_free:

 of_node_put(child);

 of_node_put(np);

 of_thermal_free_zone(tz);

 /* no memory available, so free what we have built */

 of_thermal_destroy_zones();

 return -ENOMEM;

}

//創(chuàng)建一個(gè)thermal zone節(jié)點(diǎn)

static struct __thermal_zone

__init *thermal_of_build_thermal_zone(struct device_node *np)

{

 struct device_node *child = NULL, *gchild;

 struct __thermal_zone *tz;

 int ret, i;

 u32 prop, coef[2];

 if (!np) {

 pr_err("no thermal zone np
");

 return ERR_PTR(-EINVAL);

 }

 tz = kzalloc(sizeof(*tz), GFP_KERNEL);

 if (!tz)

 return ERR_PTR(-ENOMEM);


//解析polling-delay-passive,超過閥值輪詢時(shí)間

 ret = of_property_read_u32(np, "polling-delay-passive", &prop);

 if (ret < 0) {

 pr_err("%pOFn: missing polling-delay-passive property
", np);

 goto free_tz;

 }

 tz->passive_delay = prop;

 //解析polling-delay,未超閥值輪詢時(shí)間

 ret = of_property_read_u32(np, "polling-delay", &prop);

 if (ret < 0) {

 pr_err("%pOFn: missing polling-delay property
", np);

 goto free_tz;

 }

 tz->polling_delay = prop;

 /*

 * REVIST: for now, the thermal framework supports only

 * one sensor per thermal zone. Thus, we are considering

 * only the first two values as slope and offset.

 */

 //暫時(shí)支持一個(gè)sensor對(duì)應(yīng)一個(gè)thermal zone

 ret = of_property_read_u32_array(np, "coefficients", coef, 2);

 if (ret == 0) {

 tz->slope = coef[0];

 tz->offset = coef[1];

 } else {

 tz->slope = 1;

 tz->offset = 0;

 }

 /* trips */

 // 查找trips字段

 child = of_get_child_by_name(np, "trips");

 /* No trips provided */

 if (!child)

 goto finish;

 //獲取trips字段下child數(shù)量

 tz->ntrips = of_get_child_count(child);

 if (tz->ntrips == 0) /* must have at least one child */

 goto finish;

 tz->trips = kcalloc(tz->ntrips, sizeof(*tz->trips), GFP_KERNEL);

 if (!tz->trips) {

 ret = -ENOMEM;

 goto free_tz;

 }

 i = 0;

 for_each_child_of_node(child, gchild) {

 // 遍歷解析trips字段下面的字段

 ret = thermal_of_populate_trip(gchild, &tz->trips[i++]);

 if (ret)

 goto free_trips;

 }

 // 減少節(jié)點(diǎn)引用

 of_node_put(child);

 /* cooling-maps */

 // 查找cooling-maps字段

 child = of_get_child_by_name(np, "cooling-maps");

 /* cooling-maps not provided */

 if (!child)

 goto finish;

 tz->num_tbps = of_get_child_count(child);

 if (tz->num_tbps == 0)

 goto finish;

 tz->tbps = kcalloc(tz->num_tbps, sizeof(*tz->tbps), GFP_KERNEL);

 if (!tz->tbps) {

 ret = -ENOMEM;

 goto free_trips;

 }

 i = 0;

 for_each_child_of_node(child, gchild) {

 // 遍歷解析cooling-maps下的字段,綁定cooling device

 ret = thermal_of_populate_bind_params(gchild, &tz->tbps[i++],

 tz->trips, tz->ntrips);

 if (ret)

 goto free_tbps;

 }

finish:

 of_node_put(child);

 return tz;

free_tbps:

 for (i = i - 1; i >= 0; i--) {

 struct __thermal_bind_params *tbp = tz->tbps + i;

 int j;

 for (j = 0; j < tbp->count; j++)

 of_node_put(tbp->tcbp[j].cooling_device);

 kfree(tbp->tcbp);

 }

 kfree(tz->tbps);

free_trips:

 for (i = 0; i < tz->ntrips; i++)

 of_node_put(tz->trips[i].np);

 kfree(tz->trips);

 of_node_put(gchild);

free_tz:

 kfree(tz);

 of_node_put(child);

 return ERR_PTR(ret);

}

// 遍歷解析trips下的字段

static int thermal_of_populate_trip(struct device_node *np,

 struct thermal_trip *trip)

{

 int prop;

 int ret;

 // 解析temperature字段,觸發(fā)溫度值

 ret = of_property_read_u32(np, "temperature", &prop);

 if (ret < 0) {

 pr_err("missing temperature property
");

 return ret;

 }

 trip->temperature = prop;

 // 解析hysteresis字段,下降溫度值恢復(fù)狀態(tài)

 ret = of_property_read_u32(np, "hysteresis", &prop);

 if (ret < 0) {

 pr_err("missing hysteresis property
");

 return ret;

 }

 trip->hysteresis = prop;

 // 解析type字段,一般配置為passive,當(dāng)溫控發(fā)生后由governor控制

 ret = thermal_of_get_trip_type(np, &trip->type);

 if (ret < 0) {

 pr_err("wrong trip type property
");

 return ret;

 }

 /* Required for cooling map matching */

 trip->np = np;

 of_node_get(np);

 return 0;

}

//解析cooling-maps下字段

static int thermal_of_populate_bind_params(struct device_node *np,

 struct __thermal_bind_params *__tbp,

 struct thermal_trip *trips,

 int ntrips)

{

 struct of_phandle_args cooling_spec;

 struct __thermal_cooling_bind_param *__tcbp;

 struct device_node *trip;

 int ret, i, count;

 u32 prop;

 // 默認(rèn)contribution字段,表示權(quán)重值,可選

 __tbp->usage = THERMAL_WEIGHT_DEFAULT;

 ret = of_property_read_u32(np, "contribution", &prop);

 if (ret == 0)

 __tbp->usage = prop;

 // 獲取trip字段下phandle

 trip = of_parse_phandle(np, "trip", 0);

 if (!trip) {

 pr_err("missing trip property
");

 return -ENODEV;

 }

 //匹配trips列表中的trip

 for (i = 0; i < ntrips; i++)

 if (trip == trips[i].np) {

 __tbp->trip_id = i;

 break;

 }

 if (i == ntrips) {

 ret = -ENODEV;

 goto end;

 }

 //獲取cooling-device的phandle個(gè)數(shù)

 count = of_count_phandle_with_args(np, "cooling-device",

 "#cooling-cells");

 if (count <= 0) {

 pr_err("Add a cooling_device property with at least one device
");

 ret = -ENOENT;

 goto end;

 }

 __tcbp = kcalloc(count, sizeof(*__tcbp), GFP_KERNEL);

 if (!__tcbp) {

 ret = -ENOMEM;

 goto end;

 }

 for (i = 0; i < count; i++) {

 //獲取cooling-device的phandle參數(shù)

 ret = of_parse_phandle_with_args(np, "cooling-device",

 "#cooling-cells", i, &cooling_spec);

 if (ret < 0) {

 pr_err("Invalid cooling-device entry
");

 goto free_tcbp;

 }

 __tcbp[i].cooling_device = cooling_spec.np;

 //參數(shù)個(gè)數(shù)必須大于等于2,寫最小最大的范圍值,代表可調(diào)整最小最大檔位

 if (cooling_spec.args_count >= 2) { /* at least min and max */

 __tcbp[i].min = cooling_spec.args[0];

 __tcbp[i].max = cooling_spec.args[1];

 } else {

 pr_err("wrong reference to cooling device, missing limits
");

 }

 }

 __tbp->tcbp = __tcbp;

 __tbp->count = count;

 goto end;

free_tcbp:

 for (i = i - 1; i >= 0; i--)

 of_node_put(__tcbp[i].cooling_device);

 kfree(__tcbp);

end:

 of_node_put(trip);

 return ret;

}

thermal_extra.drawio.svg

2.3 thermal governor

目前可配置默認(rèn)的thermal governor策略

/* Default Thermal Governor */

#if defined(CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE)

#define DEFAULT_THERMAL_GOVERNOR "step_wise"

#elif defined(CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE)

#define DEFAULT_THERMAL_GOVERNOR "fair_share"

#elif defined(CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE)

#define DEFAULT_THERMAL_GOVERNOR "user_space"

#elif defined(CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR)

#define DEFAULT_THERMAL_GOVERNOR "power_allocator"

#endif

Kconfig中配置thermalgovernor默認(rèn)step_wise下降溫度值恢復(fù)狀態(tài)

config THERMAL_WRITABLE_TRIPS

 bool "Enable writable trip points"

 help

 This option allows the system integrator to choose whether

 trip temperatures can be changed from userspace. The

 writable trips need to be specified when setting up the

 thermal zone but the choice here takes precedence.

 Say 'Y' here if you would like to allow userspace tools to

 change trip temperatures.

choice

 prompt "Default Thermal governor"

 default THERMAL_DEFAULT_GOV_STEP_WISE

 help

 This option sets which thermal governor shall be loaded at

 startup. If in doubt, select 'step_wise'.

config THERMAL_DEFAULT_GOV_STEP_WISE

 bool "step_wise"

 select THERMAL_GOV_STEP_WISE

 help

 Use the step_wise governor as default. This throttles the

 devices one step at a time.

config THERMAL_DEFAULT_GOV_FAIR_SHARE

 bool "fair_share"

 select THERMAL_GOV_FAIR_SHARE

 help

 Use the fair_share governor as default. This throttles the

 devices based on their 'contribution' to a zone. The

 contribution should be provided through platform data.

config THERMAL_DEFAULT_GOV_USER_SPACE

 bool "user_space"

 select THERMAL_GOV_USER_SPACE

 help

 Select this if you want to let the user space manage the

 platform thermals.

config THERMAL_DEFAULT_GOV_POWER_ALLOCATOR

 bool "power_allocator"

 depends on THERMAL_GOV_POWER_ALLOCATOR

 help

 Select this if you want to control temperature based on

 system and device power allocation. This governor can only

 operate on cooling devices that implement the power API.

endchoice

2.3.1 step_wise governor

step_wise governor 是每個(gè)輪詢周期逐級(jí)提高冷卻狀態(tài),是一種相對(duì)溫和的溫控策略。根據(jù)cur_state、溫升趨勢(shì)trend、是否throttle去計(jì)算cooling_device的target_state,從而達(dá)到控制cooling_device來控制溫升。

對(duì)于cooling state的計(jì)算策略:

1.當(dāng)溫升趨勢(shì)為上升且發(fā)生throttle,使用更高一級(jí)的cooling state

2.當(dāng)溫升趨勢(shì)為下降

若發(fā)生throttle,不改變coolingstate

若解除throttle,使用更低一級(jí)的coolingstate

1.當(dāng)達(dá)到最高溫線且發(fā)生throttle,使用最高級(jí)的 cooling state

2.當(dāng)達(dá)到最低溫線且發(fā)生throttle,使用最低級(jí)的cooling state

注意: cooling state 取值范圍在[instance->lower,instance->upper],若cur_state< instance->lower,target_state則取值為THERMAL_NO_TARGET。

代碼框架圖

10374efa-bfd4-11ed-bfe3-dac502259ad0.png

thermal.h定義了溫升趨勢(shì)trend。

enum thermal_trend {
THERMAL_TREND_STABLE, /* 穩(wěn)定temperature is stable */
 THERMAL_TREND_RAISING,/* 上升 temperature is raising */
THERMAL_TREND_DROPPING, /* 下降temperature is dropping */
THERMAL_TREND_RAISE_FULL, /* 最高溫線apply highest cooling action */
THERMAL_TREND_DROP_FULL, /* 最低溫線apply lowest cooling action */
};

gov_step_wise.c

static int step_wise_throttle(struct thermal_zone_device *tz, int trip)
{
  struct thermal_instance*instance;
 // 更新trip、trend和計(jì)算cooling_device的target_state
thermal_zone_trip_update(tz, trip);
 if (tz->forced_passive)
thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE);
mutex_lock(&tz->lock);
 // 遍歷更新cooling_device的state
list_for_each_entry(instance, &tz->thermal_instances, tz_node)
thermal_cdev_update(instance->cdev);
mutex_unlock(&tz->lock);
 return 0;
}
// 更新trip、trend和計(jì)算cooling_device的target_state
static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
{
 int trip_temp;
 enum thermal_trip_type trip_type;
 enum thermal_trend trend;
 struct thermal_instance *instance;
  bool throttle = false;
 int old_target;
 // 獲取trip的類型和溫度
 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);
 }
 // 獲取溫升趨勢(shì),穩(wěn)定(THERMAL_TREND_STABLE),上升(THERMAL_TREND_RAISING),下降(THERMAL_TREND_DROPPING)
 trend =
get_tz_trend(tz, trip);
 // 當(dāng)zone溫度大于trip_temp,則需要進(jìn)行觸發(fā)
 if (tz->temperature >= trip_temp) {
throttle = true;
trace_thermal_zone_trip(tz, trip, trip_type);
 }
dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d
",
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;
 
    // 計(jì)算cooling_device的target_state
instance->target = get_target_state(instance, trend, throttle);
dev_dbg(&instance->cdev->device, "old_target=%d,
target=%d
",
 old_target, (int)instance->target);
 if (instance->initialized && 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->initialized = true;
mutex_lock(&instance->cdev->lock);
instance->cdev->updated = false; /* cdev needs update */
mutex_unlock(&instance->cdev->lock);
 }
 mutex_unlock(&tz->lock);
}
// 計(jì)算cooling_device的target_state
static unsigned long get_target_state(struct
thermal_instance *instance,
enum thermal_trend trend,bool throttle)
{
 struct thermal_cooling_device *cdev = instance->cdev;
 unsigned long cur_state;
 unsigned long next_target;
 /*
 * We
keep this instance the way it is by default.
 *
Otherwise, we use the current state of the
 * cdev
in use to determine the next_target.
 */
 //獲取cooling
device的當(dāng)前state
cdev->ops->get_cur_state(cdev, &cur_state);
next_target = instance->target;
dev_dbg(&cdev->device, "cur_state=%ld
", cur_state);
 //如果沒有初始化
 if (!instance->initialized) {
 if (throttle) {
// next_target初始值為(cur_state + 1),取值范圍在[instance->lower,instance->upper]
next_target = (cur_state + 1) >= instance->upper ?
 instance->upper :
 ((cur_state + 1) < instance->lower ?
 instance->lower :
(cur_state + 1));
 } else {
next_target = THERMAL_NO_TARGET;
 }
 return next_target;
 }
 switch (trend) {
 // 當(dāng)溫升趨勢(shì)為上升且發(fā)生throttle,使用更高一級(jí)的cooling state
 // 取值范圍在[instance->lower,instance->upper]
   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;
 // 當(dāng)達(dá)到最高溫線且發(fā)生throttle,使用最高級(jí)的cooling state,將溫度快速降下來
 case THERMAL_TREND_RAISE_FULL:
 if (throttle)
next_target = instance->upper;
 break;
 // 當(dāng)溫升趨勢(shì)為下降
 // 發(fā)生throttle,不改變cooling
state
 // 解除throttle,使用低一級(jí)的cooling
state
 case THERMAL_TREND_DROPPING:
 if (cur_state <= instance->lower) {
if (!throttle)
next_target = THERMAL_NO_TARGET;
 } else {   
if (!throttle) {
next_target = cur_state - 1;
if (next_target >
instance->upper)
 next_target =
instance->upper;
}
 }
 break;
 // 當(dāng)達(dá)到最低溫線且發(fā)生throttle,使用最低級(jí)的cooling
state
 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_cdev_update函數(shù)

// 更新cooling device的state
void thermal_cdev_update(struct
thermal_cooling_device *cdev)
{
 struct thermal_instance *instance;
 unsigned long target = 0;
mutex_lock(&cdev->lock);
 /* cooling device is updated*/
 if (cdev->updated) {
mutex_unlock(&cdev->lock);
 return;
 }
 /* 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
",
           instance->tz->id,
instance->target);
 if (instance->target == THERMAL_NO_TARGET)
continue;
 if (instance->target > target)
target = instance->target;
 }
 // 設(shè)置cooling
device的state
thermal_cdev_set_cur_state(cdev, target);
cdev->updated = true;
mutex_unlock(&cdev->lock);
trace_cdev_update(cdev, target);
dev_dbg(&cdev->device, "set to state
%lu
", target);
}

2.3.2 power_allocator governor

IPA(Intelligent PowerAllocation)是由ARM開發(fā)的符合linux內(nèi)核thermalframework的governor,代碼中的名字為power_allocator,旨在滿足溫控效果的條件下最大化性能。IPA(Intelligent Power Allocator)模型的核心是利用 PID 控制器,ThermalZone 的溫度作為輸入,可分配功耗值作為輸出,調(diào)節(jié) Allocator 的頻率和電壓值。

代碼框架圖

10485ee8-bfd4-11ed-bfe3-dac502259ad0.png

功耗均衡原理圖

105932ae-bfd4-11ed-bfe3-dac502259ad0.png

gov_power_allocator.c

static int power_allocator_throttle(struct thermal_zone_device *tz, int trip)

{

 int ret;

 int switch_on_temp, control_temp;

 struct power_allocator_params *params = tz->governor_data;

 /*

 * We get called for every trip point but we only need to do

 * our calculations once

 */

 if (trip != params->trip_max_desired_temperature)

 return 0;

 // 獲取trip溫度,作為switch_on觸發(fā)溫度

 ret = tz->ops->get_trip_temp(tz, params->trip_switch_on,

 &switch_on_temp);

 if (!ret && (tz->temperature < switch_on_temp)) {

 tz->passive = 0;

 reset_pid_controller(params);

 allow_maximum_power(tz);

 return 0;

 }

 tz->passive = 1;

 // 獲取trip溫度,作為目標(biāo)的溫度值

 ret = tz->ops->get_trip_temp(tz, params->trip_max_desired_temperature,

 &control_temp);

 if (ret) {

 dev_warn(&tz->device,

 "Failed to get the maximum desired temperature: %d
",

 ret);

 return ret;

 }

 // IPA主要的算法邏輯

 return allocate_power(tz, control_temp);

}

// IPA主要的算法邏輯

static int allocate_power(struct thermal_zone_device *tz,

 int control_temp)

{

 struct thermal_instance *instance;

 struct power_allocator_params *params = tz->governor_data;

 u32 *req_power, *max_power, *granted_power, *extra_actor_power;

 u32 *weighted_req_power;

 u32 total_req_power, max_allocatable_power, total_weighted_req_power;

 u32 total_granted_power, power_range;

 int i, num_actors, total_weight, ret = 0;

 int trip_max_desired_temperature = params->trip_max_desired_temperature;

 mutex_lock(&tz->lock);

 num_actors = 0;

 total_weight = 0;

 list_for_each_entry(instance, &tz->thermal_instances, tz_node) {

 if ((instance->trip == trip_max_desired_temperature) &&

 cdev_is_power_actor(instance->cdev)) {

 num_actors++;

 total_weight += instance->weight;

 }

 }

 if (!num_actors) {

 ret = -ENODEV;

 goto unlock;

 }

 /*

 * We need to allocate five arrays of the same size:

 * req_power, max_power, granted_power, extra_actor_power and

 * weighted_req_power. They are going to be needed until this

 * function returns. Allocate them all in one go to simplify

 * the allocation and deallocation logic.

 */

 BUILD_BUG_ON(sizeof(*req_power) != sizeof(*max_power));

 BUILD_BUG_ON(sizeof(*req_power) != sizeof(*granted_power));

 BUILD_BUG_ON(sizeof(*req_power) != sizeof(*extra_actor_power));

 BUILD_BUG_ON(sizeof(*req_power) != sizeof(*weighted_req_power));

 req_power = kcalloc(num_actors * 5, sizeof(*req_power), GFP_KERNEL);

 if (!req_power) {

 ret = -ENOMEM;

 goto unlock;

 }

 max_power = &req_power[num_actors];

 granted_power = &req_power[2 * num_actors];

 extra_actor_power = &req_power[3 * num_actors];

 weighted_req_power = &req_power[4 * num_actors];

 i = 0;

 total_weighted_req_power = 0;

 total_req_power = 0;

 max_allocatable_power = 0;

 // 遍歷所有的cooling device

 list_for_each_entry(instance, &tz->thermal_instances, tz_node) {

 int weight;

 struct thermal_cooling_device *cdev = instance->cdev;

 if (instance->trip != trip_max_desired_temperature)

 continue;


// cooling device的ops的函數(shù)指針get_requested_power、state2power和power2state是否存在

 if (!cdev_is_power_actor(cdev))

 continue;

 // 獲取cooling device的功耗需求requested power

 if (cdev->ops->get_requested_power(cdev, &req_power[i]))

 continue;

 if (!total_weight)

 weight = 1 << FRAC_BITS;

 else

 weight = instance->weight;

 //獲取cooling device的權(quán)重功耗,weight*requested_power

 weighted_req_power[i] = frac_to_int(weight * req_power[i]);

 // 獲取cooling device可以消耗的最大功率

 if (power_actor_get_max_power(cdev, &max_power[i]))

 continue;


// 總的cdev需要的功耗

 total_req_power += req_power[i];

 // 總的最大可分配的功耗

 max_allocatable_power += max_power[i];

 // 總的cdev需要的權(quán)重功耗

 total_weighted_req_power += weighted_req_power[i];

 i++;

 }

 // PID控制算法,power_range是當(dāng)前溫度下可配置的最大功耗值

 power_range = pid_controller(tz, control_temp, max_allocatable_power);

 // 分?jǐn)傆?jì)算出當(dāng)前溫度下每個(gè)cooling device的最終的total granted_power

 // 公式:total granted_power = granted_power + extra_granted_power

 divvy_up_power(weighted_req_power, max_power, num_actors,

 total_weighted_req_power, power_range, granted_power,

 extra_actor_power);

 total_granted_power = 0;

 i = 0;

 list_for_each_entry(instance, &tz->thermal_instances, tz_node) {

 if (instance->trip != trip_max_desired_temperature)

 continue;

 if (!cdev_is_power_actor(instance->cdev))

 continue;


// 給cooling device設(shè)置granted_power

 power_actor_set_power(instance->cdev, instance,

 granted_power[i]);

 total_granted_power += granted_power[i];

 i++;

 }

 trace_thermal_power_allocator(tz, req_power, total_req_power,

 granted_power, total_granted_power,

 num_actors, power_range,

 max_allocatable_power, tz->temperature,

 control_temp - tz->temperature);

 kfree(req_power);

unlock:

 mutex_unlock(&tz->lock);

 return ret;

}

// 分?jǐn)傆?jì)算出當(dāng)前溫度下cooling device的最終的total_granted_power

static void divvy_up_power(u32 *req_power, u32 *max_power, int num_actors,

 u32 total_req_power, u32 power_range,

 u32 *granted_power, u32 *extra_actor_power)

{

 u32 extra_power, capped_extra_power;

 int i;

 /*

 * Prevent division by 0 if none of the actors request power.

 */

 if (!total_req_power)

 total_req_power = 1;

 capped_extra_power = 0;

 extra_power = 0;

 for (i = 0; i < num_actors; i++) {

 u64 req_range = (u64)req_power[i] * power_range;

 // granted_power(cooling device被分配的功耗),

 // total_req_power值為total_weighted_req_power

 // req_power值為weighted_req_power

 // power_range:power_range是當(dāng)前溫度下可配置的最大功耗值

 //公式:四舍五入power_range * (weighted_req_power[i] / total_weighted_req_power)

 granted_power[i] = DIV_ROUND_CLOSEST_ULL(req_range,

 total_req_power);

 // device granted_power不能大于max_power

 if (granted_power[i] > max_power[i]) {

 // 額外需要的功耗,累加分配過多的功耗

 extra_power += granted_power[i] - max_power[i];

 granted_power[i] = max_power[i];

 }

 // 計(jì)算分配過多的功耗,再分配的權(quán)重

// 公式:(max_power[i] - granted_power[i])/capped_extra_power

 extra_actor_power[i] = max_power[i] - granted_power[i];

 capped_extra_power += extra_actor_power[i];

 }

 if (!extra_power)

 return;

 /*

 * Re-divvy the reclaimed extra among actors based on

 * how far they are from the max

 */

 // 重新分配額外功耗

 // 假設(shè)granted_extra_power

 // 公式:granted_extra_power[i] = extra_power * (max_power[i] - granted_power[i])/capped_extra_power

 // cooling device總的分配功耗:granted_power[i] += granted_extra_power[i]

 // extra_power最大取值為capped_extra_power

 extra_power = min(extra_power, capped_extra_power);

 if (capped_extra_power > 0)

 for (i = 0; i < num_actors; i++)

 granted_power[i] += (extra_actor_power[i] *

 extra_power) / capped_extra_power;

}

// pid控制算法

static u32 pid_controller(struct thermal_zone_device *tz,

 int control_temp,

 u32 max_allocatable_power)

{

 s64 p, i, d, power_range;

 s32 err, max_power_frac;

 u32 sustainable_power;

 struct power_allocator_params *params = tz->governor_data;

 max_power_frac = int_to_frac(max_allocatable_power);

 // sustainable_power:保證所有cooling device的正常運(yùn)行的最小功耗值。(state最大)

 if (tz->tzp->sustainable_power) {

 //如果設(shè)置了,按照設(shè)置的來

 sustainable_power = tz->tzp->sustainable_power;

 } else {

 // 默認(rèn)sustainable_power,所有cooling device在最大state下的最小功耗值進(jìn)行累加

 sustainable_power = estimate_sustainable_power(tz);

 // 默認(rèn)pid的參數(shù)值,K_pu、K_po、K_pi

 estimate_pid_constants(tz, sustainable_power,

 params->trip_switch_on, control_temp,

 true);

 }

 // 當(dāng)前溫度和目標(biāo)溫度的差值

 err = control_temp - tz->temperature;

 err = int_to_frac(err);

 /*

 * 計(jì)算比例項(xiàng)

 * 公式:K_p*err(目標(biāo)溫度和當(dāng)前溫度的差值)

 * 當(dāng)前溫度<=目標(biāo)溫度 k_pu = int_to_frac(2*sustainable_power / (control_temp - switch_on_temp))

 * 當(dāng)前溫度>目標(biāo)溫度 k_po = int_to_frac(sustainable_power / (control_temp - switch_on_temp))

 */

 p = mul_frac(err < 0 ? tz->tzp->k_po : tz->tzp->k_pu, err);

 /*

 * 計(jì)算積分項(xiàng)

 * 公式:K_i*err_integral(差值的累加)

 * 默認(rèn):K_i = int_to_frac(10 / 1000)

 * if the error is less than cut off allow integration (but

 * the integral is limited to max power)

 */

 i = mul_frac(tz->tzp->k_i, params->err_integral);

 // integral_cutoff默認(rèn)為0

 // err < 0,這次的err不進(jìn)行累加

 if (err < int_to_frac(tz->tzp->integral_cutoff)) {

 s64 i_next = i + mul_frac(tz->tzp->k_i, err);

 // (K_i * err_integral)必須小于max_power_frac

 if (abs(i_next) < max_power_frac) {

 i = i_next;

 params->err_integral += err;

 }

 }

 /*

 * 計(jì)算微分項(xiàng)

 * 公式:K_d*(err - prev_err) / passive_delay

 * 默認(rèn):K_d = 0

 * We do err - prev_err, so with a positive k_d, a decreasing

 * error (i.e. driving closer to the line) results in less

 * power being applied, slowing down the controller)

 */

 d = mul_frac(tz->tzp->k_d, err - params->prev_err);

 d = div_frac(d, tz->passive_delay);

 params->prev_err = err;

 power_range = p + i + d;

 //當(dāng)前溫度下允許的最大功耗值 = sustainable_power + frac_to_int(p + i + d)

 power_range = sustainable_power + frac_to_int(power_range);

 // power_range 取值在[0,max_allocatable_power]

 power_range = clamp(power_range, (s64)0, (s64)max_allocatable_power);

 trace_thermal_power_allocator_pid(tz, frac_to_int(err),

 frac_to_int(params->err_integral),

 frac_to_int(p), frac_to_int(i),

 frac_to_int(d), power_range);

 return power_range;

}

// 所有cooling device在最大state下的最小功耗值進(jìn)行累加

static u32 estimate_sustainable_power(struct thermal_zone_device *tz)

{

 u32 sustainable_power = 0;

 struct thermal_instance *instance;

 struct power_allocator_params *params = tz->governor_data;

 list_for_each_entry(instance, &tz->thermal_instances, tz_node) {

 struct thermal_cooling_device *cdev = instance->cdev;

 u32 min_power;

 if (instance->trip != params->trip_max_desired_temperature)

 continue;

 // 獲取cdev的最小功耗值

 if (power_actor_get_min_power(cdev, &min_power))

 continue;

 // 累加cooling device的最小功耗值

 sustainable_power += min_power;

 }

 return sustainable_power;

}

// 默認(rèn)pid的參數(shù)值

static void estimate_pid_constants(struct thermal_zone_device *tz,

 u32 sustainable_power, int trip_switch_on,

 int control_temp, bool force)

{

 int ret;

 int switch_on_temp;

 u32 temperature_threshold;

 // 獲取switch_on_temp,觸發(fā)算法開關(guān)

 ret = tz->ops->get_trip_temp(tz, trip_switch_on, &switch_on_temp);

 if (ret)

 switch_on_temp = 0;

 // 目標(biāo)溫度和觸發(fā)溫度的差值

 temperature_threshold = control_temp - switch_on_temp;

 /*

 * estimate_pid_constants() tries to find appropriate default

 * values for thermal zones that don't provide them. If a

 * system integrator has configured a thermal zone with two

 * passive trip points at the same temperature, that person

 * hasn't put any effort to set up the thermal zone properly

 * so just give up.

 */

 if (!temperature_threshold)

 return;

 // Kp的取值分階段k_pu和k_po,int_to_frac只是為了避免小數(shù)的影響,先左移動(dòng),后在mul_frac中右移

 // k_po = int_to_frac(sustainable_power / (control_temp - switch_on_temp))

 if (!tz->tzp->k_po || force)

 tz->tzp->k_po = int_to_frac(sustainable_power) /

 temperature_threshold;

 // k_pu = int_to_frac(2*sustainable_power / (control_temp - switch_on_temp))

 if (!tz->tzp->k_pu || force)

 tz->tzp->k_pu = int_to_frac(2 * sustainable_power) /

 temperature_threshold;

 // k_i = int_to_frac(10 / 1000)

 if (!tz->tzp->k_i || force)

 tz->tzp->k_i = int_to_frac(10) / 1000;

 /*

 * The default for k_d and integral_cutoff is 0, so we can

 * leave them as they are.

 */

 // 默認(rèn)k_d = 0 , integral_cutoff = 0

}

power_actor_get_max_power,獲取cooling device最大功耗值

int power_actor_get_max_power(struct thermal_cooling_device *cdev,

 u32 *max_power)

{

 if (!cdev_is_power_actor(cdev))

 return -EINVAL;

 // 將cooling device的state轉(zhuǎn)換為power,當(dāng)power = max_power,state為0

 return cdev->ops->state2power(cdev, 0, max_power);

}

例如,cooling device是cpu,冷卻措施是調(diào)節(jié)cpu frequency,cpufreq_cooling.c

// 將 cpu cdev state轉(zhuǎn)換為功耗

static int cpufreq_state2power(struct thermal_cooling_device *cdev,

 unsigned long state, u32 *power)

{

 unsigned int freq, num_cpus, idx;

 struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;

 /* Request state should be less than max_level */

 if (state > cpufreq_cdev->max_level)

 return -EINVAL;

 //獲取同一個(gè)簇中的cpu數(shù)量

 num_cpus = cpumask_weight(cpufreq_cdev->policy->cpus);

 idx = cpufreq_cdev->max_level - state;

 // 獲取相應(yīng)的state對(duì)應(yīng)的CPU頻率

 freq = cpufreq_cdev->em->table[idx].frequency;

 // 獲取同一簇的cpu頻率對(duì)應(yīng)的功耗值,查表

 *power = cpu_freq_to_power(cpufreq_cdev, freq) * num_cpus;

 return 0;

}

// 獲取CPU freq的requested_power(當(dāng)前cpu load需要的功耗值)

static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev,

 u32 *power)

{

 unsigned long freq;

 int i = 0, cpu;

 u32 total_load = 0;

 struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;

 struct cpufreq_policy *policy = cpufreq_cdev->policy;

 u32 *load_cpu = NULL;

 // 獲取當(dāng)前的CPU頻率

 freq = cpufreq_quick_get(policy->cpu);

 if (trace_thermal_power_cpu_get_power_enabled()) {

 u32 ncpus = cpumask_weight(policy->related_cpus);

 load_cpu = kcalloc(ncpus, sizeof(*load_cpu), GFP_KERNEL);

 }

 // 遍歷獲取cpu的負(fù)載

 for_each_cpu(cpu, policy->related_cpus) {

 u32 load;

 if (cpu_online(cpu))

 load = get_load(cpufreq_cdev, cpu, i);

 else

 load = 0;

 total_load += load;

 if (load_cpu)

 load_cpu[i] = load;

 i++;

 }

 //cpu總負(fù)載

 cpufreq_cdev->last_load = total_load;

 // 獲取cpu動(dòng)態(tài)功耗值

 // 根據(jù)查找表,cpu當(dāng)前頻率對(duì)應(yīng)的功耗值

 // 然后raw_cpu_power * (total_load / 100)

 *power = get_dynamic_power(cpufreq_cdev, freq);

 if (load_cpu) {

 trace_thermal_power_cpu_get_power(policy->related_cpus, freq,

 load_cpu, i, *power);

 kfree(load_cpu);

 }

 return 0;

}

2.3.3 bang_bang governor

當(dāng)throttle發(fā)生,打開風(fēng)扇

當(dāng)throttle解除,關(guān)閉風(fēng)扇。

2.3.4 user_space governor

user_space governor 是通過 uevent 將溫區(qū)當(dāng)前溫度,溫控觸發(fā)點(diǎn)等信息上報(bào)到用戶空間,由用戶空間軟件制定溫控的策略。

2.4 綁定sensor

以bcl_soc為例,這里是創(chuàng)建一個(gè)platform_driver,platform_driver必須實(shí)現(xiàn)probe和remove函數(shù),bcl_soc是不需要通過polling(輪詢)的方式去檢查是否觸發(fā),polling-delay是輪詢的周期。它是通過監(jiān)聽系統(tǒng)電量的變化,去回調(diào)battery_supply_callback函數(shù),去喚醒隊(duì)列中的bcl_evaluate_soc函數(shù),通過bcl_evaluate_soc函數(shù)進(jìn)行獲取當(dāng)前的溫度和處理符合觸發(fā)條件的trips。

bcl_soc:bcl-soc {
compatible = "qcom,msm-bcl-soc";
 #thermal-sensor-cells = <0>;
};

bcl_soc.c

#define pr_fmt(fmt) "%s:%s " fmt,
KBUILD_MODNAME, __func__
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "../thermal_core.h"
#define BCL_DRIVER_NAME "bcl_soc_peripheral"
struct bcl_device {
 struct notifier_block            psy_nb;
 struct work_struct            soc_eval_work;
 long int                trip_temp;
 int                    trip_val;
 struct mutex                state_trans_lock;
 bool                    irq_enabled;
 struct thermal_zone_device        *tz_dev;
 struct thermal_zone_of_device_ops    ops;
};
static struct bcl_device *bcl_perph;
// 綁定trip_temp接口,設(shè)置觸發(fā)值trip_temp
static int bcl_set_soc(void *data, int low, int high)
{
 if (low == bcl_perph->trip_temp)
 return 0;
mutex_lock(&bcl_perph->state_trans_lock);
pr_debug("low soc threshold:%d
", low);
 // 設(shè)置trip_temp
bcl_perph->trip_temp = low;
 if (low == INT_MIN) {
bcl_perph->irq_enabled = false;
 goto unlock_and_exit;
 }
bcl_perph->irq_enabled = true;
schedule_work(&bcl_perph->soc_eval_work);
unlock_and_exit:
mutex_unlock(&bcl_perph->state_trans_lock);
 return 0;
}
// 綁定get_temp接口,獲取電量值
static int bcl_read_soc(void *data, int *val)
{
 static struct power_supply*batt_psy;
 union power_supply_propval ret = {0,};
 int err = 0;
 *val = 100;
 if (!batt_psy)
 batt_psy
= power_supply_get_by_name("battery");
 if (batt_psy) {
 // 獲取電量
 err
= power_supply_get_property(batt_psy,
POWER_SUPPLY_PROP_CAPACITY, &ret);
 if (err) {
pr_err("battery percentage read error:%d
",
err);
return err;
 }
*val = ret.intval;
 }
pr_debug("soc:%d
", *val);
 return err;
}
// 獲取當(dāng)前溫度和處理thermal zone trip
static void bcl_evaluate_soc(struct work_struct *work)
{
 int battery_percentage;
 // 獲取電量
 if (bcl_read_soc(NULL, &battery_percentage))
 return;
mutex_lock(&bcl_perph->state_trans_lock);
 if (!bcl_perph->irq_enabled)
 goto eval_exit;
 if (battery_percentage >
bcl_perph->trip_temp)
 goto eval_exit;
 // 當(dāng)前電量值
bcl_perph->trip_val = battery_percentage;
mutex_unlock(&bcl_perph->state_trans_lock);
 // 處理thermal
zone trip,調(diào)用的是thermal
core中的handle_thermal_trip
of_thermal_handle_trip(bcl_perph->tz_dev);
 return;
eval_exit:
mutex_unlock(&bcl_perph->state_trans_lock);
}
// 電量變化回調(diào)battery_supply_callback函數(shù),去喚醒隊(duì)列中的bcl_evaluate_soc函數(shù)
static int battery_supply_callback(struct notifier_block *nb,
unsigned long event, void *data)
{
 struct power_supply *psy = data;
 if (strcmp(psy->desc->name, "battery"))
 return NOTIFY_OK;
schedule_work(&bcl_perph->soc_eval_work);
 return NOTIFY_OK;
}
static int bcl_soc_remove(struct platform_device *pdev)
{
power_supply_unreg_notifier(&bcl_perph->psy_nb);
flush_work(&bcl_perph->soc_eval_work);
 if (bcl_perph->tz_dev)
thermal_zone_of_sensor_unregister(&pdev->dev,
bcl_perph->tz_dev);
 return 0;
}
static int bcl_soc_probe(struct platform_device *pdev)
{
 int ret = 0;
 //申請(qǐng)內(nèi)存空間, 當(dāng)設(shè)備被拆卸或者驅(qū)動(dòng)程序卸載時(shí),內(nèi)存會(huì)被自動(dòng)釋放
bcl_perph = devm_kzalloc(&pdev->dev, sizeof(*bcl_perph), GFP_KERNEL);
 if (!bcl_perph)
 return -ENOMEM;
mutex_init(&bcl_perph->state_trans_lock);
 // 指向get_temp、set_trips函數(shù)
bcl_perph->ops.get_temp = bcl_read_soc;
bcl_perph->ops.set_trips = bcl_set_soc;
 // 定義初始化工作隊(duì)列
INIT_WORK(&bcl_perph->soc_eval_work, bcl_evaluate_soc);
 // 回調(diào)函數(shù)
 bcl_perph->psy_nb.notifier_call
= battery_supply_callback;
 //注冊(cè)監(jiān)聽接口,系統(tǒng)任何PSY設(shè)備的狀態(tài)發(fā)生改變,并調(diào)用了power_supply_changed接口,power
supply core就通知notifier的監(jiān)聽者。
 ret =
power_supply_reg_notifier(&bcl_perph->psy_nb);
 if (ret < 0) {
pr_err("soc notifier registration error. defer.
err:%d
",
ret);
 ret
= -EPROBE_DEFER;
 goto bcl_soc_probe_exit;
 }
 // 向thermal
zone注冊(cè)sensor
bcl_perph->tz_dev =
thermal_zone_of_sensor_register(&pdev->dev,
0, bcl_perph,
&bcl_perph->ops);
 if (IS_ERR(bcl_perph->tz_dev)) {
pr_err("soc TZ register failed. err:%ld
",
PTR_ERR(bcl_perph->tz_dev));
 ret
= PTR_ERR(bcl_perph->tz_dev);
bcl_perph->tz_dev = NULL;
 goto bcl_soc_probe_exit;
 }
thermal_zone_device_update(bcl_perph->tz_dev, THERMAL_DEVICE_UP);
 // 將soc_eval_work添加到默認(rèn)的工作隊(duì)列
schedule_work(&bcl_perph->soc_eval_work);
 // 設(shè)置driver
data的結(jié)構(gòu)體是bcl_perph
dev_set_drvdata(&pdev->dev, bcl_perph);
 return 0;
bcl_soc_probe_exit:
bcl_soc_remove(pdev);
 return ret;
}
//在dtsi中匹配.compatible
= "qcom,msm-bcl-soc"的sensor,可以多個(gè)
static const struct of_device_idbcl_match[]
= {
 {
.compatible = "qcom,msm-bcl-soc",
 },
 {},
};
static struct platform_driver bcl_driver= {
.probe  = bcl_soc_probe,
 .remove
= bcl_soc_remove,
 .driver
= {
.name           = BCL_DRIVER_NAME,
.owner          = THIS_MODULE,
.of_match_table = bcl_match,
 },
};
builtin_platform_driver(bcl_driver);
提供給sensor driver去調(diào)用的API接口 
// 向thermal zone注冊(cè)sensor,通過data傳入sensor_data
struct thermal_zone_device *
thermal_zone_of_sensor_register(struct device *dev, int sensor_id, void*data,
const struct
thermal_zone_of_device_ops *ops)
{
 struct device_node *np, *child, *sensor_np;
 struct thermal_zone_device *tzd = ERR_PTR(-ENODEV);
 np =
of_find_node_by_name(NULL, "thermal-zones");
 if (!np)
 return ERR_PTR(-ENODEV);
 if (!dev || !dev->of_node) {
of_node_put(np);
 return ERR_PTR(-ENODEV);
 }
 sensor_np
= of_node_get(dev->of_node);
for_each_available_child_of_node(np, child) {
 int ret, id;
 // //解析dtsi中thermal-sensors節(jié)點(diǎn)
 ret
= thermal_zone_of_get_sensor_id(child, sensor_np, &id);
 if (ret)
continue;
 if (id == sensor_id) {
// 在thermal zone中綁定sensor
tzd = thermal_zone_of_add_sensor(child, sensor_np,
 data, ops);
if (!IS_ERR(tzd))
thermal_zone_device_enable(tzd);
of_node_put(child);
goto exit;
 }
 }
exit:
of_node_put(sensor_np);
of_node_put(np);
 return tzd;
}
/***sensor API   ***/
// 在thermal zone中綁定sensor
static struct
thermal_zone_device *
thermal_zone_of_add_sensor(struct device_node *zone,
struct device_node *sensor, void *data,
const struct
thermal_zone_of_device_ops *ops)
{
 struct thermal_zone_device *tzd;
 struct __thermal_zone *tz;
 // 獲取當(dāng)前的thermal
zone
 tzd =
thermal_zone_get_zone_by_name(zone->name);
 if (IS_ERR(tzd))
 return ERR_PTR(-EPROBE_DEFER);
 tz =
tzd->devdata;
 if (!ops)
 return ERR_PTR(-EINVAL);
mutex_lock(&tzd->lock);
 // 綁定ops
 tz->ops
= ops;
 // 綁定sensor_data
tz->sensor_data = data;
 // 綁定sensor中實(shí)現(xiàn)的get_temp、get_trend
tzd->ops->get_temp = of_thermal_get_temp;
tzd->ops->get_trend = of_thermal_get_trend;
 /*
 * The
thermal zone core will calculate the window if they have set the
 *
optional set_trips pointer.
 */
 if (ops->set_trips)
tzd->ops->set_trips = of_thermal_set_trips;
 if (ops->set_emul_temp)
tzd->ops->set_emul_temp = of_thermal_set_emul_temp;
mutex_unlock(&tzd->lock);
 return tzd;
}

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 傳感器
    +關(guān)注

    關(guān)注

    2545

    文章

    50445

    瀏覽量

    751053
  • 框架
    +關(guān)注

    關(guān)注

    0

    文章

    398

    瀏覽量

    17404
  • 程序
    +關(guān)注

    關(guān)注

    116

    文章

    3756

    瀏覽量

    80751
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    632

    瀏覽量

    29110
  • Thermal
    +關(guān)注

    關(guān)注

    0

    文章

    8

    瀏覽量

    7360

原文標(biāo)題:萬字長(zhǎng)文 | Thermal框架源碼剖析

文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    STL源碼剖析中的,這個(gè)new是什么用法?這個(gè)函數(shù)的作用是?

    invoke copy constructor of T1}STL源碼剖析中的,這個(gè)new是什么用法?這個(gè)函數(shù)是用來干什么的
    發(fā)表于 03-21 10:47

    Spring框架的設(shè)計(jì)理念

    Spring作為現(xiàn)在最優(yōu)秀的框架之一,已被廣泛的使用,51CTO也曾經(jīng)針對(duì)Spring框架中的hqC應(yīng)用做過報(bào)道。本文將從另外一個(gè)視角試圖剖析出Spring框架的作者設(shè)計(jì)Spring
    發(fā)表于 07-15 08:17

    Redux設(shè)計(jì)思想與源碼解析

    Redux源碼剖析及應(yīng)用
    發(fā)表于 08-02 08:19

    LiteOS通信模組教程04-深度剖析LiteOS的AT框架

    :CONFIG_AT_DEVICENAME由用戶指定,不重復(fù)即可,在iot_link_config.h文件中,稍后會(huì)講解。3. 剖析AT客戶端框架AT客戶端框架的實(shí)現(xiàn)源碼在SDK的Io
    發(fā)表于 02-26 09:03

    Thermal Characterization of Pa

    Thermal Characterization of Packaged Semiconductor Devices:With the continuing industry trends
    發(fā)表于 11-29 17:16 ?22次下載

    Thermal Considerations

    Thermal Considerations:Thermal management is an important part of the system design process.
    發(fā)表于 11-29 17:16 ?13次下載

    THERMAL DESIGN OF POWER MOSFET

    THERMAL DESIGN OF POWER MOSFETS OPERATING IN PARALLEL The objective of this paper is the thermal
    發(fā)表于 11-29 17:17 ?25次下載

    LDO Thermal Calculations

    LDO Thermal Calculations AgendaR26; Thermal parameters standards terminology and definitionsR26; Suitable packages for g
    發(fā)表于 04-16 10:59 ?26次下載

    Thermal Management Handbook

    Thermal Ma
    發(fā)表于 05-06 17:58 ?17次下載
    <b class='flag-5'>Thermal</b> Management Handbook

    LDO Thermal Calculations

    AgendaR26; Thermal parameters standards terminology and definitionsR26; Suitable packages for good
    發(fā)表于 07-30 09:39 ?14次下載

    Thermal Protection in Low-Cost

    Thermal Pr
    發(fā)表于 04-18 10:35 ?1301次閱讀
    <b class='flag-5'>Thermal</b> Protection in Low-Cost

    Add Thermal Monitoring to Redu

    Add Thermal Monitoring to Reduce Data Center Energy Consumption Abstract: Precise and adaptable
    發(fā)表于 05-29 11:01 ?762次閱讀
    Add <b class='flag-5'>Thermal</b> Monitoring to Redu

    STL源碼剖析的PDF電子書免費(fèi)下載

    學(xué)習(xí)編程的人都知道,閱讀、剖析名家代碼乃是提高水平的捷徑。源碼之前,了無秘密。大師們的縝密思維、經(jīng)驗(yàn)結(jié)晶、技術(shù)思路、獨(dú)到風(fēng)格,都原原本本體現(xiàn)在源碼之中。
    發(fā)表于 06-29 08:00 ?0次下載
    STL<b class='flag-5'>源碼</b><b class='flag-5'>剖析</b>的PDF電子書免費(fèi)下載

    基于STM32移植UCGUI圖形界面框架(3.9.0源碼版本)

    基于STM32移植UCGUI圖形界面框架(3.9.0源碼版本)
    發(fā)表于 11-30 16:06 ?0次下載
    基于STM32移植UCGUI圖形界面<b class='flag-5'>框架</b>(3.9.0<b class='flag-5'>源碼</b>版本)

    存放OpenHarmony驅(qū)動(dòng)子系統(tǒng)源碼信息的HDF驅(qū)動(dòng)框架

    簡(jiǎn)介 該倉(cāng)主要存放OpenHarmony驅(qū)動(dòng)子系統(tǒng)核心源碼信息(包括驅(qū)動(dòng)框架、配置管理、配置解析、驅(qū)動(dòng)通用框架模型、硬件通用平臺(tái)能力接口等),旨在為開發(fā)者提供更精準(zhǔn)、更高效的開發(fā)環(huán)境,力求做到一次開發(fā),多系統(tǒng)部署。 圖1 驅(qū)動(dòng)
    發(fā)表于 04-13 11:13 ?8次下載
    存放OpenHarmony驅(qū)動(dòng)子系統(tǒng)<b class='flag-5'>源碼</b>信息的HDF驅(qū)動(dòng)<b class='flag-5'>框架</b>