
简介CF卡与SD卡驱动源码资源面向ARM架构Linux开发者与嵌入式驱动学习者针对存储设备在视频解码等对传输速度和稳定性要求较高的场景中驱动移植与调试需求。压缩包共8个文件包含5个C源码、2个头文件和1个Makefile整体仅24KB代码精简集中覆盖MMC协议核心、CRC校验及平台插槽实现。源码涉及Linux内核块设备驱动模型、MMC子系统、SPI/IDE接口选择及设备树配置可结合Makefile快速编译加载并通过insmod或modprobe挂载测试。通过阅读MMC核心模块、CRC校验模块与磁盘驱动实现开发者既能理解从命令发送、数据传送到块设备操作的完整链路也能掌握平台相关驱动的适配方法和常见读写错误、设备无法识别的排查思路。目前已有620人学习下载适合需要参考成熟驱动实现以优化存储性能的中高级嵌入式开发人员。1. 从一份 SD.rar 说起ARM Linux 下 CF/SD 驱动到底在折腾什么很多做 ARM 嵌入式的人第一次接触 CF 卡和 SD 卡驱动都是从老平台迁移开始的。手头一个基于 S3C2410 的项目视频解码出来要落盘板子上只有 CF 卡槽和 SD 卡槽内核起来之后/dev下根本看不到mmcblk0。这时候你才发现所谓“Linux 自带存储驱动”在 ARM 平台上是句空话——SD/MMC 协议栈、块设备层、板级 slot 控制代码全得自己编进内核。这份 SD.rar 就是干这个用的里面是 mmc_core、mmc_slot_s3c2410、crc、mmc_disk 这一整套可编译的驱动源码加上一份 5.6SD 驱动使用实验专门针对 ARM Linux 下 CF/SD 卡的识别、读写和调试。适合正在做视频存储、数据采集或者维护老 ARM 板卡的系统工程师也适合刚入门想搞明白块设备驱动怎么落地的嵌入式开发者。2. 源码结构拆解MMC 协议栈、CRC 校验和块设备层各管哪一段2.1 为什么 CF 卡和 SD 卡都挤在 MMC 协议栈里先说一个很多人容易搞混的点CF 卡有 IDE 和 SPI 两种模式SD 卡走 MMC 协议但这两类卡在内核驱动里经常共用同一套mmc_core框架。原因不复杂——SD 卡规范本身就是从 MMC 规范演化来的命令集、响应格式、寄存器定义有大量重叠而 CF 卡在 SPI 模式下底层传输逻辑也和 MMC 的 SPI 模式兼容。所以你在老内核里会看到drivers/mmc目录下同时管着 MMC、SD 和部分 CF 卡靠 card 识别阶段发的 ACMD41、CMD1 这类命令去区分卡的类型。这份源码里的mmc_core.c就是协议栈的中枢。它不直接操作寄存器而是维护一张卡的状态机从上电后的初始态到发送 CMD0 进入空闲态再通过 CMD2 获取 CID、CMD3 分配 RCA最后 CMD7 选中卡进入传输态。mmc_core-1.c从命名看应该是早期版本或参考实现我一般拿它当对照——当新版 core 调试不过去时翻一翻老版本的状态跳转逻辑往往能定位到是哪一步命令时序被改动过。2.2 mmc.h 和 crc.c协议宏定义与校验是驱动最容易翻车的地方mmc.h里定义的是整个协议层的命令字面量和数据结构比如MMC_CMD_READ_SINGLE_BLOCK、MMC_CMD_WRITE_SINGLE_BLOCK、struct mmc_command和struct mmc_data。这块没什么玄学纯粹是规范翻译但要注意不同内核版本对struct mmc_command的字段定义有差异比如flags字段从u32拆成unsigned int的位域直接决定了你这包代码能不能在目标内核版本上编译过。crc.c是很多人会忽略但绝对不能省的部分。MMC 协议里命令和响应都要带 CRC7 校验数据块在 SD 卡上要用 CRC16。协议规定 CRC7 的生成多项式是0x89即x^7 x^3 1CRC16 用的则是标准 CCITT 多项式0x1021。给一个常见的 CRC7 实现片段unsigned char mmc_crc7(unsigned char *buf, int len) { unsigned char crc 0; int i, bit; for (i 0; i len; i) { for (bit 0; bit 8; bit) { crc 1; if ((buf[i] (0x80 bit)) ^ (crc 0x80)) crc ^ 0x09; /* 生成多项式 0x89 去掉最高位后的值 */ } } return crc | 1; /* 协议要求 CRC7 结果最低位置 1 */ }这段代码的关键点有两个。第一crc ^ 0x09是用移位后的常数参与异或等价于对多项式0x89做逐位模 2 除法很多照着规范写的人容易把0x89整个写进去算出来的校验值永远是错的。第二返回值最低位强制写 1这是 MMC 协议对 CRC7 传输格式的特殊要求把校验值和结束位拼在一个字节里发出去。SD 卡对命令的 CRC 错误是静默丢弃的表现在现象上就是发 CMD2 之后总线没有任何响应而dmesg里只留下一条超时日志。我调试过的一块板子就是这里没置位最后一位卡死活不进识别流程排查了大半天。2.3 mmc_disk.c从协议到 /dev/mmcblk0 的最后一公里协议栈负责和卡对话但用户程序读写文件最终走的是块设备层。mmc_disk.c做的事情就是注册一个gendisk设备把协议层的读写请求转成request队列里的struct request再映射到具体的 MMC 命令上。这块代码最重要的两个参数是块大小和容量计算。mmc_disk.c里会从 CSD 寄存器读C_SIZE、C_SIZE_MULT、READ_BL_LEN字段算出卡的扇区数和总容量。初始化时注册 512 字节逻辑块、设置队列逻辑块大小为 512 字节调用set_capacity()写入设备容量。这里有个老内核常见的坑READ_BL_LEN大于 9即物理块大于 512 字节时驱动要左移READ_BL_LEN - 9位得到实际扇区数有些移植代码把移位写反了容量直接差 2 倍。mmc_disk.c里还承担了写保护状态的管理。卡侧有WP引脚和状态寄存器两个来源驱动通过mmc_card_read_only()判断是否只读这个状态在挂载时直接决定mount -o rw是否报错。如果硬件上 WP 引脚悬空没接上拉驱动会误判为写保护表现为卡能识别、分区能看到但一挂载就返回Read-only file system。2.4 Makefile这套源码怎么编成内核模块压缩包里的Makefile走的是标准内核外部模块编译方式obj-m : mmc_core.o mmc_slot_s3c2410.o mmc_disk.o mmc_core-objs : mmc_core-1.c crc.c KDIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KDIR) M$(PWD) ARCHarm CROSS_COMPILEarm-linux-gnueabihf- modules注意mmc_core-objs这一行——mmc_core.o不是单文件编出来的而是把mmc_core-1.c和crc.c链接成一个目标文件。这种写法在驱动拆分成多个源文件时很常见但新手容易漏了-objs变量直接把两个.c都写成obj-m的成员结果编译出两个独立模块mmc_core.ko什么也没编进去。KDIR指向的是目标平台的内核源码树交叉编译时必须配合ARCHarm和CROSS_COMPILE一起用否则跑到 x86 的内核树里编出来一个vmlinux不认的模块insmod时直接invalid module format。3. 把驱动跑起来配置内核、交叉编译、加载与挂载全流程3.1 内核配置哪些项不开驱动编了也白编把代码拿到手先别急着make要先确认目标内核的drivers/mmc相关配置。MMC 子系统要能工作至少得开以下配置项配置项作用选值CONFIG_MMCMMC/SD 协议栈主开关y 或 mCONFIG_MMC_BLOCK块设备层驱动生成 /dev/mmcblkXy 或 mCONFIG_MMC_S3C2410S3C2410 平台控制器驱动mCONFIG_MMC_DEBUG协议层调试日志建议 yCONFIG_BLOCK块设备层基础y如果CONFIG_MMC_BLOCK没开协议栈仍然能识别卡但/dev下永远不会出现mmcblk0SD 卡对外表现的只有 sysfs 里的一个 mmc 设备节点。很多人在这一步排查半天以为驱动没加载其实cat /sys/bus/mmc/devices/里已经能看到设备了卡在的是块设备注册环节。CONFIG_MMC_DEBUG建议在开发阶段打开它会把协议层的命令发送、响应状态、超时重试等信息打到dmesg里。内核的mmc/core/debug.c会通过pr_debug输出日志运行时还需要echo 8 /proc/sys/kernel/printk把调试级别拉起来否则默认级别下pr_debug是不会出现在控制台和日志里的。我第一次调这块驱动时不知道这层关系dmesg干干净净以为是驱动没加载成功白白浪费了两个小时。3.2 交叉编译与加载insmod 和 modprobe 的区别要分清配置项确认之后回到源码目录直接执行make产出.ko文件。把这些.ko通过 NFS 或者 TFTP 传到目标板上然后按顺序加载。加载顺序有讲究先加载mmc_core.ko再加载mmc_slot_s3c2410.ko最后加载mmc_disk.ko。因为mmc_slot_s3c2410在 probe 阶段会调用mmc_alloc_host和mmc_add_host注册一个 host而mmc_disk要等 card detect 触发后才真正开始干活。反过来先加载mmc_disk也没报错但它会找不到已注册的 host卡插入事件来了之后没有块设备响应。# 目标板终端按顺序执行 insmod mmc_core.ko insmod mmc_slot_s3c2410.ko insmod mmc_disk.ko # 查看内核是否识别到 MMC/SD 设备 dmesg | tail -n 20 # 如果使用了 modprobe 和 depmod depmod -a modprobe mmc_diskinsmod和modprobe的差别在于依赖处理。modprobe会读取modules.dep自动加载依赖模块但前提是执行过depmod -a生成了依赖文件。我一般调试阶段全是insmod一条一条来因为哪个模块加载失败、具体是什么原因insmod的报错比modprobe的二次封装更直白。modprobe经常出现的情况是依赖解析正确、模块也加载了但总线无响应最后发现是依赖文件的路径指向了内核自带的旧版本模块——这种“加载了但加载的不是你的代码”的坑最难查。3.3 验证识别结果mmcblk0 出现之后格式化与挂载才算真跑通驱动加载成功后dmesg里应该能看到完整的卡识别链路mmc0: new SD card at address 1234 mmcblk0: mmc0:1234 SD32G 29.7 GiB mmcblk0: p1第一行是协议层完成识别第二行是mmc_disk注册块设备成功第三行是分区扫描结果。注意mmcblk0后面的p1是分区不是整卡设备。接下来格式化、挂载mkfs.vfat /dev/mmcblk0p1 mount -t vfat /dev/mmcblk0p1 /mnt/sd mount | grep mmcblk0格式化工具的选择这里有个现实问题。Windows 上用的 SD Memory Card Formatter 会把卡格式化成带隐藏保留区的结构Linux 上mkfs.vfat不会动保留区。所以同一张卡在 Windows 格式化后插到 Linux 上分区起始位置可能不是 2048 扇区旧驱动按固定偏移扫描分区表可能识别不正常。如果遇到分区表解析失败先fdisk -l /dev/mmcblk0看看实际的分区结构而不是直接怀疑驱动。挂载成功后用dd做一轮全卡读写验证同时打开CONFIG_MMC_DEBUG观察命令时序是否异常。这套流程跑通驱动的基本功能就算落地了——但后面还有更隐蔽的板级问题等着你。4. S3C2410 平台接入mmc_slot_s3c2410.c 里的中断、GPIO 和时序细节4.1 platform_driver 的匹配逻辑老内核没有设备树怎么办mmc_slot_s3c2410.c是典型的老式platform_driver写法。在引入设备树之前ARM 平台描述硬件的方式是arch/arm/mach-s3c2410/mach-smdk2410.c里静态定义的platform_device包括寄存器地址、中断号、GPIO 配置全部写死在 C 代码里。驱动侧要做的就是让自己的platform_driver和这个platform_device名字对上static struct platform_driver s3c2410_mmc_driver { .probe s3c2410_mmc_probe, .remove s3c2410_mmc_remove, .driver { .name s3c2410-mmc, .owner THIS_MODULE, }, }; module_platform_driver(s3c2410_mmc_probe);module_platform_driver是一个封装宏展开后就是module_init和module_exit内部调用platform_driver_register注册驱动。匹配的关键是.driver.name也就是s3c2410-mmc这个字符串必须和板级代码里platform_device的name字段完全一致。这个串儿不统一是移植驱动时最常见的低端错误但报错信息贼隐晦——驱动注册成功probe 就是不执行因为总线匹配没找到对应的设备。设备树引入之后匹配方式改成了compatible属性。drivers/mmc/host/dw_mmc.c这类新驱动会声明samsung,s3c2410-mmc这样的 compatible 列表设备树节点里写compatible samsung,s3c2410-mmc才能对上。老驱动往新内核上移植时这一步是绕不过去的。4.2 probe 函数拿到什么资源映射和中断申请的必修课probe 函数被执行后驱动要完成四件事映射寄存器地址、申请中断、初始化 GPIO、注册 mmc host。S3C2410 的 MMC 控制器通过 SDINIT/SDCMD/SDDAT 等寄存器组操作总线寄存器基地址由platform_get_resource从设备资源里拿到res platform_get_resource(pdev, IORESOURCE_MEM, 0); s3c2410_mmc_base ioremap(res-start, resource_size(res)); irq platform_get_irq(pdev, 0); ret request_irq(irq, s3c2410_mmc_irq, IRQF_TRIGGER_RISING, s3c2410-mmc, host);ioremap拿到的是虚拟地址之后的寄存器读写全部通过writel/readl在这块虚拟地址上进行。中断号在 S3C2410 上是 IRQ_S3C2410_MMC 这样的平台宏定义但驱动代码里不应该写死用platform_get_irq动态获取才是正确做法。request_irq注册的中断处理函数里要操作状态寄存器清除中断标志位否则中断一直挂起CPU 被中断风暴打满——表现为系统响应极慢top里irq占用接近 100%。GPIO 的初始化容易被忽略。S3C2410 的 MMC 数据线、命令线、时钟线需要复用为特殊功能引脚板级代码通过s3c2410_gpio_cfgpin设置引脚功能。如果引脚复用配置不对总线波形根本出不来逻辑分析仪上看 CLK 始终为低卡永远不会有响应。我遇到过的一种场景是内核里CONFIG_S3C2410_GPIO没开引脚配置函数变成空操作驱动一切正常就是不识别卡查到最后是内核裁剪惹的祸。4.3 卡检测机制轮询还是中断决定你的 sd 卡是不是“玄学”识别SD/CF 卡座子上通常有一根卡检测引脚CD#插入时被拉低拔出时恢复高电平。mmc_slot_s3c2410.c对这种 detection 有两种处理方式轮询和中断。老内核里很多板子用轮询驱动每 500ms 调一次mmc_detect_change检查 GPIO 状态。这种做法的缺点是插入到识别之间有最大 500ms 的延迟而且如果 GPIO 状态读反了会一直报卡移除static void s3c2410_mmc_detect(struct work_struct *work) { struct s3c2410_mmc_host *host; int status; status gpio_get_value(host-pdata-det_pin); if (status host-pdata-det_pin_invert) mmc_detect_change(host-mmc, 0); }核心参数是det_pin_invert。它表示卡插入时 GPIO 是低电平还是高电平取决于板级电路设计。拉上拉到高、插入拉低det_pin_invert就是 0反过来插入拉高就要设成 1。这个参数错了驱动会把卡移除事件当成插入把插入当成移除dmesg里反复出现mmc0: card removed。所以拿到别人的驱动源码第一件事是核对板子原理图上 CD 引脚接的是高有效还是低有效不要想当然。中断方式则是在 CD 引脚上注册 GPIO 中断卡插入瞬间触发mmc_detect_change。代价是占一个系统中断号但在低功耗场景下比轮询省电得多。新内核里都推荐中断方式老驱动改造时加一段request_threaded_irq也不是很难关键点是中断触发沿要匹配det_pin_invert的极性沿设反了依然是反复插拔的假象。5. 避坑与常见问题CF/SD 驱动跑不起来的五类高频翻车现场5.1 insmod 报“version magic”不匹配现象insmod mmc_core.ko直接失败dmesg里看到version magic 5.4.0 ARM should be 5.4.0-xyz ARM。原因内核开启了CONFIG_MODVERSIONS和CONFIG_MODULE_FORCE_LOAD未开启时模块的 vermagic 必须精确匹配当前运行内核。你的源码包是针对某个内核版本写的而目标板跑的内核带了发行版后缀或者打了补丁版本字符串不一致。解决在目标板的/lib/modules/$(uname -r)/build下重新编这个模块而不是用宿主机 x86 内核树编。如果手头没有完全一致的内核源码树用make modules_prepare生成 Module.symvers 后重编。我一般会在源码包根目录留一份build.sh把KDIR和CROSS_COMPILE参数化换平台只需要改两个变量。5.2 卡识别成功但 /dev/mmcblk0 不出现现象dmesg里能看到mmc0: new SD card at address ...但/dev下面没有mmcblk0挂载无从谈起。原因CONFIG_MMC_BLOCK没开或者mmc_disk.ko没有成功加载。前者是内核配置问题后者是模块加载顺序问题。协议层和块设备层是两层识别卡的工作由 core 层完成生成块设备由mmc_disk完成缺一环都不行。解决lsmod | grep mmc先看mmc_disk在不在。不在就insmod mmc_disk.ko。在的话查内核配置zcat /proc/config.gz | grep MMC_BLOCK。老内核里也可以用grep MMC_BLOCK /boot/config-$(uname -r)。确认是y还是m然后把对应配置改掉重编内核。这台设备上如果不方便重编内核就用模块方式加载。5.3 SD 卡容量显示 2 倍或减半现象插入 32GB 卡fdisk -l显示只有 16GB或者显示 64GB。原因mmc_disk.c解析 CSD 寄存器计算扇区数时移位逻辑出错。CSD 里C_SIZE和C_SIZE_MULT组合出总块数计算公式是(C_SIZE 1) * (1 (C_SIZE_MULT 2))再乘以块大小得到字节容量。如果代码里把READ_BL_LEN的理解错位扇区数和字节数之间换算就出偏差。解决打开CONFIG_MMC_DEBUG通过dmesg查看打印出来的 CSD 原始值和计算过程逐一核对C_SIZE、C_SIZE_MULT、READ_BL_LEN三个字段。对照 SD 卡 Physical Spec 2.0 的 CSD 寄存器定义表检查移位位数。我调过的一块板子就是READ_BL_LEN为 112048 字节物理块代码却按 512 字节算扇区数容量直接变成 4 倍修改后恢复正常。5.4 挂载报 Read-only file system现象mount -t vfat /dev/mmcblk0p1 /mnt/sd失败dmesg里没有明显的 I/O 错误但mount返回只读。原因mmc_card_read_only()函数返回了真。两个来源CSD 寄存器的PERM_WRITE_PROTECT位被置位或者硬件 WP 引脚电平被误判。前者是卡本身出厂设置后者是板级电路问题。解决拿一张确定没有写保护的卡交叉验证排除卡的问题。然后检查 WP 引脚 GPIO 配置用gpio_get_value手动读一次电平跟原理图对照。如果 WP 引脚悬空且没有上拉/下拉电阻电平处于不定态驱动读到的值不稳定时好时坏——这种硬件问题无解只能在驱动里把mmc_card_read_only强制改写为 0 绕过前提是系统设计上不需要硬件写保护。5.5 数据读写 CRC 错误频繁现象大文件拷贝时卡死dmesg刷mmc0: error -EILSEQ或mmc0: CRC error。原因总线时序不满足 SD 卡规格。常见诱因是时钟频率太高S3C2410 的 MMC 控制器跑 48MHz 主频时分频配置不对实际 CLK 超过卡的 25MHz 上限。另一个常见原因是数据线布线太长信号完整性恶化。解决把时钟频率降档试SD 卡标准模式最高 25MHz高速模式才到 50MHz。控制器寄存器里通过SDIPRE分频寄存器配置 CLK公式是CLK PCLK / (SDIPRE 1)。老内核里的mmc_slot_s3c2410.c初始化代码会写这个寄存器把分频值调大时钟砍到 12.5MHz 再看 CRC 错误是否消失。如果消失基本锁定是时序裕量不足再考虑调 I/O 驱动强度或者换卡测试而不是盲目改驱动代码。6. 用 5.6SD 驱动使用实验做一次靠谱的读写吞吐验证驱动能识别、能挂载只是及格线。对视频解码落盘这种场景还得证明它能在持续读写下不掉链子。源码包里那份“5.6SD驱动使用实验”就是干这个用的我会拿它当模板加一段自己的验证脚本跑吞吐和稳定性。先做一轮全卡顺序写把脏数据填满再看读取速度和校验一致性#!/bin/bash # 驱动压测脚本顺序写 - 回读校验 - 吞吐统计 DEV/dev/mmcblk0p1 MNT/mnt/sdtest mkdir -p $MNT mount -t vfat $DEV $MNT # 1. 顺序写 1GBblock size 1MB统计平均速度 dd if/dev/urandom of$MNT/test.img bs1M count1024 convfsync 21 | tail -n 1 # 2. 落盘后 sync 一次确保写缓存全部刷进卡里 sync # 3. 回读并计算 md5同时统计读取速度 dd if$MNT/test.img of/dev/null bs1M count1024 21 | tail -n 1 md5sum $MNT/test.img # 4. 随机读 100 次每次 4KB模拟视频解码时的索引查询 time (for i in $(seq 1 100); do dd if$MNT/test.img of/dev/null bs4K count1 skip$((RANDOM % 262144)) 2/dev/null done) umount $MNT脚本里的convfsync很关键它强制dd在写完每个块后调fsync避免数据还滞留在页缓存里就报告完成。视频解码场景你最怕的就是写了一半掉电全卡文件系统损坏。md5sum那一步是校验数据一致性顺序写之后必须回读比对。随机读那一段模拟的是解码头索引的跳跃访问——视频流的包头和关键帧位置是随机分布的dd skip配合$RANDOM能粗略模拟。我从这轮实验里学到的教训是驱动编译的内核版本必须和目标板运行版本严格一致加载顺序必须按 core、slot、disk 来CRC 校验实现的细节一处都不能想当然。从那以后我每移植一次这套源码都强制走一遍“版本核对、配置确认、卡检测 GPIO 极性、dmesg 逐行核对”的流程先把板级问题排除干净再碰协议层的代码。这套驱动源码的价值不在代码本身多精巧而是它能让你在一个完整的 MMC 协议栈上摸清 ARM Linux 驱动从 probe 到块设备落地的每一环。希望这轮拆解能帮你的平台少走几步弯路。本文还有配套的精品资源点击获取