2. Linux内核驱动基础回顾:字符设备驱动框架、platform驱动模型、设备树(DTS)基础
在开始Sensor驱动从ARM到RISC-V的移植适配之前,必须夯实Linux内核驱动的基础知识。本章聚焦三大核心主题:字符设备驱动框架、platform驱动模型以及设备树(DTS)基础。这三者构成了现代Linux驱动开发的基石,也是后续移植工作的核心操作对象。
学习提示:本章内容并非简单的理论回顾,而是为后续移植实战提供可直接对照的“参考坐标系”。请重点关注ARM与RISC-V平台在设备树描述和驱动匹配机制上的差异点。
2.1 字符设备驱动框架
字符设备是Linux驱动中最基础、最常用的设备类型,Sensor通常被抽象为字符设备。其核心框架围绕struct file_operations和struct cdev展开。
2.1.1 核心数据结构
| 数据结构 | 作用 | 关键成员 |
|---|---|---|
struct cdev |
内核中表示字符设备的对象 | owner, ops, dev |
struct file_operations |
定义设备支持的系统调用接口 | open, read, write, ioctl, release |
dev_t |
设备号(主设备号 + 次设备号) | MAJOR(), MINOR() |
2.1.2 标准注册流程
- 分配设备号:使用
alloc_chrdev_region()动态分配,或register_chrdev_region()静态指定。 - 初始化cdev:
cdev_init()绑定file_operations。 - 添加cdev:
cdev_add()将设备注册到内核。 - 创建设备节点:使用
class_create()和device_create()在/dev/下自动生成节点。
// 字符设备驱动核心骨架示例
static int __init sensor_drv_init(void)
{
dev_t dev_num;
struct class *sensor_class;
// 1. 分配设备号
alloc_chrdev_region(&dev_num, 0, 1, "sensor_dev");
// 2. 初始化cdev
cdev_init(&sensor_cdev, &sensor_fops);
sensor_cdev.owner = THIS_MODULE;
// 3. 添加cdev到内核
cdev_add(&sensor_cdev, dev_num, 1);
// 4. 创建设备节点
sensor_class = class_create(THIS_MODULE, "sensor_class");
device_create(sensor_class, NULL, dev_num, NULL, "sensor0");
return 0;
}
static void __exit sensor_drv_exit(void)
{
dev_t dev_num = sensor_cdev.dev;
// 反向清理:销毁设备节点、删除cdev、释放设备号
device_destroy(sensor_class, dev_num);
class_destroy(sensor_class);
cdev_del(&sensor_cdev);
unregister_chrdev_region(dev_num, 1);
}
module_init(sensor_drv_init);
module_exit(sensor_drv_exit);
移植关注点:字符设备框架本身是平台无关的,ARM和RISC-V上完全一致。但
ioctl命令码的构造(_IOR/_IOW)涉及sizeof和数据类型对齐,在64位RISC-V平台上需注意long类型大小与ARM32的差异。
2.2 platform驱动模型
platform驱动模型是Linux设备驱动模型的核心,用于管理那些不依赖PCI、USB等热插拔总线的设备(如SoC内部外设、Sensor)。它将设备与驱动分离,通过匹配机制进行绑定。
2.2.1 模型三要素
2.2.2 匹配方式
| 匹配方式 | 匹配依据 | 典型场景 |
|---|---|---|
| OF匹配 | 设备树中的compatible属性 |
现代ARM/RISC-V平台(主流方式) |
| ACPI匹配 | ACPI表HID/CID |
x86/ARM服务器 |
| ID表匹配 | platform_device.name vs id_table |
旧式平台或非设备树平台 |
| 名称回退 | 驱动名称与设备名称相同 | 兼容性回退机制 |
// platform_driver 定义示例(OF匹配方式)
static const struct of_device_id sensor_of_match[] = {
{ .compatible = "vendor,imu-sensor", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sensor_of_match);
static struct platform_driver sensor_platform_driver = {
.probe = sensor_probe,
.remove = sensor_remove,
.driver = {
.name = "sensor_imu",
.of_match_table = sensor_of_match,
.pm = &sensor_pm_ops,
},
};
module_platform_driver(sensor_platform_driver);
移植陷阱:ARM平台常使用
arch/arm/boot/dts/下的设备树,而RISC-V平台使用arch/riscv/boot/dts/。驱动中的compatible字符串必须与设备树中的定义完全一致,包括大小写和标点符号。移植时需检查RISC-V平台设备树中Sensor节点的compatible值。
2.3 设备树(DTS)基础
设备树(Device Tree)是一种描述硬件资源的数据结构,用于将硬件配置从内核代码中解耦。在ARM和RISC-V平台上,设备树是描述Sensor挂接位置、中断号、I2C/SPI总线地址等信息的标准方式。
2.3.1 设备树语法核心
// 设备树源文件(.dts)示例:I2C总线上的Sensor节点
/dts-v1/;
/ {
model = "RISC-V Sensor Board";
compatible = "vendor,riscv-sensor-board";
soc {
#address-cells = <1>;
#size-cells = <1>;
i2c0: i2c@10010000 {
compatible = "snps,designware-i2c";
reg = <0x10010000 0x1000>;
interrupts = <31>;
#address-cells = <1>;
#size-cells = <0>;
// Sensor子节点
imu_sensor: imu@68 {
compatible = "vendor,imu-sensor";
reg = <0x68>; // I2C从设备地址
interrupt-parent = <&gpio0>;
interrupts = <5 2>; // GPIO5, 下降沿触发
vdd-supply = <®_3v3>; // 电源供应
mount-matrix = "0", "1", "0",
"-1", "0", "0",
"0", "0", "1"; // 安装方向矩阵
};
};
};
};
2.3.2 关键属性说明
| 属性 | 含义 | Sensor驱动常用场景 |
|---|---|---|
compatible |
设备兼容性字符串,驱动匹配依据 | 必须与驱动of_match_table一致 |
reg |
寄存器地址或I2C/SPI从设备地址 | Sensor的I2C地址或SPI片选 |
interrupts |
中断号及触发类型 | Sensor数据就绪中断 |
xxx-supply |
电源 regulator 引用 | Sensor的VDD/VDDIO供电控制 |
pinctrl-* |
引脚复用配置 | I2C/SPI引脚功能选择 |
2.3.3 驱动中读取设备树信息
// 在probe函数中解析设备树属性
static int sensor_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct i2c_client *client;
u32 irq_num;
u32 reg_addr;
int ret;
// 1. 获取I2C从设备地址
ret = of_property_read_u32(np, "reg", ®_addr);
if (ret) {
dev_err(dev, "failed to get reg property\n");
return ret;
}
// 2. 获取中断号
irq_num = irq_of_parse_and_map(np, 0);
if (!irq_num) {
dev_err(dev, "failed to parse interrupt\n");
return -EINVAL;
}
// 3. 获取电源供应
struct regulator *vdd = devm_regulator_get(dev, "vdd");
if (IS_ERR(vdd)) {
dev_err(dev, "failed to get vdd regulator\n");
return PTR_ERR(vdd);
}
// 4. 获取自定义属性(如安装矩阵)
struct property *prop = of_find_property(np, "mount-matrix", NULL);
if (prop) {
// 解析安装方向矩阵
}
return 0;
}
2.3.4 ARM vs RISC-V 设备树差异
移植核心要点:Sensor驱动本身的设备树节点结构(compatible、reg、interrupts、xxx-supply)在ARM和RISC-V平台上基本一致。主要差异在于父节点(如I2C控制器、中断控制器、GPIO控制器)的compatible字符串和寄存器布局。移植时只需修改设备树包含的头文件路径和父节点引用,Sensor子节点通常无需改动。
2.4 三者的协同关系
字符设备框架、platform驱动模型和设备树在实际Sensor驱动中协同工作:
- 设备树描述Sensor的硬件连接和配置参数。
- platform驱动模型负责在设备树中查找匹配的Sensor节点,并触发
probe()。 - 字符设备框架在
probe()中注册,为用户空间提供open/read/ioctl接口。
// 三者协同的典型probe函数结构
static int sensor_probe(struct platform_device *pdev)
{
// 1. 从设备树获取资源(中断、地址、电源)
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
int irq = platform_get_irq(pdev, 0);
// 2. 初始化硬件(寄存器映射、复位、配置)
sensor->base = devm_ioremap_resource(&pdev->dev, res);
// 3. 注册字符设备(为用户空间提供接口)
alloc_chrdev_region(&sensor->dev_num, 0, 1, "sensor");
cdev_init(&sensor->cdev, &sensor_fops);
cdev_add(&sensor->cdev, sensor->dev_num, 1);
// 4. 创建sysfs设备节点
device_create(sensor_class, &pdev->dev, sensor->dev_num, NULL, "sensor%d", sensor->id);
return 0;
}
回顾总结:本章的三个知识点构成了Sensor驱动移植的“三脚架”。在后续章节中,我们将反复使用设备树解析、platform驱动匹配和字符设备注册这三个操作。请确保理解每个环节的作用,特别是设备树中
compatible字符串与驱动中of_match_table的对应关系——这是跨平台移植时最容易出错的地方。