1. 从一次SD卡识别异常说起:RK3576的CD检测到底卡在哪
拿到RK3576这块板子的时候,我第一反应是"这平台接口真全"——双千兆网口、多路MIPI、PCIe、USB3.0一应俱全,做边缘计算盒子或者工业网关都挺合适。但真正上手第一天,我就被一个看起来最不起眼的功能绊住了:SD卡的插拔检测。
事情是这样的。板子跑的是嵌入式Linux,系统本身从eMMC启动没问题,串口终端也能正常登录。我插了一张32GB的TF卡,准备挂载上去做数据存储。结果lsblk看不到任何新设备,dmesg里也没有mmc相关的枚举日志。拔出来再插,还是没反应。换了一张卡,一样。这时候我意识到,问题不在卡上,而在板子对"卡插入"这件事的感知上。
SD卡能不能被系统识别,第一步不是看驱动,而是看CD(Card Detect)信号有没有被正确捕获。CD是SD卡座上的一个物理引脚,卡插进去时它会接地或拉高,SoC通过这个电平变化判断"有卡来了"。如果CD信号没接对、没配置对、或者被软件屏蔽了,那后面的SDIO枚举流程根本不会启动,你在用户空间怎么折腾都是白费。
RK3576的SDMMC控制器支持CD检测,但它的实现方式和很多老平台不太一样。老平台比如一些早期的ARM SoC,CD往往直接连到GPIO,驱动里写个中断就行。而RK3576的CD可以走SDMMC控制器的内部CD采样逻辑,也可以走外部GPIO,具体取决于硬件设计和设备树怎么配。我踩的第一个坑,就是没搞清楚这块板子到底用的是哪种方式,直接照着网上搜到的"通用RK平台SD卡配置"去改,结果方向完全错了。
提示:遇到SD卡不识别,先别急着改驱动。第一步永远是确认硬件上CD信号接到了哪里,是SDMMC专用CD引脚,还是普通GPIO,还是干脆没接(靠软件轮询)。这个信息决定了你后面所有配置的走向。
我后来翻原理图才发现,这块板子的TF卡座CD脚接的是GPIO3_B5,而不是SDMMC控制器的专用CD输入。这就意味着,设备树里必须把这个GPIO配成cd-gpios,并且驱动要能正确处理它的中断。而我之前参考的配置里写的是broken-cd(表示没有CD检测,靠轮询),两者行为完全不同——一个靠中断即时响应,一个靠定时轮询,延迟和功耗都不一样。
这个发现让我重新梳理了整个链路:硬件连接 → 设备树描述 → 驱动解析 → 控制器初始化 → 卡枚举。任何一环对不上,卡就是认不出来。下面我把这条链路上我实际踩过的坑和验证方法,按顺序拆开讲。
2. 设备树里cd-gpios和broken-cd的区别,以及配错之后的真实表现
设备树是嵌入式Linux里硬件描述的"总纲",SD卡能不能被正确识别,八成问题出在这一层。RK3576的SDMMC节点在arch/arm64/boot/dts/rockchip/rk3576.dtsi里定义,板级dts再覆盖具体参数。我一开始犯的错,就是没区分清楚几个关键属性的语义。
2.1 cd-gpios、broken-cd、non-removable三者的适用场景
这三个属性经常被混用,但它们描述的是完全不同的硬件形态:
| 属性 | 含义 | 适用硬件 | 驱动行为 |
|---|---|---|---|
cd-gpios | 指定CD检测用的GPIO | 卡座CD脚接到普通GPIO | 注册GPIO中断,插拔触发枚举/移除 |
broken-cd | 声明CD信号不可用 | 卡座无CD脚或CD未接 | 定时轮询,周期检测卡状态 |
non-removable | 卡不可移除 | eMMC、焊接式存储 | 启动时枚举一次,不检测插拔 |
我那块板子CD接的是GPIO3_B5,正确写法应该是:
&sdmmc { status = "okay"; cd-gpios = <&gpio3 RK_PB5 GPIO_ACTIVE_LOW>; /* 其他参数 */ };而我最初抄来的配置写的是broken-cd;,驱动就按轮询模式跑。轮询模式下,如果轮询周期设得长(默认可能几百毫秒到秒级),插卡后要等一会儿才识别;更关键的是,如果硬件上CD脚是接着的,但你没告诉驱动,驱动可能在某些状态下误判卡的存在,导致枚举失败。
实测表现上,broken-cd配置下插卡,dmesg里会看到周期性的mmc检测日志,但卡始终枚举不出来,因为控制器在等待一个它认为"卡已就位"的稳定状态,而实际电平变化它没通过中断及时捕获。换成cd-gpios后,插卡瞬间就能看到mmc1: new high speed SDHC card这类日志。
2.2 GPIO_ACTIVE_LOW还是HIGH,取决于卡座电路
GPIO_ACTIVE_LOW和GPIO_ACTIVE_HIGH这个极性,很多人随手写一个,结果就是插卡没反应、拔卡反而触发。它的依据是卡座CD脚的电路设计:
- 如果卡座CD脚在无卡时通过上拉电阻拉高,插卡时接地,那有效电平是低,写
GPIO_ACTIVE_LOW。 - 反之如果无卡时下拉到地,插卡时拉高,写
GPIO_ACTIVE_HIGH。
我拿万用表量过:无卡时GPIO3_B5对地电压3.3V,插卡后变成0V。所以是低有效,GPIO_ACTIVE_LOW正确。如果你写反了,现象很诡异——系统认为"卡一直插着",启动时就尝试枚举,但实际没卡,枚举超时;真插卡时反而认为卡被拔了。
注意:极性判断不要靠猜,用万用表或示波器量一下CD脚在插拔两种状态下的电平,这是最靠谱的。原理图上如果有标注"CD#",通常意味着低有效。
2.3 引脚复用(pinctrl)没配对,GPIO根本不工作
RK3576的引脚功能高度复用,GPIO3_B5要作为普通GPIO使用,必须在pinctrl里把它从其他功能(比如某个外设的专用脚)切回来。我遇到的情况是,dts里cd-gpios写了,但pinctrl没配,结果这个脚还停留在默认的复用功能上,GPIO中断永远不触发。
正确的做法是在sdmmc节点里引用一个pinctrl状态:
&sdmmc { pinctrl-names = "default"; pinctrl-0 = <&sdmmc_cd_pin>; }; &pinctrl { sdmmc { sdmmc_cd_pin: sdmmc-cd-pin { rockchip,pins = <3 RK_PB5 RK_FUNC_GPIO &pcfg_pull_up>; }; }; };这里RK_FUNC_GPIO表示切到GPIO功能,pcfg_pull_up是上拉配置。如果漏了这段,或者rockchip,pins里的bank、pin、func写错,CD检测就是死的。我排查时用cat /sys/kernel/debug/gpio看GPIO3_B5的状态,发现它显示的功能不是GPIO,才定位到pinctrl问题。
3. 从dmesg到debugfs:一步步定位SD卡枚举失败的真实原因
设备树改对之后,我以为万事大吉,结果插卡还是时好时坏。这时候就不能靠猜了,得用系统提供的调试手段把整条链路的状态看清楚。我总结了一套从粗到细的排查顺序,基本能覆盖SD卡不识别的绝大多数情况。
3.1 第一层:dmesg里的mmc日志告诉你卡走到哪一步
dmesg | grep -i mmc是最快的信息来源。正常插卡应该看到类似这样的序列:
mmc1: SDHCI controller on ... using ADMA mmc1: new high speed SDHC card at address 0007 mmcblk1: mmc1:0007 SD32G 29.7 GiB如果只看到控制器注册,没有new ... card,说明卡枚举没启动或失败了。常见日志和含义:
mmc1: card detect gpio not found:cd-gpios没配或GPIO申请失败。mmc1: timeout waiting for hardware interrupt:控制器和卡通信超时,可能是时钟、电压或信号完整性问题。mmc1: error -110 whilst initialising SD card:-110是ETIMEDOUT,枚举阶段超时,重点查供电和时钟。- 完全没有mmc1相关日志:控制器status不是okay,或者根本没使能。
我那次"时好时坏"的现象,dmesg里偶尔出现error -110,偶尔又正常。这种间歇性问题最烦人,但也最有排查价值。
3.2 第二层:debugfs看控制器和卡的实时状态
内核开了CONFIG_DEBUG_FS后,/sys/kernel/debug/mmc1/下面有一堆有用的文件:
ios:当前IO设置,包括时钟频率、总线宽度、电压。如果时钟是0或者电压不对,枚举肯定失败。err_stats:错误统计,能看到CRC错误、超时次数。state:卡的状态机当前状态。
我通过cat /sys/kernel/debug/mmc1/ios发现,正常时clock是50000000(50MHz),出问题时clock显示400000(400kHz,枚举初始频率)且一直不往上走。这说明卡在识别阶段(400kHz)就没通过,根本没进入高速模式。进一步查err_stats,CRC错误计数在涨,指向信号完整性问题。
3.3 第三层:示波器看SDIO信号,确认是硬件还是软件
软件层面查到最后,如果怀疑信号问题,就得上示波器了。SDIO的关键信号有CLK、CMD、DAT0-3。我重点看了两点:
- CLK频率:枚举阶段应该是400kHz,识别后升到50MHz。如果CLK一直是400kHz,说明卡没被正确识别。
- CMD线上的响应:主机发命令后,卡应该在约定时间内拉低CMD线响应。如果看不到响应,要么卡没供电,要么CMD线被拉死。
我实测发现,出问题时CMD线上主机的命令波形正常,但卡的响应时有时无。结合CRC错误,判断是DAT/CMD线的上拉电阻或走线有问题。后来检查PCB,发现CMD线的上拉电阻焊盘有点虚焊,补焊之后间歇性问题消失。
提示:SDIO信号对走线和上拉很敏感。CMD和DAT线通常需要10k-50k的上拉电阻,CLK不需要上拉。如果硬件是自己设计的,这几根线的等长和阻抗要留意。
3.4 第四层:供电和电压切换,SD卡对电源很挑剔
SD卡规范要求初始化时用3.3V,高速模式可以切到1.8V(UHS-I)。RK3576的SDMMC控制器支持电压切换,但如果供电电路(LDO或负载开关)响应慢,或者电压切换时序不对,卡就会枚举失败。
我遇到过一次,卡在3.3V下能识别,但一切到1.8V就掉。查下来是板子上SD卡供电的LDO使能脚接错了,切换时LDO被意外关断。这种问题在dmesg里表现为mmc1: switching to 1.8V failed或者直接超时。解决办法是检查供电电路的使能逻辑,确保电压切换时供电不中断。
排查到这里,SD卡识别的问题基本能定位到具体环节了。但识别只是第一步,真正让SD卡在系统里稳定可用,还有挂载、性能、热插拔这些事要处理。
4. 卡认出来之后:挂载、性能与热插拔的连锁问题
卡能被lsblk看到,只说明块设备注册成功了。要真正用起来,还得过挂载、文件系统、性能、热插拔这几关。这几步里也有不少坑,而且很多是"卡能认但用着用着出问题"的类型。
4.1 自动挂载:udev规则还是fstab,选错场景很尴尬
嵌入式系统里SD卡挂载有两种常见做法:
- fstab静态挂载:在
/etc/fstab里写死设备节点和挂载点。适合卡长期插着不拔的场景,比如数据盘。缺点是卡不在时启动会报错,需要加nofail选项。 - udev/mdev规则动态挂载:卡插入时自动挂载,拔出时自动卸载。适合需要热插拔的场景。
我一开始用fstab,结果卡没插的时候系统启动卡在mount那一步,虽然加了nofail不阻塞启动,但每次都要手动挂。后来改用udev规则:
KERNEL=="mmcblk1p1", SUBSYSTEM=="block", ACTION=="add", RUN+="/bin/mount /dev/mmcblk1p1 /mnt/sdcard" KERNEL=="mmcblk1p1", SUBSYSTEM=="block", ACTION=="remove", RUN+="/bin/umount /mnt/sdcard"这里有个细节:RUN+=里的命令要尽量简单,udev环境里很多路径和变量和正常shell不一样。复杂逻辑建议写个脚本,udev只负责调用脚本。
4.2 文件系统选择:exFAT、ext4还是FAT32
SD卡用什么文件系统,取决于用途:
- FAT32:兼容性最好,Windows/Linux/相机都能读,但单文件不能超过4GB。
- exFAT:支持大文件,跨平台好,但Linux下需要额外驱动(内核5.4+原生支持较好)。
- ext4:Linux下性能和可靠性最好,支持权限、日志,但Windows不认。
我做数据记录用ext4,因为要频繁写入小文件,ext4的日志和延迟分配能减少碎片。但要注意,ext4默认的commit间隔是5秒,突然断电可能丢最近几秒数据。对可靠性要求高的场景,可以调小commit或者用sync挂载(性能会降)。
4.3 性能调优:SD卡在嵌入式Linux下的读写瓶颈
SD卡的读写性能受几个因素影响:总线宽度(1bit/4bit)、时钟频率、文件系统、挂载选项。RK3576的SDMMC支持4bit和50MHz,理论带宽25MB/s左右,实际受卡本身限制。
我用dd测过:
# 写测试 dd if=/dev/zero of=/mnt/sdcard/test.bin bs=1M count=100 conv=fsync # 读测试 dd if=/mnt/sdcard/test.bin of=/dev/null bs=1M实测写入约18MB/s,读取约22MB/s,对于Class10的卡算正常。如果明显偏低,检查:
- 总线宽度是不是4bit(
cat /sys/kernel/debug/mmc1/ios看bus width)。 - 时钟是不是50MHz。
- 挂载选项有没有加
noatime(减少元数据写入)。
4.4 热插拔:拔出时正在写入怎么办
热插拔最怕的是拔出瞬间还有数据在写,轻则文件损坏,重则文件系统崩溃。Linux的块设备层有缓存,umount会先刷缓存再卸载。但用户直接拔卡,缓存没刷就断了。
缓解办法:
- 挂载时用
sync选项,每次写都同步,性能差但安全。 - 应用层定期
sync,比如数据记录程序每写一批就调一次。 - 用
fsck在挂载前检查,ext4可以配errors=remount-ro,出错时只读挂载避免扩散。
我现在的做法是,数据记录程序每5秒fsync一次,配合ext4的日志,即使直接拔卡,最多丢5秒数据,文件系统不会崩。
5. 那些文档不会写的实操心得与避坑清单
踩完这一圈,我把RK3576上SD卡相关的经验整理成几条,都是文档里不会明说、但实际会卡住你的点。
5.1 先量电平再改代码,硬件确认永远排第一
我最大的教训就是一开始没量CD脚电平,直接改dts,来回折腾了好几轮。后来养成习惯:任何和GPIO、电平相关的功能,先拿万用表量,确认硬件行为,再动软件。这一条能省掉至少一半的无效调试时间。
5.2 设备树改动后,确认真的生效了
dts改完编译进dtb,但有时候你改的节点没被板级dts覆盖,或者编译产物没更新。验证方法:
# 看运行时设备树里sdmmc节点的实际内容 ls /proc/device-tree/ | grep mmc cat /proc/device-tree/mmc@.../cd-gpios如果/proc/device-tree里没有你加的属性,说明dtb没生效,检查编译和烧录流程。
5.3 保留一份"最小可用"配置作为对照
调试时我习惯保留一份确认能工作的最小dts配置,出问题时切回去对比。这样能快速判断是新改的参数引入的问题,还是环境本身的问题。SD卡这块,最小配置就是status = "okay"加正确的cd-gpios和pinctrl,其他先不加。
5.4 注意内核版本差异,RK3576的支持在持续更新
RK3576是比较新的平台,不同内核版本(比如5.10、6.1)对它的SDMMC驱动支持程度不一样。我用的6.1内核,某些早期版本里SDMMC的CD中断处理有bug,升级到较新的小版本后问题消失。如果遇到诡异行为,先确认内核版本,必要时看Rockchip的官方补丁或社区提交。
5.5 热插拔测试要覆盖"拔出时正在写"的极端场景
很多人测热插拔就是插进去、读一下、拔出来,这测不出问题。真正要测的是:正在dd写入时直接拔卡,看文件系统是否损坏、重新插入后能否正常挂载。这个测试能暴露缓存、日志、错误处理的问题。我第一次测就把一个ext4分区搞成了需要fsck修复的状态,后来加了fsync才稳。
5.6 供电不足的表现往往伪装成"信号问题"
SD卡瞬时电流可能到100mA以上,如果板子供电设计余量不足,插卡瞬间电压跌落,表现可能是枚举超时、CRC错误,看起来像信号问题,实际是电源问题。用示波器看SD卡供电脚在插卡瞬间的波形,如果有明显跌落,先解决供电。
6. 写在最后:嵌入式调试的耐心比技巧更重要
RK3576这块板子整体素质不错,接口丰富、性能也够,但新平台总会有些细节需要自己趟。SD卡这个事看起来小,却把硬件、设备树、驱动、文件系统、应用层串了一遍,是个很好的练手案例。
我现在回头看,最大的收获不是解决了某个具体bug,而是建立了一套排查顺序:先确认硬件电平,再看设备树是否生效,然后dmesg定位阶段,debugfs看细节,最后示波器确认信号。这个顺序能覆盖大部分外设不工作的问题,不只是SD卡。
如果你也在RK3576或者类似平台上折腾SD卡,建议从CD检测这个点切入,把整条链路走通一遍。走通之后,你对嵌入式Linux的存储子系统会有完全不一样的理解。至于那些热词里提到的各种"初始化"问题,本质上都是链路某一环没对齐,耐心一层层剥,总能找到。