尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

S3C2410平台ARM Linux SD/MMC驱动源码解析与移植实战

S3C2410平台ARM Linux SD/MMC驱动源码解析与移植实战 简介针对ARM架构Linux系统的CF卡与SD卡驱动源码包面向嵌入式驱动开发、系统集成及视频解码场景的研发人员用于解决Linux下CF/SD存储设备识别失败、块设备读写异常、协议适配不兼容等问题。压缩包内共8个文件包含5个C源码、2个头文件和1个Makefile整体大小仅24KB覆盖MMC核心、SD卡插槽检测、CRC校验、块设备驱动等模块清晰展示了SD/CF卡驱动的框架与接口设计便于直接阅读、交叉编译和灵活移植。目前已有620人学习下载适合作为Linux驱动开发从入门到进阶的参考资料。内容涉及Linux内核驱动模型、块设备驱动架构、MMC/SD协议、SPI/IDE接口、设备树配置、驱动编译加载等关键知识点并附有“5.6SD驱动使用实验”指导可辅助读者完成驱动功能验证与性能测试掌握常见故障排查思路。对于希望在视频解码等高吞吐场景中优化存储驱动、提升系统稳定性的开发者而言这份紧凑的参考代码具有直接借鉴与实用价值。1. 这套 SD.rar 驱动源码ARM Linux 上把 CF 卡和 SD 卡盘活的钥匙做视频解码板卡的人大概都有这种经历解码芯片跑起来了数据源却卡在存储上——CF 卡写一帧丢一帧SD 卡枚举失败dmesg 里刷了一屏 mmc0: error -110。这套 SD.rar 里装的正是解决这类问题的 ARM Linux 驱动源码包含mmc_core.c、mmc_slot_s3c2410.c、mmc_disk.c、crc.c和对应的头文件与 Makefile覆盖了从 MMC 协议层到底层块设备注册的完整链路。它特别适合两类人一是正在三星 S3C2410 这类 ARM 平台上移植 Linux 存储驱动的工程师二是想搞懂 MMC 协议栈是怎么把 SD 卡变成/dev/mmcblk0的入门者。前者可以直接拿这套代码改寄存器地址和中断号后者能把mmc_core.c当成一份带注释的协议实现教材来啃。别指望它是那种 insmod 就能跑的现成货——驱动代码从来都是半成品关键是你能从里面读出多少设计思路。2. 源码拆解从裸机寄存器到块设备节点的三层结构2.1 文件清单这套驱动里到底有什么解开 SD.rar 之后核心文件就这些文件职责mmc_core.cMMC/SD 协议核心命令发送、卡识别、状态机mmc_slot_s3c2410.c平台相关层S3C2410 的控制器寄存器操作mmc_disk.c块设备层把 MMC 设备注册成 Linux 块设备mmc.h协议层头文件定义命令号、状态、数据结构mmc_core-1.c核心层的一个变体通常用于对比或替换crc.cCRC7命令和 CRC16数据校验实现Makefile内核模块编译脚本这个分层跟 Linux 内核后来的 MMC 子系统思路一致底层控制器驱动、协议核心、块设备接口三层解耦。对 2.6 时代的内核来说这套代码把 MMC 子系统最核心的东西用最小集合实现了读起来比直接啃内核源码轻松得多。2.2 三层结构S3C2410 控制器怎么和内核对话第一层是mmc_slot_s3c2410.c它直接面对硬件寄存器。S3C2410 的 SD/MMC 控制器有 SDICON、SDIPRE、SDICMD、SDIDAT 等一组寄存器驱动要从platform_get_resource拿到 IO 地址然后映射、配置时钟分频、设置中断。这一层做的事基本上就是把内核的mmc_host_ops回调函数翻译成寄存器读写。第二层是mmc_core.c它是整套驱动的中枢。卡的上电、卡识别、读取 CID/CSD、切换工作模式都由这一层组织。它调用底层的mmc_slot_s3c2410.c发命令、传数据同时向上层mmc_disk.c暴露块设备操作接口。在mmc_core.c里你能看到完整的卡识别流程mmc_go_idle、mmc_send_op_cond、mmc_all_send_cid、mmc_send_relative_addr这些步骤对应 SD 协议里的 CMD0、CMD1/ACMD41、CMD2、CMD3。第三层是mmc_disk.c负责把底层传上来的数据块包装成 Linux 的request_queue。它注册block_device_operations实现 open、release、request 函数数据请求进来之后由请求队列机制分发到具体的读写命令。这个文件里能看到alloc_disk、add_disk、blk_init_queue这几个关键调用都是块设备驱动的标准套路。2.3 数据流一次读操作从 VFS 到寄存器的完整路径读 SD 卡一个扇区的数据在用户态就是一个read()syscall但落到这套驱动里要走完整条链VFS - mmc_disk.c 的 mmc_disk_request() - 把 bio 请求拆成 struct mmc_request - 调用 mmc_wait_for_req() 进入 mmc_core.c - mmc_core.c 组织命令序列 (CMD17/CMD18) - 通过 host-ops-request 下发到 mmc_slot_s3c2410.c - 写 SDICMD 寄存器发命令读 SDIDAT 寄存器取数据mmc_disk.c只负责跟内核块设备层打交道它不认识 SD 协议只管把数据包装成请求结构体。mmc_core.c负责翻译——把读扇区的需求翻译成 CMD17单块读或者 CMD18多块读处理卡的状态寄存器处理 CRC 校验。到了最底层就是往寄存器里填数、等中断、读数据。理解这条链路很重要。很多人在改代码时只盯着mmc_slot_s3c2410.c里的寄存器操作结果发现读回来的数据全是乱的——问题往往不在寄存器而是mmc_core.c里命令参数填错了。寄存器操作是体力活协议分析才是真正的技术活。3. MMC 协议交互细节命令、CRC 与状态机3.1 命令通道为什么 CMD0 之后一定要带 74 个时钟SD 卡上电之后并不是立刻就能收命令的。协议规定SD 卡需要至少 74 个时钟周期的稳定输入才能完成上电初始化这是为了给卡内部的稳压器充电。如果省掉这一步卡可能对后续的 CMD0 毫无反应dmesg 里报mmc0: card never left busy state。mmc_core.c里上电初始化时序大致是static void mmc_power_up(struct mmc_host *host) { /* 打开电源等待 1ms 让电压稳定 */ host-ops-set_ios(host, ios); /* 发送 74 个时钟周期 */ for (i 0; i 74; i) { host-ops-send_clock(host); } /* 进入 idle 状态 */ mmc_go_idle(host); }这段代码里有个关键点set_ios不只是开关电源它会同时配置总线宽度、时钟频率和电压。S3C2410 上初始化时钟不能一下拉满常见做法是先给 400kHz等卡识别完成后再切到高速模式。如果一开始就上 25MHz很多卡根本起不来。3.2 CRC 校验CRC7 和 CRC16 别搞混crc.c里实现了两套 CRC命令和响应用 CRC7数据块用 CRC16。很多第一次接触 MMC 驱动的人在这里翻车——把 CRC7 的查表法用到数据校验上结果每块数据都报 CRC error。CRC7 的生成多项式是x^7 x^3 1初始值 0x00输出的 7 位 CRC 拼在 48 位命令的最低位。CRC16 的生成多项式是x^16 x^12 x^5 1初始值 0x0000跟 CRC7 完全不是一回事。/* CRC7 查表法用于命令和响应 */ u8 mmc_crc7(u8 *buf, int len) { u8 crc 0; for (int i 0; i len; i) { crc crc7_table[(crc 1) ^ buf[i]]; } return crc 0x7f; }CRC 校验失败时别急着怀疑卡有问题。先查主机控制器有没有开启 CRC 检查功能——S3C2410 的 SDICON 寄存器里有个 bit 控制 CRC 校验的开关如果硬件检查关闭了软件层校验拿到的全是错误结果。这是硬件和数据链路的问题不是卡的问题。3.3 状态机流转卡怎么从 idle 状态一路走到 transferMMC/SD 协议的核心是一个状态机mmc_core.c里大量代码都是在处理状态切换。从mmc_go_idle到mmc_send_op_cond再到mmc_all_send_cid每一步都依赖上一步的状态正确。状态进入条件关键命令idle上电或 CMD0CMD0readyACMD41 成功ACMD41/CMD1identCMD2 返回 CIDCMD2stbyCMD3 分配 RCACMD3tranCMD7 选中卡CMD7/CMD17/CMD18data数据传输中CMD17/CMD18/CMD25常见问题在这条链路上有两处一是 ACMD41 的 HCS 位没有正确设置导致 SDHC 卡被当成普通 SD 卡初始化容量直接减半甚至变成 0二是 CMD3 之后没有正确保存 RCA相对卡地址后续所有命令都带着错误的地址发出去。如果看到卡识别成功了但读写失败优先检查mmc_core.c里 RCA 有没有被正确保存并传递。4. 编译与移植Makefile 分析和 ARM 平台适配4.1 Makefile 逐段拆解编模块还是编进内核这套驱动提供的 Makefile 是典型的内核模块编译方式obj-m : mmc_drv.o mmc_drv-objs : mmc_core.o mmc_slot_s3c2410.o mmc_disk.o \ crc.o KERNELDIR : /home/arm/linux-2.6.24 PWD : $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M$(PWD) modulesobj-m表示编成可加载模块mmc_drv-objs把四个.o文件链接成一个mmc_drv.ko。KERNELDIR指向内核源码目录编译时必须保证它是已经配置过的、跟你目标平台匹配的内核源码树。如果要编进内核而不是模块把obj-m改成obj-y就行同时把mmc_drv.o的依赖项填进对应的 Makefile 里。编进内核的好处是不用关心模块加载顺序缺点是每次改代码都要重新编译内核并烧写镜像调试周期长很多。4.2 交叉编译环境工具链和内核版本要对上ARM 平台免不了交叉编译。这套代码是 2.6.24 左右时代的用的工具链大概率是arm-linux-gcc 3.4.x或4.2.x。如果你的开发环境是 GCC 9 以上的新版交叉编译器编译时很可能报一堆类型不匹配或者隐式声明错误。两个解决办法一是把源码里的__u8、__u32换成u8、u32同时补齐linux/types.h头文件二是用-Wno-error选项把警告降级先保证编出来再说。但我建议不要直接忽略编译警告——很多时序问题就是在编译告警中埋下的。4.3 内核配置需要打开哪些选项模块编好之后能不能加载成功还取决于内核有没有打开 MMC 子系统的支持。在make menuconfig里面至少要确保这几项都是打开的Device Drivers --- * MMC/SD card support --- * MMC block device driver * Samsung S3C2410 MMC/SD interface support如果内核压根没编 MMC 子系统mmc_drv.ko加载时会报Unknown symbol mmc_register_host之类的错误。这不算代码问题是内核配置没对上。4.4 硬件引脚和中断从原理图到设备资源S3C2410 的 MMC/SD 控制器通常接在固定的 GPIO 上但中断号可能因为板级设计不同而变化。移植时重点核对mmc_slot_s3c2410.c里的 platform device 注册static struct resource s3c_mmc_resources[] { [0] { .start S3C2410_PA_MMC, .end S3C2410_PA_MMC S3C2410_SZ_MMC - 1, .flags IORESOURCE_MEM, }, [1] { .start IRQ_S3C2410_MMCD0, .end IRQ_S3C2410_MMCD0, .flags IORESOURCE_IRQ, }, };S3C2410_PA_MMC是 MMC 控制器的物理基地址S3C2410_SZ_MMC是映射大小这两个值来自芯片手册一般不需要改。IRQ_S3C2410_MMCD0是中断号这个就必须跟板卡实际走线对齐了——有些板子把卡检测引脚接在 GPIO 中断上有些直接复用控制器的专用中断查原理图的时候要仔细。寄存器读写的排坑方法后面单独聊。5. 避坑指南MMC 驱动移植中的常见问题与排查手段5.1 编译通过但 insmod 报 Unknown symbol现象模块编译无误insmod 时却报一串Unknown symbol mmc_alloc_host之类的错误。原因内核没有开启 MMC 子系统支持模块引用的内核符号根本不存在。解决回到make menuconfig确认MMC/SD card support和MMC block device driver都编进去了。如果还不行检查内核版本——源码是用 2.6.24 开发的API 函数名跟新版内核差异很大mmc_alloc_host在 3.x 之后就改了签名直接用旧代码可能连编译都过不去。5.2 上电初始化时系统无任何反应现象insmod 成功但 dmesg 里没有mmc0: new SD card之类的日志寄存器写进去读出来全零。原因最典型的是时钟没开。S3C2410 的外设时钟默认是关闭的需要在mmc_slot_s3c2410.c的 probe 函数里先通过clk_get和clk_enable打开外设时钟。另一个原因是 GPIO 复用配置错误MMC 控制器对应的引脚被配成了普通 GPIO 模式。解决在 probe 函数里加上时钟使能struct clk *mmc_clk; mmc_clk clk_get(dev, mmc); if (IS_ERR(mmc_clk)) { printk(KERN_ERR Failed to get MMC clock\n); return -ENOENT; } clk_enable(mmc_clk);这段代码的位置很关键——必须在任何寄存器读写之前执行。如果时钟没使能后面所有寄存器访问都等于打到空气上读回来全是默认值。5.3 卡能识别读写时 CRC 错误刷屏现象设备节点/dev/mmcblk0能创建mount 也成功但一拷大文件就报mmc0: CRC error小文件偶尔能过。原因这个坑我栽过两次。第一次是时钟频率拉太高S3C2410 的 SDIO 控制器在高速模式下信号完整性不够把传输频率从 25MHz 降到 12MHz 就稳定了。第二次是 DMA 缓冲区的对齐问题——控制器要求数据缓冲区 4 字节对齐而内核块层给的数据缓冲区只做了 2 字节对齐。解决先降频测稳定性再排查 DMA 对齐。如果走 PIO 模式就没问题铁定是 DMA 缓冲物理地址没对齐。5.4 SDHC 卡识别成普通 SD 卡容量只剩几百 MB现象插入 8GB SDHC 卡fdisk -l显示容量只有 200MB 左右。原因ACMD41 的 HCS 位没有设置。SD 协议规定容量超过 2GB 的 SDHC 卡必须等 HCS1 的 ACMD41 才会回应否则它装成普通 SD 卡响应而普通 SD 卡的容量上限就是 2GB。解决在mmc_sd_init_card里构造 ACMD41 参数时把OCR_HCS位加上/* 设置 HCS 位支持 SDHC 卡 */ cmd.arg OCR_HCS | host-ocr_avail; cmd.flags MMC_RSP_R3 | MMC_CMD_AC;这个问题在旧驱动里特别常见因为 SDHC 标准是 2006 年才定的2.6.24 时代的内核对 SDHC 支持并不完善。如果改完 HCS 位还是不行大概率是卡响应里的 OCR 寄存器解析逻辑也有问题需要用逻辑分析仪或者示波器抓一下响应数据。5.5 内存访问异常用 ioremap 还是直接物理地址现象驱动加载后系统直接 oops报 Unable to handle kernel paging request at virtual address。原因S3C2410 的寄存器物理地址不能直接被内核访问必须通过ioremap映射成虚拟地址。有人图省事直接#define MMC_BASE 0x5A000000就拿来用内核起个 oops 很正常。解决所有寄存器访问都走映射后的虚拟地址static void __iomem *mmc_base; mmc_base ioremap(S3C2410_PA_MMC, S3C2410_SZ_MMC); if (!mmc_base) { printk(KERN_ERR ioremap failed\n); return -ENOMEM; } /* 读写寄存器统一使用 readl/writel */ writel(value, mmc_base S3C2410_SDICON); value readl(mmc_base S3C2410_SDICMD);这里有两个知识点ioremap之后的地址要用readl/writel访问不能用指针直接解引用否则编译器可能做怪优化__iomem注解是给 sparse 静态检查用的能帮你在编译期发现地址空间混用的问题。ioremap 失败要记得返回错误不要硬着头皮往下走。6. 模块加载与验证insmod 到 /dev/mmcblk0 的完整链路模块编译好交叉编译环境没问题接下来就是验证环节。建议按以下顺序走# 1. 加载模块 insmod mmc_drv.ko # 2. 查看内核日志 dmesg | tail -30 # 3. 创建设备节点如果 udev 不自动创建 mknod /dev/mmcblk0 b 254 0 # 4. 查看块设备是否出现 cat /proc/partitions设备号 254 是动态分配的如果系统里已经加载了其他块设备驱动主设备号可能不同。/proc/partitions是最可靠的信息来源不要猜设备号直接看系统分配了什么。驱动加载成功后第一件事不是急着 mount而是先读# 读取整个块设备检测读路径是否正常 dd if/dev/mmcblk0 of/dev/null bs512 count2 # 如果返回 EIO先看 dmesg 里有没有 CRC 错误 # 如果正常再测试写路径 dd if/dev/zero of/dev/mmcblk0 bs1024 count16 convfsync读写都通了再分区格式化fdisk /dev/mmcblk0 mkfs.vfat /dev/mmcblk0p1 mount /dev/mmcblk0p1 /mnt/sd性能测试这一步不要跳过。视频解码对存储带宽要求很苛刻连续读至少要能稳定跑到 4MB/s 以上才够用time dd if/dev/mmcblk0 of/dev/null bs4096 count10000这里有个经验bs的值对测试结果影响很大。块设备层有预读机制bs512测出来可能只有几百 KB/sbs4096就能跑满。视频解码场景下系统调用一般是按 4KB 对齐的用bs4096的测法更接近实际负载。验证阶段如果发现性能不达标优先检查mmc_core.c里的传输模式。单块读CMD17和多块读CMD18的性能差距在一个数量级如果驱动只实现了单块读性能差是必然的。mmc_disk.c里如果请求队列没有正确合并 bio每次读请求都被拆成单块处理也会导致性能上不去。这套流程走完之后我建议把 dmesg 的完整启动日志存一份。从那以后我每次拿到一块新板子都是强制先跑一遍完整的 insmod → dd 读 → dd 写 → 格式化 → mount → 大文件拷贝任何一个环节报错都当场记录现象和 dmesg 日志。驱动调试最怕的就是好像可以了——能跑通一次不算通连续跑一百遍不出错才叫稳。这套源码的参考价值在于它把 MMC 协议栈的标准路径展示得很完整你修改时只要对照这条链路排查绝大多数问题都能很快定位。希望帮到你。本文还有配套的精品资源点击获取
返回列表