Linux MMC驱动开发实战:从零开始搭建SD卡读写模块(Kernel 5.4版)

在嵌入式Linux开发中,SD卡作为最常用的存储介质之一,其驱动稳定性直接影响产品可靠性。本文将基于Kernel 5.4源码,带你深入MMC子系统内部,从host控制器注册到block设备创建的完整流程,并分享实际开发中遇到的典型问题解决方案。

1. 开发环境准备与基础概念

1.1 硬件与工具链配置

推荐使用以下开发环境组合:

  • 硬件平台:树莓派4B或i.MX6ULL开发板(自带SDIO控制器)
  • 工具链:arm-linux-gnueabihf-gcc 8.3以上版本
  • 调试工具
    • mmc-utils:官方SD卡调试工具集
    • devmem2:内存寄存器查看工具
    • sysfs接口:/sys/kernel/debug/mmc*/下的调试节点

关键依赖安装命令:

sudo apt install build-essential libncurses-dev bison flex libssl-dev
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

1.2 MMC子系统架构速览

Linux内核中MMC子系统采用分层设计:

层级 组件 功能描述
硬件抽象层 host控制器驱动 sdhci-esdhc-imx(i.MX系列)
核心层 drivers/mmc/core 提供总线注册、协议处理等基础服务
块设备层 drivers/mmc/block 实现块设备读写接口

典型SD卡初始化流程:

  1. 控制器探测(mmc_alloc_host
  2. 卡检测(mmc_detect_change
  3. 协议协商(mmc_sd_init_card
  4. 块设备注册(mmc_blk_probe

2. Host控制器驱动开发

2.1 控制器注册关键代码

以i.MX6ULL平台为例,注册host控制器的核心步骤:

static int sdhci_esdhc_imx_probe(struct platform_device *pdev)
{
    struct sdhci_host *host;
    struct esdhc_platform_data *boarddata;
    
    // 1. 分配host结构体
    host = sdhci_pltfm_init(pdev, &sdhci_esdhc_imx_pdata, 0);
    
    // 2. 配置硬件特性
    host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL;
    host->mmc->caps |= MMC_CAP_NEEDS_POLL;
    
    // 3. 注册到MMC核心层
    ret = sdhci_add_host(host);
    if (ret)
        goto err_add_host;
    
    return 0;
}

常见问题排查:

  • 时钟未启用:检查clk_prepare_enable调用
  • DMA配置错误:确认dma-ranges设备树属性
  • 电压不匹配:通过mmc_ocr_voltages设置正确电压

2.2 设备树配置详解

典型SDIO控制器设备树节点:

usdhc1: mmc@2194000 {
    compatible = "fsl,imx6ull-usdhc";
    reg = <0x02194000 0x4000>;
    interrupts = <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_USDHC1>;
    clock-names = "ipg", "ahb", "per";
    bus-width = <4>;
    vmmc-supply = <®_sd1_vmmc>;
    status = "okay";
};

关键参数说明:

  • bus-width:4线或8线模式
  • max-frequency:建议初始设为25MHz
  • no-1-8-v:禁用1.8V低电压模式(兼容性选项)

3. SD卡初始化流程深度解析

3.1 卡检测机制实现

内核通过以下函数实现热插拔检测:

static void mmc_rescan(struct work_struct *work)
{
    struct mmc_host *host = container_of(work, struct mmc_host, detect);
    
    // 1. 发送CMD0复位卡
    mmc_go_idle(host);
    
    // 2. 发送CMD8检查电压范围
    err = mmc_send_if_cond(host, host->ocr_avail);
    
    // 3. 识别卡类型(SDv1/SDv2/SDHC)
    if (!mmc_attach_sd(host))
        return;
    
    // 4. 初始化块设备
    mmc_blk_alloc(host);
}

调试技巧:

  • 使用mmc_test工具强制触发重新检测:
    echo 1 > /sys/bus/mmc/devices/mmc0\:0001/uevent
    
  • 查看检测日志:
    dmesg | grep mmc0
    

3.2 典型初始化失败场景

错误现象 可能原因 解决方案
卡无法识别 电源不稳定 增加电源滤波电容
CMD5超时 卡处于睡眠模式 发送CMD0唤醒
CRC校验错误 信号质量差 调整PCB走线阻抗
容量识别错误 不支持SDHC 升级内核到4.19+

4. 块设备操作优化

4.1 请求队列调优

修改/sys/block/mmcblk0/queue/下参数提升性能:

# 调整调度器为deadline
echo deadline > /sys/block/mmcblk0/queue/scheduler

# 增大NR_requests值
echo 128 > /sys/block/mmcblk0/queue/nr_requests

# 启用写入缓存(需确保供电稳定)
echo 1 > /sys/block/mmcblk0/device/cache_ctrl

4.2 读写超时问题处理

在驱动代码中添加超时处理逻辑:

static void sdhci_esdhc_imx_timeout_timer(struct timer_list *t)
{
    struct sdhci_host *host = from_timer(host, t, timeout_timer);
    
    if (sdhci_readl(host, SDHCI_INT_STATUS) & SDHCI_INT_DATA_TIMEOUT) {
        pr_warn("Detected SD card timeout, resetting controller\n");
        sdhci_reset(host, SDHCI_RESET_CMD);
        sdhci_reset(host, SDHCI_RESET_DATA);
    }
}

实际项目中遇到频繁超时的情况,最终发现是SD卡槽接触不良导致。更换卡槽后稳定性显著提升,这也提醒我们在硬件设计阶段就要重视连接器的选型。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐