Linux Thermal机制源码分析之Governor

一、thermal_init()

       在开始源码分析之前,需要先说明一下。Linux 内核代码庞大而复杂,如何 reading the Fxxking source code 相信是很多从事 Linux 内核/驱动开发人员非常关注也一直苦恼的事情,尤其是初涉内核开发的同行。本文在剖析 Thermal 机制的同时,也试图向读者展示作者如何来分析内核源码,以期读者可以借鉴一二。当然,由于本人能力有限,有错误的地方也请读者朋友不吝指正。同时,如果有更好的源码分析方法,也请各位不吝赐教,在此感激不尽。

       分析当然是从 thermal 驱动模块的入口开始(thermal_core.c 里):

static int __init thermal_init(void)
{
	int result;

	/* 重点分析:向thermal core注册governor */
	result = thermal_register_governors();
	if (result)
		goto error;

	/* 向内核注册thermal class: /sys/class/thermal/... */
	result = class_register(&thermal_class);
	if (result)
		goto unregister_governors;

	/* netlink机制,猜测应该是用于用户空间和内核空间通信的 */
	result = genetlink_init();
	if (result)
		goto unregister_class;

	/* 重点分析:解析dts里的thermal_zones */
	result = of_parse_thermal_zones();
	if (result)
		goto exit_netlink;

	/* notifier机制,对pm事件感兴趣 */
	result = register_pm_notifier(&thermal_pm_nb);
	if (result)
		pr_warn("Thermal: Can not register suspend notifier, return %d\n",
			result);

	return 0;

exit_netlink:
	genetlink_exit();
unregister_class:
	class_unregister(&thermal_class);
unregister_governors:
	thermal_unregister_governors();
error:
	idr_destroy(&thermal_tz_idr);
	idr_destroy(&thermal_cdev_idr);
	mutex_destroy(&thermal_idr_lock);
	mutex_destroy(&thermal_list_lock);
	mutex_destroy(&thermal_governor_lock);
	return result;
}

static void __exit thermal_exit(void)
{
	unregister_pm_notifier(&thermal_pm_nb);
	of_thermal_destroy_zones();
	genetlink_exit();
	class_unregister(&thermal_class);
	thermal_unregister_governors();
	idr_destroy(&thermal_tz_idr);
	idr_destroy(&thermal_cdev_idr);
	mutex_destroy(&thermal_idr_lock);
	mutex_destroy(&thermal_list_lock);
	mutex_destroy(&thermal_governor_lock);
}

/* thermal驱动模块会早于tsadc驱动加载 */
fs_initcall(thermal_init);
module_exit(thermal_exit);

thermal 模块加载进内核用的 fs_initcall(),tsadc 驱动一般用的是 module_init(),前者会早于后者加载,这点比较重要,有些代码流程上会依赖这种先后关系,需要留意。thermal_init() 函数里比较简单,先后调用了 5 个函数:

1、thermal_register_governors():注册 governor,这个函数会重点分析。

2、class_register():注册 thermal class,这个函数不会跟进去分析。class 框架性代码,大概知道是干什么用的即可,详细分析对当前所要学习的 Thermal 框架没有太大帮助。

3、genetlink_init():不太了解,百度查了下,好像是用于用户空间和内核空间通信的,先放一放(从后面的分析来看,netlink 机制不了解也不影响分析 Thermal 框架)。

4、of_parse_thermal_zones():解析 dts 里面的 thermal_zones,这个是重中之重。

5、register_pm_notifier():notifier 机制,表明 Thermal core 对 pm 事件感兴趣。

       在开始分析 thermal_register_governors() 和 of_parse_thermal_zones() 之前,先扫清一下比较简单的 register_pm_notifier()。Notifier 机制属于内核基础设施,本文就不展开分析了,以后会开专文来分析,网上也有相关文章。注意传进去的参数是 thermal_pm_nb 的地址,thermal_pm_nb 必须要提供 notifier_call 回调:

static struct notifier_block thermal_pm_nb = {
        .notifier_call = thermal_pm_notify,
};

系统休眠唤醒时,会去调用这个回调:

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

	switch (mode) {
	case PM_HIBERNATION_PREPARE:
	case PM_RESTORE_PREPARE:
	case PM_SUSPEND_PREPARE:
		/* 原子变量,标记suspend事件 */
		atomic_set(&in_suspend, 1);
		break;
	case PM_POST_HIBERNATION:
	case PM_POST_RESTORE:
	case PM_POST_SUSPEND:
		atomic_set(&in_suspend, 0);
		list_for_each_entry(tz, &thermal_tz_list, node) {
			thermal_zone_device_reset(tz);
			thermal_zone_device_update(tz);
		}
		break;
	default:
		break;
	}
	return 0;
}

case 的这几个宏定义如下:

/* Hibernation(冬眠) and suspend events */
#define PM_HIBERNATION_PREPARE   0x0001 /* Going to hibernate */
#define PM_POST_HIBERNATION      0x0002 /* Hibernation finished */
#define PM_SUSPEND_PREPARE       0x0003 /* Going to suspend the system */
#define PM_POST_SUSPEND          0x0004 /* Suspend finished */
#define PM_RESTORE_PREPARE       0x0005 /* Going to restore a saved image */
#define PM_POST_RESTORE          0x0006 /* Restore failed */

每个宏的具体含义需要详细了解 PM 相关代码才能知道了,但是可以知道的是 PM_xxx_PREPARE 宏表明系统 suspend 了,PM_POST_xxx 宏表明系统 resume 了。in_suspend 是一个静态的原子变量:

static atomic_t in_suspend;

原子变量主要用于同步机制,也是内核的基础设施之一,这里不展开了。可以看到,函数 thermal_pm_nb 做的事情很简单:

1、系统 suspend 的时候,标记 in_suspend 为 1;

2、系统 resume 的时候,标记 in_suspend 为 0,并调用 thermal core 提供的复位和更新函数,因为我们还没开始整个 Thermal 框架的代码分析,所以这两个函数在这里先不展开,后面会再次碰到,尤其是 update 函数,还非常重要。

既然对 in_suspend 做了标记,那肯定是在什么地方要用到,好在 in_suspend 是静态全局的,所以很自然地搜索了整个 thermal_core.c 文件,发现只在一个地方用到了:

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

	if (atomic_read(&in_suspend))
		return;

	...
}
EXPORT_SYMBOL_GPL(thermal_zone_device_update);

是的,就是这个 update 函数,为了只关注 in_suspend 的代码逻辑,我删掉了 update 函数后面的内容。这样一看,in_suspend 的作用就很明显了:如果系统 suspend 了,那么就不 update 了,直接返回;否则正常走 update 流程。

二、thermal_register_governors()

       这个部分是本篇博文的核心:governor 注册。让我们看看 governor 到底是个什么东东:

static int __init thermal_register_governors(void)
{
	int result;

	result = thermal_gov_step_wise_register();
	if (result)
		return result;

	result = thermal_gov_fair_share_register();
	if (result)
		return result;

	result = thermal_gov_bang_bang_register();
	if (result)
		return result;

	result = thermal_gov_user_space_register();
	if (result)
		return result;

	return thermal_gov_power_allocator_register();
}

可以看到,5 种 governor 依次注册,由于 RK 平台默认使用 power allocator,我们以此为例来分析:

static struct thermal_governor thermal_gov_power_allocator = {
	.name		= "power_allocator",
	.bind_to_tz	= power_allocator_bind,
	.unbind_from_tz	= power_allocator_unbind,
	.throttle	= power_allocator_throttle,
};

int thermal_gov_power_allocator_register(void)
{
	return thermal_register_governor(&thermal_gov_power_allocator);
}

再次调用 thermal_register_governor() 这个通用接口,之所以说是通用,是因为另外 4 个 governor 也都是用的这个函数,只不过各自的传参不一样。传参过来的结构体就是上节介绍的 struct thermal_governor,提供了三个回调,回调后面再来分析。跟着代码流程先分析 thermal_register_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;
	/*
	 * 在全局链表thermal_governor_list里查找现在要注册的
	 * thermal_governor是否已经存在了,通过name来判断存在与否
	 */
	if (__find_governor(governor->name) == NULL) {
		err = 0;
		/* 如果没有找到,就把当前的thermal_governor加入该全局链表 */
		list_add(&governor->governor_list, &thermal_governor_list);
		/*
		 * 全局变量def_governor的处理
		 * 如果def_governor指针为空,并且当前的thermal_governor
		 * 的名字和DEFAULT_THERMAL_GOVERNOR一致,那么就赋值给
		 * def_governor
		 */
		if (!def_governor && !strncmp(governor->name,
			DEFAULT_THERMAL_GOVERNOR, THERMAL_NAME_LENGTH))
			def_governor = governor;
	}

	/* 也是静态初始化的 */
	mutex_lock(&thermal_list_lock);

	/* 此时链表thermal_tz_list下其实并没有成员 */
	list_for_each_entry(pos, &thermal_tz_list, node) {
		/*
		 * only thermal zones with specified tz->tzp->governor_name
		 * may run with tz->governor unset
		 */
		if (pos->governor)
			continue;

		name = pos->tzp->governor_name;

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

			/*
			 * name匹配,thermal_zone_device和当前governor绑定
			 * 这个函数暂时就不进去了,后面会再次碰到,到时再详细分析
			 */
			ret = thermal_set_governor(pos, governor);
			if (ret)
				dev_err(&pos->device,
					"Failed to set governor %s for thermal zone %s: %d\n",
					governor->name, pos->type, ret);
		}
	}

	mutex_unlock(&thermal_list_lock);
	mutex_unlock(&thermal_governor_lock);

	return err;
}

       首先是安全性检查,确保传进来的 governor 是非空指针。然后使用了 thermal_governor_lock 互斥锁,互斥锁也是内核同步机制之一,不做展开介绍。有两点说明:

1、这是一个已经静态初始化的全局变量,所以这里可以直接使用 mutex_lock 对临界区上锁,初始化代码如下(thermal_core.c):

static DEFINE_MUTEX(thermal_governor_lock);

2、见名知意,thermal_governor_lock 主要用于保证全局链表 thermal_governor_list 的增删操作有序进行,具体到这里的代码上下文,就是 list_add()。

       __find_governor() 上方的注释说明了这个函数的作用。让我们看看源码:

static struct thermal_governor *__find_governor(const char *name)
{
	struct thermal_governor *pos;

	/* name不能为空,否则返回默认的governor */
	if (!name || !name[0])
		return def_governor;

	/* 遍历全局链表,通过name来找到目标governor */
	list_for_each_entry(pos, &thermal_governor_list, governor_list)
		if (!strncasecmp(name, pos->name, THERMAL_NAME_LENGTH))
			return pos;

	/* 没有找到就返回空指针 */
	return NULL;
}

可以看到 5 个 governor 都会进入 if 代码块里面,里面做了两件事情,注释已经说明的很清楚了,不再赘述。默认的 governor 可以进入 menuconfig 里进行选择,RK 平台选择的是 power allocator。DEFAULT_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

       接着往下分析,thermal_list_lock,又是互斥锁,访问全局链表 thermal_tz_list 时都需要上锁,并要约定好。thermal_tz_list 链表串起来的是 struct thermal_zone_device 结构体,关于 thermal_zone_device 后面会专文来分析。事实上,代码跑到这里时链表 thermal_tz_list 元素为空,所以不会跑到里面。里面调用的 thermal_set_governor() 函数在 thermal_zone_device 代码分析时会再次碰到,届时再具体分析。最后,关于两个互斥锁的情况需要留意,因为 AB-BA 死锁问题还是很常见的,所以自己在编写驱动程序时要谨慎处理这种情况。

       至此,governor 的注册流程分析完了,但是 governor 的角色还没有开始发挥作用,后面会再次登场,让我们拭目以待。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值