在内核中,为了对硬件设备进行监视,内核提供了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温度监控的分析。