Linux hwmon子系统分析2(基于Linux6.6)---hwmon driver开发实践介绍
一、hwmon 子系统的框架
hwmon子系统的架构如下,主要借助sysfs文件系统及相关接口、设备驱动模型中class、device相关的接口,实现了针对hwmon芯片各主要参数的访问。如下图所示,针对hwmon子系统驱动而言,需要做的内容如下:
- 针对每一个hwmon芯片的参数,均实现struct sensor_device_attribute或sensor_device_attribute类型的变量(包括index、struct device_attribute类型变量(主要实现读写访问接口))
二、hwmon driver开发流程说明
针对hwmon driver的开发流程,其实现步骤大致如下:
- 为该hwmon的主要参数(如温度传感器的当前温度、最大温度、最小温度、最大报警温度、最小报警温度等),均实现struct sensor_device_attribute或sensor_device_attribute类型的变量,并提供针对该参数的读写方法(show、store);
- 调用devm_hwmon_device_register_with_groups,完成hwmon device的注册。
三、实现一个虚拟的温度传感器驱动
在 Linux hwmon
驱动开发过程中,主要的步骤是为硬件监控设备(如温度传感器、风扇转速、功率等)提供相应的参数、实现 show
和 store
函数来读写这些参数,并通过 devm_hwmon_device_register_with_groups()
函数注册设备及其属性。以下是详细的开发流程和实现步骤。
1. 定义硬件监控设备的参数
首先,需要定义硬件监控设备的相关参数,例如温度、最大温度、最小温度、最大报警温度、最小报警温度等。这些参数通常会使用 sensor_device_attribute
结构体进行定义,sensor_device_attribute
结构体包含了属性的名称、读取和写入方法。
#include <linux/hwmon.h>
#include <linux/sysfs.h>
struct sensor_device_attribute my_temp_attr[] = {
SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, temp_show, NULL, 0), // 读操作
SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, temp_show, temp_store, 1), // 读写操作
SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, temp_alarm_show, NULL, 1), // 只读报警
SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, temp_show, temp_store, 2), // 读写操作
SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, temp_alarm_show, NULL, 2), // 只读报警
};
2. 实现属性的 show
和 store
函数
每个 sensor_device_attribute
结构体都需要对应一个 show
和可选的 store
函数。show
函数用于读取硬件传感器的数据,而 store
函数则用于设置硬件的相关参数。
例如,读取温度的 show
函数和设置温度的 store
函数可以如下实现:
static ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int temp = read_temperature_from_sensor(); // 读取硬件传感器的温度值
return snprintf(buf, PAGE_SIZE, "%d\n", temp); // 返回温度值
}
static ssize_t temp_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int temp;
if (sscanf(buf, "%d", &temp) == 1) {
set_temperature_on_sensor(temp); // 设置硬件传感器的温度值
return count;
}
return -EINVAL;
}
在这个例子中,temp_show
用于读取温度传感器的数据,temp_store
用于设置温度阈值。
3. 注册硬件监控设备
当所有的属性和对应的操作函数都实现完毕后,需要将该设备注册到 hwmon
子系统,并将属性与设备进行绑定。可以通过 devm_hwmon_device_register_with_groups()
函数来完成设备的注册。
devm_hwmon_device_register_with_groups()
函数会将设备与相关的属性组(由 sensor_device_attribute
数组组成)进行绑定,并将设备注册到 hwmon
子系统中。
static int my_hwmon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int err;
// 注册 hwmon 设备,并将属性组与设备关联
err = devm_hwmon_device_register_with_groups(dev, "my_hwmon", NULL, my_temp_attr);
if (err) {
dev_err(dev, "Failed to register hwmon device\n");
return err;
}
return 0;
}
4. 注册硬件监控设备的属性
当设备被注册时,内核会为该设备自动创建一个 /sys/class/hwmon/
目录,并将所有的属性文件(例如 temp1_input
, temp1_max
, temp1_min
等)链接到该目录中。用户空间程序可以通过读取这些文件来获取硬件的监控数据,也可以通过写这些文件来设置硬件的某些参数。
5. 硬件监控设备卸载
当硬件设备不再需要时,应该卸载相应的驱动并清理资源。这个过程通常在 remove
函数中进行:
static int my_hwmon_remove(struct platform_device *pdev)
{
// 无需显式注销属性,devm_hwmon_device_register_with_groups 会自动管理
return 0;
}
6. 示例代码总结
以下是完整的示例代码,展示了如何为一个简单的温度传感器编写 hwmon
驱动。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
static struct class *hwmon_class;
static struct device *hwmon_device;
static int current_temperature = 25000; // 假设温度为 25.000°C(单位是毫度)
// sysfs 属性:用于读取温度
static ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", current_temperature);
}
// sysfs 属性:用于设置温度阈值
static ssize_t temp_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int temp;
if (sscanf(buf, "%d", &temp) == 1) {
current_temperature = temp; // 设置新的温度
return count;
}
return -EINVAL;
}
// 创建一个 sysfs 属性:temp1_input
static DEVICE_ATTR(temp1_input, S_IRUGO, temp_show, NULL);
static DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, temp_show, temp_store);
static struct attribute *attrs[] = {
&dev_attr_temp1_input.attr,
&dev_attr_temp1_max.attr,
NULL,
};
static struct attribute_group attr_group = {
.attrs = attrs,
};
// 驱动的 probe 和 remove 函数
static int my_hwmon_probe(struct platform_device *pdev)
{
int ret;
// 创建 sysfs 接口
hwmon_device = device_create(hwmon_class, &pdev->dev, 0, NULL, "hwmon_device");
if (IS_ERR(hwmon_device)) {
ret = PTR_ERR(hwmon_device);
pr_err("Failed to create device: %ld\n", ret);
return ret;
}
// 创建并注册 sysfs 属性组
ret = sysfs_create_group(&hwmon_device->kobj, &attr_group);
if (ret) {
pr_err("Failed to create sysfs group\n");
device_unregister(hwmon_device);
return ret;
}
pr_info("HWMon driver probed successfully\n");
return 0;
}
static int my_hwmon_remove(struct platform_device *pdev)
{
// 删除 sysfs 属性组
sysfs_remove_group(&hwmon_device->kobj, &attr_group);
// 注销设备
device_unregister(hwmon_device);
pr_info("HWMon driver removed\n");
return 0;
}
// 设备匹配表
static const struct of_device_id my_hwmon_of_match[] = {
{ .compatible = "myvendor,mytemperature", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, my_hwmon_of_match);
// 平台驱动结构体
static struct platform_driver my_hwmon_driver = {
.probe = my_hwmon_probe,
.remove = my_hwmon_remove,
.driver = {
.name = "my_hwmon",
.of_match_table = my_hwmon_of_match,
},
};
module_platform_driver(my_hwmon_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("A simple temperature sensor driver using sysfs");