CPU Temp hardware monitor

令狐跃
2023-12-01

在内核中,为了对硬件设备进行监视,内核提供了hwmon(hardware monitor)方法,来方便对硬件设备的监视。

比如:为了对处理器的温度进行监控,可以利用该方法来完成。

static int __init xxx_hwmon_init(void)
{
	int ret;
	pr_info("xxx Hwmon Enter...\n");
	
	if (cpu_has_csr())
		csr_temp_enable = csr_readl(XXX_CSR_FEATURE) & XXX_CSRF_TEMP;

	cpu_hwmon_dev = hwmon_device_register(NULL);
	//该函数用来注册并返回cpu_hwmon_dev
	if (IS_ERR(cpu_hwmon_dev)) {
		ret = PTR_ERR(cpu_hwmon_dev);
		pr_err("hwmon_device_register fail!\n");
		goto fail_hwmon_device_register;
	}
	...
}

关于cpu_has_csr,该函数与处理器的结构相关,这里对其进行解析:

static inline bool cpu_has_csr(void)
{
	if (cpu_has_cfg())
	//检查处理中是否存在相关的配置寄存器
		return (read_cpucfg(XXX_CFG2) & XXX_CFG2_LCSRP);
		//读取处理器中的相关寄存器的配置

	return false;
}

static inline bool cpu_has_cfg(void)
{
	return ((read_c0_prid() & PRID_IMP_MASK) == PRID_IMP_XXX_64G);
}

#define read_c0_prid()		__read_const_32bit_c0_register($15, 0)

#define __read_const_32bit_c0_register(source, sel)			\
	___read_32bit_c0_register(source, sel,)

#define ___read_32bit_c0_register(source, sel, vol)			\
({ unsigned int __res;							\
	if (sel == 0)							\
		__asm__ vol(						\
			"mfc0\t%0, " #source "\n\t"			\
			: "=r" (__res));				\
	else								\
		__asm__ vol(						\
			".set\tpush\n\t"				\
			".set\tmips32\n\t"				\
			"mfc0\t%0, " #source ", " #sel "\n\t"		\
			".set\tpop\n\t"					\
			: "=r" (__res));				\
	__res;								\
})

通过上述代码可以看出,在sel == 0的情况下,通过指令mfc将$15(即15号寄存器,CP0)中的内容写入res变量中,随后将改变量与PRID_IMP_MASK宏进行匹配,判断res变量中是否包含PRID_IMP_XXX_64G。这里,分析的处理器架构为MIPS架构。

CP0寄存器的结构如下:
±-----------------------±-----------------±-----------------±-----------+
| Company Options | Company ID | Processor ID | Revision |
±-----------------------±-----------------±-----------------±-----------+
31 24 23 16 15 8 7 0

#define PRID_IMP_MASK 0Xff00
#define PRID_IMP_XXX_64G 0xc000

所以,cpu_has_cfg实际用来判断是否存在Company Options。

关于另一个与处理器结构相关的read_cpucfg函数,其原型如下:

static inline u32 read_cpucfg(u32 reg)
{
	u32 __res;

	__asm__ __volatile__(
		"parse_r __res,%0\n\t"
		"parse_r reg,%1\n\t"
		".insn \n\t"
		".word (0xc8080118 | (reg << 21) | (__res << 11))\n\t"
		:"=r"(__res)
		:"r"(reg)
		:
		);
	return __res;
}

#define XXX_CFG2 0x2
#define XXX_CFG2_LCSRP	BIT(27)
#define BIT(nr)			(UL(1) << (nr))

综上分析,read_cpucfg()函数的执行结果为1。所以,csr_temp_enable为1。

hwmon_device_register()函数实际的执行者为__hwmon_device_register()。

static struct device *
__hwmon_device_register(struct device *dev, const char *name, void *drvdata,
			const struct hwmon_chip_info *chip,
			const struct attribute_group **groups)
{
	//在实际的代码中,这里传入的参数全部为NULL。因此,关于部分分支条件可以暂且忽略。
	
	struct hwmon_device *hwdev;
	struct device *hdev;
	int i, j, err, id;
	
	//检查name变量的长度以及是否包含字符“-”。
	if (name && (!strlen(name) || strpbrk(name, "-* \t\n")))
		dev_warn(dev,
			 "hwmon: '%s' is not a valid name attribute, please fix\n",
			 name);
	
	//获取id编号
	id = ida_simple_get(&hwmon_ida, 0, 0, GFP_KERNEL);
	if (id < 0)
		return ERR_PTR(id);

	//申请hwmon_device结构体,并将其内容清零。
	hwdev = kzalloc(sizeof(*hwdev), GFP_KERNEL);
	if (hwdev == NULL) {
		err = -ENOMEM;
		goto ida_remove;
	}
	//对hwdev以及hdev结构体对象进行初始化赋值。
	hdev = &hwdev->dev;

	if (chip) {
		...
	} else {
		hdev->groups = groups;
	}
	...
	hwdev->name = name;
	hdev->class = &hwmon_class;
	hdev->parent = dev;
	hdev->of_node = dev ? dev->of_node : NULL;
	hwdev->chip = chip;

	//hdev->driver_data = drvdata;
	dev_set_drvdata(hdev, drvdata);

	//sprintf(hdev->kobj->name, ”hwmon%d“,id);
	dev_set_name(hdev, HWMON_ID_FORMAT, id);

	//注册hdev设备。该函数首先对device结构体对象hdev进行初始化(device_initialize(dev)),随后添加该结构体(device_add(dev))。
	err = device_register(hdev);
	if (err)
		goto free_hwmon;

	return hdev;

free_hwmon:
	hwmon_dev_release(hdev);
ida_remove:
	ida_simple_remove(&hwmon_ida, id);
	return ERR_PTR(err);
}

关于device_register函数,其内部实现的内容较多且复杂,这里只对设备的属性来进行分析,即device_add_attrs()函数。

//添加与设备相关的属性文件。
static int device_add_attrs(struct device *dev)
//这里所传入的参数实际为上边代码中的hdev
{
	//对hdev的初始化过程中,hdev->class = &hwmon_class。
	struct class *class = dev->class;
	...
	if (class) {
		//参数实际分别为dev=》hdev, class->dev_groups=》hwmon_dev_attr_groups。
		//该函数接下来会遍历hwmon_dev_attr_groups来执行internal_create_group()函数。
		error = device_add_groups(dev, class->dev_groups);
		if (error)
			return error;
	}
}

//hwmon_dev_attr_groups的定义如下:
static const struct attribute_group *hwmon_dev_attr_groups[] = {
	&hwmon_dev_attr_group,
	NULL
};

static const struct attribute_group hwmon_dev_attr_group = {
	.attrs		= hwmon_dev_attrs,
	.is_visible	= hwmon_dev_name_is_visible,
};

static struct attribute *hwmon_dev_attrs[] = {
	&dev_attr_name.attr,
	NULL
};

static int internal_create_groups(struct kobject *kobj, int update,
				  const struct attribute_group **groups)
{
	int error = 0;
	int i;

	if (!groups)
		return 0;

	for (i = 0; groups[i]; i++) {
		//通过上述结构体的声明,可以知道该循环中只执行一次,kobj=》hdev->kobj,update=》0, groups[i]=》hwmon_dev_attr_group
		//该函数执行create_files()函数来创建相关文件。
		error = internal_create_group(kobj, update, groups[i]);
		if (error) {
			while (--i >= 0)
				sysfs_remove_group(kobj, groups[i]);
			break;
		}
	}
	return error;
}

static int create_files(struct kernfs_node *parent, struct kobject *kobj,
			kuid_t uid, kgid_t gid,
			const struct attribute_group *grp, int update)
{
	struct attribute *const *attr;
	struct bin_attribute *const *bin_attr;
	int error = 0, i;
	
	//由上述结构体可以知道grp->attrs为真,因此进入该条件分支。
	if (grp->attrs) {
		//由前边可知,ARRAY_SIZE(grp->attrs)=1,所以该循环只遍历一次。
		for (i = 0, attr = grp->attrs; *attr && !error; i++, attr++) {
			umode_t mode = (*attr)->mode;
			
			//update此时为0,所以不执行该分支
			if (update)
				kernfs_remove_by_name(parent, (*attr)->name);

			//此时grp->is_visible为真,执行该分支。
			if (grp->is_visible) {
				//该函数通过kobj找到hdev,随后通过hdev找hwdev,并判断hwdev的name属性是否有值,如果无值则继续循环。
				//如果有值,则继续向下执行。
				mode = grp->is_visible(kobj, *attr, i);
				if (!mode)
					continue;
			}
			...
			//添加文件,该函数根据传入的第三个参数来选择执行相对应的条件分支。
			error = sysfs_add_file_mode_ns(parent, *attr, false,
						       mode, uid, gid, NULL);
			if (unlikely(error))
				break;
		}
		if (error) {
			remove_files(parent, grp);
			goto exit;
		}
	}

	if (grp->bin_attrs) {
		...
	}
exit:
	return error;
}

int sysfs_add_file_mode_ns(struct kernfs_node *parent,
			   const struct attribute *attr, bool is_bin,
			   umode_t mode, kuid_t uid, kgid_t gid, const void *ns)
{
	struct lock_class_key *key = NULL;
	const struct kernfs_ops *ops;
	struct kernfs_node *kn;
	loff_t size;
	//对sysfs_ops结构体进行赋值。
	if (!is_bin) {
		struct kobject *kobj = parent->priv;
		const struct sysfs_ops *sysfs_ops = kobj->ktype->sysfs_ops;
		
		if (WARN(!sysfs_ops, KERN_ERR
			 "missing sysfs attribute operations for kobject: %s\n",
			 kobject_name(kobj)))
			return -EINVAL;

		if (sysfs_ops->show && sysfs_ops->store) {
			if (mode & SYSFS_PREALLOC)
				ops = &sysfs_prealloc_kfops_rw;
			else
				ops = &sysfs_file_kfops_rw;
		} else if (sysfs_ops->show) {
			if (mode & SYSFS_PREALLOC)
				ops = &sysfs_prealloc_kfops_ro;
			else
				ops = &sysfs_file_kfops_ro;
		} else if (sysfs_ops->store) {
			if (mode & SYSFS_PREALLOC)
				ops = &sysfs_prealloc_kfops_wo;
			else
				ops = &sysfs_file_kfops_wo;
		} else
			ops = &sysfs_file_kfops_empty;

		size = PAGE_SIZE;
	} else {
		...
	}

#ifdef CONFIG_DEBUG_LOCK_ALLOC
	if (!attr->ignore_lockdep)
		key = attr->key ?: (struct lock_class_key *)&attr->skey;
#endif
	//创建文件。
	kn = __kernfs_create_file(parent, attr->name, mode & 0777, uid, gid,
				  size, ops, (void *)attr, ns, key);
	if (IS_ERR(kn)) {
		if (PTR_ERR(kn) == -EEXIST)
			sysfs_warn_dup(parent, attr->name);
		return PTR_ERR(kn);
	}
	return 0;
}

综上,便是处理器温度监控的必要的初始动作,而关于真正的温度监控动作,其执行如下:

static int __init loongson_hwmon_init(void)
{
	...
	INIT_DEFERRABLE_WORK(&thermal_work, do_thermal_timer);
	schedule_delayed_work(&thermal_work, msecs_to_jiffies(20000));
	...
}

static void do_thermal_timer(struct work_struct *work)
{
	int i, value, temp_max = 0;

	for (i = 0; i < nr_packages; i++) {
		value = xxx_cpu_temp(i);
		//读取cpu的温度
		if (value > temp_max)
			temp_max = value;
	}
	
	if (temp_max <= CPU_THERMAL_THRESHOLD)
		schedule_delayed_work(&thermale_work, msecs_to_jiffies(5000));
	else
		orderly_poweroff(true);
}

以上,便是对cpu温度监控的分析。

 类似资料:

相关阅读

相关文章

相关问答