1. Linux设备驱动联动机制深度解析
在Linux内核中,设备与驱动的联动机制是整个设备模型的核心。理解这一机制对于开发高质量的内核驱动至关重要。让我们从最基础的数据结构开始,逐步剖析这个复杂而精妙的系统。
1.1 核心数据结构关系
Linux内核通过一系列精心设计的数据结构来实现设备与驱动的绑定:
c复制/* 设备端结构 */
struct platform_device {
const char *name; // 设备名称
struct device dev; // 基础设备结构
struct resource *resource; // 设备资源
// ...
};
/* 驱动端结构 */
struct platform_driver {
int (*probe)(struct platform_device *); // 关键连接点!
struct device_driver driver; // 基础驱动结构
const struct platform_device_id *id_table; // 支持的设备ID
// ...
};
这两个结构体通过struct device和struct device_driver建立联系。其中最关键的是probe函数,它是驱动与设备成功匹配后的入口点。
1.2 总线匹配机制
总线在内核中扮演着"媒人"的角色:
c复制struct bus_type {
const char *name; // 总线名称
int (*match)(struct device *, struct device_driver *); // 匹配函数!
int (*probe)(struct device *);
int (*remove)(struct device *);
struct kset devices; // 设备集合
struct kset drivers; // 驱动集合
// ...
};
匹配过程通常包括以下步骤:
- 设备注册时被添加到总线的设备链表
- 驱动注册时被添加到总线的驱动链表
- 总线遍历驱动列表,调用match函数进行匹配
- 匹配成功后调用驱动的probe函数
提示:平台设备(platform device)使用platform_bus_type作为其总线类型,这是Linux为片上系统(SoC)设备设计的虚拟总线。
2. 设备与驱动绑定全流程
2.1 设备注册流程详解
设备注册的完整调用链如下:
c复制platform_device_register
→ device_add
→ bus_probe_device
→ device_attach
→ __device_attach
→ bus_for_each_drv
→ driver_match_device
→ bus->match(dev, drv) // 调用匹配函数!
→ 如果匹配成功 → driver_probe_device
→ really_probe
→ dev->driver = drv // 建立连接!
→ drv->probe(dev) // 调用驱动probe函数
2.2 驱动注册与匹配
驱动注册时,内核会尝试为它匹配未绑定的设备:
c复制static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
// 尝试多种匹配方式
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
return (strcmp(pdev->name, drv->name) == 0); // 名称匹配
}
匹配成功后,驱动的probe函数被调用:
c复制static int my_driver_probe(struct platform_device *pdev)
{
// 获取设备资源
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// 获取平台特定数据
struct my_device_platform_data *pdata = dev_get_platdata(&pdev->dev);
// 绑定驱动和设备
platform_set_drvdata(pdev, private_data);
return 0;
}
3. 多层次设备驱动联动实例
3.1 USB摄像头驱动架构
USB摄像头设备展现了Linux设备模型的层次化特性:
code复制USB摄像头 → usb_driver → video_driver → v4l2_device → /dev/video0
每一层都有自己的设备-驱动匹配机制:
c复制// USB层匹配
static int usb_vision_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
// 注册为视频设备
struct video_device *vdev = video_device_alloc();
video_register_device(vdev, VFL_TYPE_VIDEO, -1);
// 触发视频子系统匹配...
}
// 视频层匹配
int video_register_device(struct video_device *vdev, int type, int nr)
{
// 创建设备节点
vdev->dev = device_create(video_class, vdev->dev_parent,
devno, vdev, "%s%d",
video_device_node_name(vdev), vdev->num);
}
3.2 数据流路径
应用层与硬件间的完整数据流:
c复制static ssize_t vision_read(struct file *file, char __user *data,
size_t count, loff_t *ppos)
{
// 1. 从USB URB获取数据
struct urb *urb = vision->urb;
unsigned char *buffer = urb->transfer_buffer;
// 2. 数据格式转换
yuv_to_rgb(buffer, rgb_buffer, vision->width, vision->height);
// 3. 复制到用户空间
copy_to_user(data, rgb_buffer, min(count, vision->frame_size));
}
4. 动态绑定与热插拔机制
4.1 手动绑定与解绑
通过sysfs接口可以动态管理设备驱动绑定:
c复制// 绑定设备到驱动
echo "device_name" > /sys/bus/bus_name/drivers/driver_name/bind
// 解绑设备
echo "device_name" > /sys/bus/bus_name/drivers/driver_name/unbind
内核中对应的实现:
c复制static int my_driver_bind(struct device *dev)
{
// 设备特定的初始化
struct my_driver_data *data = dev_get_drvdata(dev);
if (!data) {
data = kzalloc(sizeof(*data), GFP_KERNEL);
dev_set_drvdata(dev, data);
}
return 0;
}
4.2 热插拔事件处理
设备插入时内核发送uevent:
c复制static int my_device_uevent(struct device *dev, struct kobj_uevent_env *env)
{
if (add_uevent_var(env, "DEVICE_NAME=%s", dev->init_name))
return -ENOMEM;
return 0;
}
用户空间udev收到事件后加载驱动:
code复制# udev规则示例
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="046d", RUN+="/sbin/modprobe uvcvideo"
5. 设备树(DTS)驱动开发实践
5.1 设备树硬件描述
典型的I2C设备树节点:
dts复制i2c@40005400 {
compatible = "st,stm32-i2c";
reg = <0x40005400 0x400>;
temperature-sensor@48 {
compatible = "ti,tmp102"; // 匹配驱动的关键!
reg = <0x48>;
ti,extended-mode = <1>;
};
};
5.2 驱动解析设备树
驱动通过of_接口获取设备树配置:
c复制static int tmp102_probe(struct i2c_client *client)
{
struct device_node *node = client->dev.of_node;
// 获取属性
of_property_read_u32(node, "ti,resolution", &tmp102->resolution);
// 获取GPIO中断
tmp102->irq = of_irq_get(node, 0);
// 获取校准数据
if (of_get_property(node, "ti,calibration", &len)) {
of_property_read_u8_array(node, "ti,calibration",
(u8 *)&tmp102->calibration, len);
}
}
6. 复杂驱动开发技巧
6.1 多设备共享驱动
一个驱动可以支持多个设备实例:
c复制struct multi_device_data {
struct list_head list; // 设备链表
struct mutex lock; // 保护锁
int device_count; // 设备计数
};
static int multi_device_probe(struct platform_device *pdev)
{
struct device_instance *inst;
// 添加到全局链表
mutex_lock(&global_data->lock);
inst->index = global_data->device_count++;
list_add_tail(&inst->node, &global_data->list);
mutex_unlock(&global_data->lock);
}
6.2 父子设备联动
典型的父子设备关系(如USB HUB):
c复制struct usb_hcd {
struct device dev; // 父设备
struct list_head devices; // 子设备列表
};
static int usb_hcd_probe(struct platform_device *pdev)
{
// 初始��父设备
device_initialize(&hcd->dev);
hcd->dev.parent = dev; // 设置父设备
// 创建子设备
for (i = 0; i < udev->config->desc.bNumInterfaces; i++) {
struct device *child = kzalloc(sizeof(*child), GFP_KERNEL);
device_initialize(child);
child->parent = parent; // 设置父设备
device_add(child);
}
}
7. 串口驱动开发实战
7.1 设备树配置
dts复制uart0: serial@101f0000 {
compatible = "vendor,my-uart";
reg = <0x101f0000 0x1000>;
interrupts = <44>;
vendor,baud-rate = <115200>;
};
7.2 驱动初始化
c复制static int my_uart_probe(struct platform_device *pdev)
{
// 获取设备资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
uart->membase = devm_ioremap_resource(dev, res);
// 从设备树获取属性
of_property_read_u32(np, "vendor,baud-rate", &uart->baud_rate);
// 初始化uart_port
uart->port.dev = dev;
uart->port.ops = &my_uart_ops;
// 注册串口端口
uart_add_one_port(&my_uart_driver, &uart->port);
}
7.3 中断处理
c复制static irqreturn_t my_uart_interrupt(int irq, void *dev_id)
{
// 读取中断标识寄存器
iir = readb(port->membase + UART_IIR);
// 处理接收中断
if (iir & UART_IIR_RDI) {
ch = readb(port->membase + UART_RX);
// 放入环形缓冲区
uart->rx_buf.buf[uart->rx_buf.head] = ch;
wake_up_interruptible(&port->state->port.read_wait);
}
}
8. 关键联动机制总结
Linux设备驱动联动主要通过7种机制实现:
- 名称匹配:最基本的设备驱动同名匹配
- ID表匹配:PCI/USB设备的vendor/device ID匹配
- 设备树匹配:通过compatible属性匹配
- ACPI匹配:x86平台的ACPI _HID/_CID匹配
- 类设备匹配:字符/块设备的主设备号匹配
- 平台数据传递:通过platform_data传递配置
- 资源传递:IORESOURCE_MEM/IORESOURCE_IRQ等资源匹配
理解这些机制后,开发者可以更灵活地设计驱动架构,处理各种复杂的硬件适配场景。在实际开发中,建议结合内核源码中的实例(如drivers目录下的各种驱动实现)来加深理解。
