4、SDIO驱动初始化流程:模块加载与卸载、probe函数实现、设备识别与配置、资源申请与映射
好,咱们今天来聊聊SDIO驱动的初始化流程。这部分说白了,就是驱动和硬件第一次「握手」的过程。我做了这么多年驱动开发,发现很多新手栽跟头就栽在这里——不是probe没跑通,就是资源映射出了岔子。别急,咱们一步步拆开来看。
4.1 模块加载与卸载:驱动的「开关」
每个Linux驱动都有两个基本动作:加载和卸载。SDIO驱动也不例外。你想想看,驱动加载时内核要做什么?注册驱动、初始化数据结构、准备好和硬件通信。卸载时呢?反过来,释放资源、注销驱动。
我个人习惯这样写模块入口和出口:
static int __init sdio_drv_init(void)
{
int ret;
ret = sdio_register_driver(&my_sdio_driver);
if (ret) {
pr_err("SDIO driver registration failed: %d\n", ret);
return ret;
}
pr_info("My SDIO driver loaded successfully\n");
return 0;
}
static void __exit sdio_drv_exit(void)
{
sdio_unregister_driver(&my_sdio_driver);
pr_info("My SDIO driver unloaded\n");
}
module_init(sdio_drv_init);
module_exit(sdio_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("SDIO Driver Example");
嗯,这里要注意一点:__init 和 __exit 这两个宏别漏了。它们告诉内核,这些函数只在初始化或卸载时使用,之后可以回收内存。我在项目中遇到过有人忘了加,结果内存白白浪费了几KB——虽然不多,但嵌入式设备讲究的就是「锱铢必较」。
4.2 probe函数实现:驱动的「身份证验证」
probe函数是驱动初始化的核心。它什么时候被调用?当内核发现一个SDIO设备,并且这个设备的VID(厂商ID)和PID(产品ID)匹配到你的驱动时,probe就会被触发。
说白了,probe就是驱动和硬件的「相亲现场」——内核把设备信息递给你,你得确认「嗯,这设备我能管」,然后开始干活。
static int my_sdio_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{
struct my_dev *dev;
int ret;
dev_info(&func->dev, "Probing SDIO device: vendor=0x%04x, device=0x%04x\n",
id->vendor, id->device);
// 1. 分配私有数据结构
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
// 2. 关联设备与私有数据
dev->func = func;
sdio_set_drvdata(func, dev);
// 3. 使能SDIO功能
ret = sdio_enable_func(func);
if (ret) {
dev_err(&func->dev, "Failed to enable SDIO function\n");
goto err_free;
}
// 4. 设置块大小(通常为512字节)
ret = sdio_set_block_size(func, 512);
if (ret) {
dev_err(&func->dev, "Failed to set block size\n");
goto err_disable;
}
// 5. 读取设备寄存器,验证通信
ret = my_sdio_read_cfg(func);
if (ret) {
dev_err(&func->dev, "Failed to read device config\n");
goto err_disable;
}
dev_info(&func->dev, "SDIO device probed successfully\n");
return 0;
err_disable:
sdio_disable_func(func);
err_free:
kfree(dev);
return ret;
}
看到没?probe里我做了五件事:分配内存、关联设备、使能功能、设置块大小、读取配置。每一步都可能失败,所以错误处理一定要到位。我曾经在一个项目里忘了调用 sdio_disable_func,结果设备一直处于半使能状态,后续读写全乱了——那叫一个头疼。
4.3 设备识别与配置:匹配表与寄存器初始化
设备识别靠什么?靠 sdio_device_id 匹配表。内核拿着这个表,去和实际插入的SDIO设备比对。匹配上了,probe就被调用。
| 字段 | 含义 | 示例值 |
|---|---|---|
| vendor | 厂商ID | 0x02D0 (Broadcom) |
| device | 产品ID | 0x4324 (WiFi芯片) |
| class | 设备类别 | 0x00 (标准) |
| driver_data | 驱动私有数据 | 自定义值 |
static const struct sdio_device_id my_sdio_ids[] = {
{ SDIO_DEVICE(0x02D0, 0x4324) },
{ /* end: all zero */ },
};
MODULE_DEVICE_TABLE(sdio, my_sdio_ids);
配置阶段呢?我一般会在probe里读取设备的CIS(Card Information Structure),确认设备支持的功能。然后根据需求配置相关寄存器。比如设置中断使能、选择数据传输模式等。
4.4 资源申请与映射:内存、中断与IO
驱动要正常工作,离不开三种资源:内存、中断、IO地址。申请和映射的顺序很重要,搞反了容易出问题。
内存申请:
除了probe里分配的私有数据结构,SDIO驱动通常还需要DMA缓冲区。我建议用 dma_alloc_coherent 来申请,保证物理地址连续:
dev->dma_buf = dma_alloc_coherent(&func->dev, BUF_SIZE,
&dev->dma_handle, GFP_KERNEL);
if (!dev->dma_buf) {
dev_err(&func->dev, "Failed to allocate DMA buffer\n");
ret = -ENOMEM;
goto err_disable;
}
中断申请:
SDIO设备通过SDIO中断通知主机。申请中断时,要指定触发方式和处理函数:
ret = request_threaded_irq(func->irq, NULL, my_sdio_irq_handler,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"my_sdio", dev);
if (ret) {
dev_err(&func->dev, "Failed to request IRQ %d\n", func->irq);
goto err_dma;
}
嗯,这里有个坑:SDIO中断是电平触发的,所以一定要加 IRQF_ONESHOT 标志。不加的话,中断处理函数还没执行完,同一个中断又来了——死循环。我曾经在这个问题上折腾了两天,最后发现是标志位没设对。
IO映射:
SDIO设备的IO空间通过 sdio_readb、sdio_writeb 等函数访问,不需要像PCI那样做ioremap。但如果你需要访问设备内部的FIFO或大块内存区域,可以考虑使用 sdio_memcpy_fromio 和 sdio_memcpy_toio:
// 读取设备配置寄存器
u8 reg_val = sdio_readb(func, 0x10, &ret);
if (ret)
dev_err(&func->dev, "Failed to read register 0x10\n");
// 批量读取数据
sdio_memcpy_fromio(func, dev->dma_buf, 0x1000, BUF_SIZE);
4.5 初始化流程全景图
说了这么多,咱们用一张图把整个流程串起来。这张图是我自己画的一个简化版,但核心逻辑都在里面了:
这张图把整个流程分成了两条路径:加载路径(绿色)和卸载路径(红色)。加载时,从模块入口一路走到probe,完成资源申请;卸载时,反过来释放所有资源。你想想看,如果卸载时漏了某个资源的释放,下次加载就会出问题——内存泄漏、中断冲突,各种怪毛病都来了。
- probe函数是初始化的「心脏」,所有关键步骤都在这里完成
- 资源申请要「谁申请谁释放」,probe里申请的资源,remove里必须释放
- 错误处理要完整,任何一个步骤失败都要能回滚到安全状态
- 匹配表要准确,VID/PID写错了,驱动根本不会加载
好了,SDIO驱动初始化流程就讲到这里。这部分内容虽然基础,但却是整个驱动的基石。我见过太多驱动出问题,追根溯源都是初始化没做好——要么资源没申请够,要么中断配置错了。记住一句话:初始化做扎实了,后面就顺了。
可可精品资料库