linux学习--总线设备驱动模型
跟着韦东山老师学习的linux,在到了驱动这里,算是有了一些疑惑,在这里记录下来。代码如下:
driver.c
#include <linux/module.h>
#include <linux/platform_device.h>static int my_driver_probe(struct platform_device *pdev)
{struct resource *res_mem, *res_irq;pr_info("Driver probe called for device: %s\n", pdev->name);// 在这里你就可以用 platform_get_resource() 拿硬件信息res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);if (res_mem)pr_info("MEM resource: start=0x%lx end=0x%lx name=%s\n",(unsigned long)res_mem->start, (unsigned long)res_mem->end, res_mem->name);if (res_irq)pr_info("IRQ resource: start=%lu name=%s\n",(unsigned long)res_irq->start, res_irq->name);return 0;
}static int my_driver_remove(struct platform_device *pdev)
{pr_info("Driver remove called for device: %s\n", pdev->name);return 0;
}static struct platform_driver my_driver = {.probe = my_driver_probe,.remove = my_driver_remove,.driver = {.name = "my_demo_dev", // 一定要匹配 device 的名字.owner = THIS_MODULE,},
};static int __init my_driver_init(void)
{pr_info("Registering platform driver: my_demo_dev\n");return platform_driver_register(&my_driver);
}static void __exit my_driver_exit(void)
{pr_info("Unregistering platform driver: my_demo_dev\n");platform_driver_unregister(&my_driver);
}module_init(my_driver_init);
module_exit(my_driver_exit);MODULE_LICENSE("GPL");
device.c
#include <linux/module.h>
#include <linux/platform_device.h>
static struct resource my_resources[] = {{.start = 0x10000000,.end = 0x10000fff,.flags = IORESOURCE_MEM,.name = "my_demo_mem",},{.start = 42,.end = 42,.flags = IORESOURCE_IRQ,.name = "my_demo_irq",}
};
static void dummy_dev_release(struct device *dev)
{pr_info("dummy_dev_release called\n");
}static struct platform_device my_device = {.name = "my_demo_dev", // 用于匹配 driver 名字.id = -1,.dev = { .release = dummy_dev_release },.resource = my_resources,.num_resources = ARRAY_SIZE(my_resources),
};static int __init my_device_init(void)
{pr_info("Registering platform device: my_demo_dev\n");platform_device_register(&my_device)return 0;
}static void __exit my_device_exit(void)
{pr_info("Unregistering platform device: my_demo_dev\n");platform_device_unregister(&my_device);
}module_init(my_device_init);
module_exit(my_device_exit);MODULE_LICENSE("GPL");
Makefile
# 我们希望生成两个 .ko 模块
obj-m := device.o driver.o# 内核构建目录
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)# 本地编译(不交叉编译),指定空的 CROSS_COMPILE
all:$(MAKE) ARCH=x86_64 CROSS_COMPILE="" -C $(KDIR) M=$(PWD) modulesclean:$(MAKE) -C $(KDIR) M=$(PWD) clean
make后,加载两个ko后,即可看到结果。
以下是我的一些疑惑:
为什么 driver 里用 platform_get_resource(pdev, IORESOURCE_MEM, 0),而不是直接访问 pdev->resource?
pdev->resource 是 platform_device 结构内部的资源数组指针,但这是设备核心结构的私有数据,driver 不建议直接访问成员变量。
通过 platform_get_resource() 接口访问资源是内核官方约定的安全用法:
struct resource *platform_get_resource(struct platform_device *pdev,unsigned int type, unsigned int idx);
参数说明:
pdev —— 指向当前 platform 设备结构
type —— 资源类型,比如 IORESOURCE_MEM (内存区域)、IORESOURCE_IRQ (中断线) 等
idx —— 该类型资源的第几个。例如 idx=0 是该类型的第一个资源,如果你设备有多个同类型资源,idx=1 是第2个,以此类推。
resource 结构体介绍
static struct resource my_resources[] = {{.start = 0x10000000, // 资源起始地址(物理地址或者中断号等).end = 0x10000fff, // 资源结束地址(或同中断起始一致).flags = IORESOURCE_MEM, // 资源类型标记:内存、IO、中断等.name = "my_demo_mem", // 资源名字,仅供分析调试},{.start = 42, // IRQ 号 42.end = 42,.flags = IORESOURCE_IRQ,.name = "my_demo_irq",}
};
如果有多个同类型资源,如何获取?
举例:某设备有两个内存区域和一个中断号:
static struct resource my_resources[] = {{ .start=0x10000000, .end=0x10000fff, .flags=IORESOURCE_MEM, .name="mem0" },{ .start=0x10001000, .end=0x10001fff, .flags=IORESOURCE_MEM, .name="mem1" },{ .start=42, .end=42, .flags=IORESOURCE_IRQ, .name="irq" },
};
驱动中访问时,传递第三个参数 idx 来访问:
struct resource *mem0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 第一个mem资源
struct resource *mem1 = platform_get_resource(pdev, IORESOURCE_MEM, 1); // 第二个mem资源
struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); // 第一个irq资源
如果你写错了索引,比如访问 IORESOURCE_MEM, 2,返回 NULL,因为没有第 3 个内存资源。
多个设备如何区分处理?
假设你有多个设备:
static struct platform_device my_device1 = {.name = "my_demo_dev",.id = 0,.resource = my_resources1,.num_resources = ARRAY_SIZE(my_resources1),// ...
};static struct platform_device my_device2 = {.name = "my_demo_dev",.id = 1,.resource = my_resources2,.num_resources = ARRAY_SIZE(my_resources2),// ...
};
驱动 probe 里可以通过 pdev->id 或 pdev->name 来区分:
static int my_driver_probe(struct platform_device *pdev)
{pr_info("Probing device id=%d name=%s
", pdev->id, pdev->name);if (pdev->id == 0) {// 设备0特有初始化} else if (pdev->id == 1) {// 设备1特有初始化}// 资源获取等通用代码struct resource *res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);// ...return 0;
}
每一个设备注册后都会调用驱动的 probe函数
Linux设备模型(bus + device + driver)设计是:
- 驱动程序注册后不会主动运行 probe,只有系统检测到 对应设备被注册 且匹配成功,才会调用驱动 probe。
- 每个匹配的设备都会独立调用一次 probe 函数,并且在 probe 函数里,传入的是匹配的那个 platform_device *pdev 指针。
- 所以如果你注册了两个 my_demo_dev 设备,probe 会调用两次,分别传入两台设备的指针。
flags 有哪些?含义和场景
struct resource 中的 flags 用来描述资源类型及属性,常见的宏定义在 <linux/ioport.h>。以下整理部分常用 flag:
| 宏名 | 说明 | 典型场景 |
|---|---|---|
| IORESOURCE_MEM | 内存映射资源区 | 通常是设备的寄存器物理地址区域 |
| IORESOURCE_IO | IO 端口资源(x86 用较多 | 设备 IO 端口地址映射 |
| IORESOURCE_IRQ | 中断资源 | 设备使用的中断号 |
| IORESOURCE_DMA | DMA 资源 | 设备用的DMA通道 |
| IORESOURCE_PREFETCH | 该内存区域可预取 | 给 CPU 优化提示 |
| IORESOURCE_CACHEABLE | 该内存区域可缓存 | 同上 |
| IORESOURCE_DISABLED | 该资源禁用 | 设备掉电或者硬件不在线时使用 |
| IORESOURCE_BUSY | 资源已被占用 | 设备在使用该资源时状态标记 |
| IORESOURCE_READONLY | 资源只读 | 某些只读资源区 |
| 你也可以查看 kernel 源码 include/linux/ioport.h,里面定义了更多标志及宏。 |
如果两个设备的初始化函数不一样,怎么办?
平台驱动的 probe 函数是所有匹配设备的统一入口,如果多个设备的初始化逻辑差异较大,可以有下面做法:
在相同 probe 内区分设备(最常用)
利用 pdev->id 或者 pdev->name,根据ID或者名称区分后分别执行对应的初始化代码。
示例:
static int my_driver_probe(struct platform_device *pdev)
{pr_info("probe device: name=%s id=%d", pdev->name, pdev->id);if (pdev->id == 0) {pr_info("Init device 0 specific code");// 初始化逻辑1} else if (pdev->id == 1) {pr_info("Init device 1 specific code");// 初始化逻辑2} else {pr_warn("Unknown device id %d", pdev->id);}return 0;
}
方案B:使用设备树/ID表 区分多设备驱动(更高级)
设备树或者 Platform ID 表里定义多个设备描述,driver 用匹配表匹配不同设备,probe 中根据匹配表传入的上下文(id_entry->data)区别初始化。
如果有误、希望大家多多指导!
