1. Linux 2.6.20 SD/MMC卡驱动架构解析
在Linux 2.6.20内核中,SD/MMC卡驱动的架构设计体现了典型的总线-设备-驱动模型。整个驱动栈可以分为四个关键层次:
- 硬件抽象层(HAL):由s3cmci.c实现,负责与S3C2440芯片的MMC控制器直接交互
- 核心层:包括mmc.c、mmc_sysfs.c等文件,提供总线注册、设备发现等基础功能
- 块设备层:mmc_block.c和mmc_queue.c将SD卡抽象为块设备
- 文件系统接口:通过VFS层对接用户空间
关键提示:在分析这类老版本内核驱动时,需要注意2.6.20的驱动模型与现代内核(4.x+)有显著差异,特别是在设备树支持、电源管理等方面。
驱动初始化流程的起点是mmc_init()函数,它会创建三个关键数据结构:
static struct bus_type mmc_bus_type = { .name = "mmc", .dev_attrs= mmc_dev_attrs, .match = mmc_bus_match, .uevent = mmc_bus_uevent, .probe = mmc_bus_probe, .remove = mmc_bus_remove, .suspend = mmc_bus_suspend, .resume = mmc_bus_resume, }; static struct class mmc_host_class = { .name = "mmc_host", .dev_release = mmc_host_classdev_release, }; static struct workqueue_struct *workqueue;这三个组件的分工非常明确:
- mmc_bus_type:定义MMC总线类型及其操作
- mmc_host_class:管理主机控制器设备
- workqueue:处理卡检测等异步操作
2. S3C2440平台驱动实现细节
2.1 平台驱动注册流程
S3C2440的MMC控制器驱动采用平台设备模型注册,主要流程如下:
- 在s3cmci_init()中调用platform_driver_register()
- 匹配到设备后调用s3cmci_probe()
- 在probe函数中完成关键资源分配:
host = mmc_alloc_host(sizeof(*host), &pdev->dev); mmc = host->mmc; /* 设置主机能力 */ mmc->ops = &s3cmci_ops; mmc->f_min = 400000; // 400kHz mmc->f_max = 24000000; // 24MHz /* 获取中断和IO资源 */ host->irq = platform_get_irq(pdev, 0); host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); /* 初始化硬件 */ s3cmci_hwinit(host);2.2 中断处理机制
驱动需要处理三种关键中断:
- 卡检测中断(CD pin电平变化)
- 数据传输完成中断
- 命令响应中断
卡检测中断的处理最为关键:
static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id) { struct s3cmci_host *host = dev_id; mmc_detect_change(host->mmc, 500); // 500ms防抖延迟 return IRQ_HANDLED; }这个中断处理程序非常简单,只是调度了一个延迟工作队列。实际卡检测在mmc_rescan()中完成,这种设计避免了在中断上下文中进行复杂操作。
2.3 时钟与电源管理
S3C2440的MMC控制器时钟需要特别注意:
host->clk = clk_get(&pdev->dev, "sdi"); if (IS_ERR(host->clk)) { dev_err(&pdev->dev, "failed to get clock\n"); return PTR_ERR(host->clk); } clk_enable(host->clk);在2.6.20内核中,许多外设时钟默认是关闭的,驱动必须显式启用。实测中发现,如果不正确设置时钟分频,会导致CMD超时等奇怪问题。
3. 卡初始化和枚举过程
3.1 卡检测状态机
当mmc_rescan()被调用时,会执行以下状态转换:
- IDLE状态:发送CMD0使卡进入空闲状态
- 识别模式:通过CMD8区分SD卡版本(v1.1/v2.0)
- 初始化模式:发送ACMD41/CMD1获取OCR寄存器
- 识别阶段:读取CID、RCA等寄存器
- 数据传输模式:切换到高速模式(如支持)
关键代码路径:
mmc_rescan() → mmc_power_up() → mmc_send_if_cond() // CMD8 → mmc_send_app_op_cond() // ACMD41 → mmc_all_send_cid() // CMD2 → mmc_set_relative_addr() // CMD3 → mmc_send_csd() // CMD9 → mmc_select_card() // CMD73.2 块设备创建流程
当卡被成功识别后,会触发以下块设备注册流程:
- mmc_blk_probe()被总线核心调用
- 分配mmc_blk_data结构体
- 初始化请求队列
- 注册gendisk结构
static int mmc_blk_probe(struct mmc_card *card) { md = mmc_blk_alloc(card); mmc_init_queue(&md->queue, card, &md->lock); md->queue.issue_fn = mmc_blk_issue_rq; md->disk->fops = &mmc_bdops; add_disk(md->disk); }特别值得注意的是块大小的设置:
md->block_bits = 9; // 512字节块大小 blk_queue_hardsect_size(md->queue.queue, 1 << md->block_bits);这是SD规范的要求,所有兼容卡都必须支持512字节的块大小。
4. 数据传输与性能优化
4.1 请求处理流程
块设备请求的处理涉及多个组件的协作:
- 通用块层将请求放入队列
- mmc_queue_thread()取出请求
- mmc_blk_issue_rq()构造MMC命令
- s3cmci_request()最终操作硬件
关键数据结构转换:
struct request → struct mmc_request → 硬件寄存器操作4.2 DMA传输配置
S3C2440支持SDIO DMA传输,驱动中需要正确配置:
host->dma = dma_request_channel(DMA_MEM_TO_DEV); dmaengine_slave_config(host->dma, &dma_conf); /* 在请求处理中 */ sg_init_one(&host->sg,>mmc->f_max = 24000000; // 24MHz- 块大小:使用最大允许的块大小(通过CMD16设置)
mmc_set_blocklen(card, block_len);- 预分配缓冲区:减少内存分配开销
host->buffer = kmalloc(S3CMCI_FIFOSIZE, GFP_DMA);- 中断合并:适当调整中断触发阈值
writel(S3CMCI_FIFOTH_RXTH(32) | S3CMCI_FIFOTH_TXTH(32), host->base + S3CMCI_FIFOTH);5. 常见问题与调试技巧
5.1 典型问题排查
CMD超时问题:
- 检查时钟是否启用
- 验证GPIO配置是否正确
- 测量卡槽供电电压(应在2.7-3.6V之间)
DMA传输失败:
- 确保缓冲区按DMA要求对齐
- 检查DMA通道配置
- 验证sg列表是否正确构建
卡检测不稳定:
- 添加适当的防抖延迟(通常300-500ms)
- 检查CD引脚的上拉电阻
- 验证中断触发方式(边沿/电平)
5.2 调试手段
- 内核日志分析:
dmesg | grep s3c2410-sdi- Sysfs调试接口:
cat /sys/kernel/debug/mmc0/ios # 查看当前总线设置- 硬件寄存器查看:
void s3cmci_dump_regs(struct s3cmci_host *host) { printk(KERN_DEBUG "CTRL: %08x\n", readl(host->base + S3CMCI_CTRL)); printk(KERN_DEBUG "STAT: %08x\n", readl(host->base + S3CMCI_STAT)); /* 其他关键寄存器 */ }- 协议分析仪:使用专业SD协议分析仪捕获总线信号
5.3 移植到新平台的注意事项
时钟配置:
- 确保主机控制器时钟频率正确
- 注意时钟分频器设置
电源管理:
- 正确处理电源状态转换
- 实现必要的电源控制回调
DMA引擎集成:
- 选择正确的DMA引擎
- 配置适当的DMA参数
板级特定设置:
- GPIO复用配置
- 卡检测引脚极性
- 总线宽度设置(4位/8位)
在移植过程中,最有效的调试方法是分阶段验证:
- 首先确保能正确检测卡存在
- 然后验证基本命令(CMD0/CMD8等)
- 最后实现数据传输
这种由简到繁的验证方式可以快速定位问题所在层次。