一块RK3566核心板加一块转接底板,插一张64GB A2 U3 TF卡,系统起来后dd读速度只有40MB/s,写入只有20MB/s。一开始我以为是卡的问题,换了两三张卡都差不多,后来翻内核日志才发现,SDMMC控制器从头到尾就没让卡跑进UHS SDR104模式,一直停在SDR50档位上。RK3566这颗芯片的SDMMC0控制器其实是完整支持SD 3.0 UHS-I SDR104的,理论带宽104MB/s,实际调好后顺序读可以稳定到85-95MB/s。这篇文章就把我从40MB/s调到90MB/s的完整过程拆开讲:SDR104是什么、为什么默认上不去、设备树怎么改、以及调完之后碰到的一堆信号完整性问题。正在做RK3566/RK3568 Linux或Android BSP、想把TF卡性能拉满的开发者,可以直接照着这条思路走。
1. SDR104模式的价值:为什么一张TF卡能跑出接近eMMC的速度
1.1 SD卡UHS-I档位,到底差在哪
在进入调优之前,得先把UHS-I的几个档位弄清楚。SD 3.0规范里引入了UHS-I总线模式,它不是单一的速度标准,而是一组工作模式。常见的是下面这几个:
| 总线模式 | 时钟频率 | 数据线数量 | 理论带宽 | 典型应用 |
|---|---|---|---|---|
| Default Speed (DS) | 25MHz | 4bit | 12.5MB/s | 老卡、读卡器兼容模式 |
| High Speed (HS) | 50MHz | 4bit | 25MB/s | 大多数C10卡 |
| SDR50 | 100MHz | 4bit | 50MB/s | UHS-I卡的中档模式 |
| SDR104 | 208MHz | 4bit | 104MB/s | 高端UHS-I卡 |
| DDR50 | 50MHz DDR | 4bit | 50MB/s | 较少见,兼容性一般 |
SDR104 名字里的104,来源就是 208MHz × 4bit ÷ 8 = 104MB/s。注意SD卡的数据线最多只有4根,没法像eMMC那样走8bit并行,所以想往上提速度只能靠拉高时钟。SDR104把时钟做到208MHz,理论带宽就接近百兆。而普通HS模式只有50MHz,理论上限25MB/s,实际能跑20MB/s左右就算不错了。所以我一直强调,看到TF卡读写只有20MB/s,先别急着怪卡,先去看看当前跑在哪个模式。
1.2 不是所有UHS-I卡都默认跑SDR104
卡面上印着U3、A2、V30这些标识,只能说明卡的主控和闪存颗粒有能力跑高带宽,但最终跑哪个模式取决于主机控制器和卡双方协商的结果。UHS-I卡也不是全都支持SDR104,有些兼容卡最高只支持到SDR50,或者DDR50。一般来说,近几年的A2/U3卡基本都支持SDR104,但“基本”不等于“一定”。我手头有几张卡,同一台设备上枚举出来的模式就不同,一张是SDR104,另一张只到SDR50。
判断卡支持哪些模式,不用查特别复杂的文档,直接看内核枚举日志最省事。如果卡跑在SDR104,dmesg里会出现new ultra high speed SDR104 SD card;如果只支持到SDR50,就会显示new ultra high speed SDR50 SD card。这也是后面调优验证是否生效的最直接证据。
1.3 RK3566的SDMMC控制器能力与实际限制
RK3566是瑞芯微面向AIoT、平板、云终端这类场景的四核Cortex-A55处理器,它内部有独立的SDMMC控制器,用来接TF卡。从寄存器标准和协议支持上讲,RK3566的SDMMC0是完整支持SD 3.0 UHS-I SDR104的,不是那种只能跑到HS模式的阉割版本。eMMC是另一套控制器,别跟SDMMC混在一起。
但“控制器支持”不等于“主板支持”。UHS-I SDR104要求通信信号电压从3.3V切到1.8V,这需要板子上有可调电源,或者电平转换电路。很多低成本开发板虽然把SDMMC0接到了TF卡座,但电源部分只给了3.3V,没有给IO参考电压留可调LDO,这种板子软件上再努力也跑不了SDR104。调优的第一步永远是看原理图,而不是改设备树。
1.4 从CPU到TF卡的完整链路,哪里会限速
一次TF卡读取,数据从卡里出来,经过卡座引脚、电平转换电路、SDMMC控制器、DMA,最后才进到内存里。这条链路上任何一个环节掉链子,最终都会反映在dd的吞吐数字上。我们在嵌入式Linux里能直接控制的主要是设备树里的几个点:时钟频率上限、总线宽度、UHS模式开关、vqmmc电源、pinctrl引脚配置。
如果vqmmc不能切到1.8V,UHS模式直接失效;如果max-frequency被限制在50MHz,就算卡支持SDR104也只能跑HS;如果pinctrl驱动强度不合理,高频下信号波形畸变,就会偶发读写错误。搞清楚这条链路,后面遇到问题就不会一头扎进代码里瞎猜。
2. 默认配置摸底:先确认卡到底工作在哪个档位
2.1 测试环境与卡的选择
我的测试环境是RK3566核心板配一块自研底板,系统用的是Rockchip Linux 5.10 SDK,根文件系统跑在eMMC上,把TF卡挂到/mnt/sd单独测试。TF卡选了一张64GB A2 U3规格的卡,卡面标称读100MB/s写60MB/s。A2一般意味着随机性能不错,U3代表持续写入不低于30MB/s,这样的卡如果跑不满,基本可以排除卡本身太弱的原因。
如果你手头的卡是老旧的C10卡,就算模式调对也可能跑不快,因为卡内部闪存颗粒和主控不支持高频率。所以做调优前先确认手头的卡至少是U1或U3标识,最好能查到官方datasheet里对SDR104的支持。UHS-I卡一般都会支持SDR104,但不是绝对,个别兼容卡只做到SDR50。
2.2 三分钟查清当前模式:debugfs里的ios节点
系统起来之后,我习惯先看两样东西:内核枚举日志和当前IO状态。先挂载debugfs:
mount -t debugfs debugfs /sys/kernel/debug cat /sys/kernel/debug/mmc0/ios注意,我这里SDMMC0对应的是mmc0,如果你的平台编号不同,以实际为准,可能在/sys/kernel/debug/mmc1/ios或mmc2。默认配置下我看到的输出是:
clock: 100000000 Hz vdd: 21 (3.3 ~ 3.4 V) bus mode: 2 (push-pull) chip select: 0 (don't care) power mode: 2 (on) bus width: 2 (4 bits) timing spec: 3 (sd uhs SDR50) signal voltage: 2 (1.80 V) driver type: 0 (driver type B)关键看两个字段:timing spec和signal voltage。timing spec: 3表示卡跑在SDR50,signal voltage: 1.80 V表示IO电平已经切到1.8V,说明硬件vqmmc链路是通的。再看dmesg:
dmesg | grep mmc0里面有这么一行:
mmc0: new ultra high speed SDR50 SD card at address 0001这就实锤了:卡本身支持UHS,但内核只协商到了SDR50,没往上走。
2.3 用dd和hdparm摸底,记录基线数据
接着用两个命令把当前性能记录下来:
hdparm -t /dev/mmcblk0 dd if=/dev/mmcblk0 of=/dev/null bs=1M count=1024 iflag=direct我测出来的读速度稳定在40MB/s左右,写入大概20MB/s。SDR50的时钟是100MHz,四线理论带宽50MB/s,实际效率80%左右,40MB/s对SDR50来说很正常。如果某天你看到TF卡读写只有20MB/s,那基本可以判断卡跑在HS模式,因为HS理论上限就是25MB/s。
dd测读一定要加iflag=direct,否则读的是page cache,数字会虚高,测出来的不是卡的性能。写入的命令我后面单独讲,因为写操作还会被卡内部缓存影响,测试姿势不对,数据会被带偏。
2.4 默认为什么只停在SDR50
改之前我特意看了SDK自带的设备树,发现问题出在sdmmc0节点里:厂商默认只开了cap-sd-highspeed和sd-uhs-sdr50,sd-uhs-sdr104是注释掉的。这种配置策略很常见,目的是为了保证最广泛的卡兼容性,同时避免高频信号问题给用户带来售后麻烦。
对产品开发来说,默认保守不是坏事,毕竟不是每块底板都能在208MHz下稳定工作。但我们自己做BSP调优,目的就是把硬件能力吃满。所以下一步就是改设备树,把SDR104能力声明打开,然后根据实测信号质量决定要不要上208MHz。
3. 设备树与内核配置:把SDR104模式真正打开
3.1 先确认板子的硬件能不能切1.8V
UHS-I模式有一个硬门槛:工作信号电压要从3.3V切到1.8V。这是协议标准步骤,但需要硬件上有一个能输出3.3V/1.8V可切换的电源,接到了TF卡的IO参考电压上。常见做法是用PMIC的LDO输出,或者一颗独立的电平转换芯片。
查原理图的时候重点看TF卡座旁边有没有类似VQMMC、IO_VDD、SD_VCC_IO这样的网络,跟着网络找它是不是接到了RK809或其他PMIC的可调LDO上。如果这个网络直接跟3.3V短接,那改设备树也白搭,硬件不支持UHS,最多跑SDR50。我第一次就是在一张没有vqmmc的底板上面折腾了大半天,最后看原理图才发现供电被短接了。
3.2 设备树节点关键属性逐行解释
确认硬件支持之后,打开设备树,找到sdmmc0节点。我最终用的配置长这样:
&sdmmc0 { status = "okay"; bus-width = <4>; cap-sd-highspeed; sd-uhs-sdr104; sd-uhs-sdr50; supports-sd; disable-wp; max-frequency = <150000000>; vmmc-supply = <&vcc3v3_sd>; vqmmc-supply = <&vccio_sd>; pinctrl-names = "default", "sleep"; pinctrl-0 = <&sdmmc0_clk>, <&sdmmc0_cmd>, <&sdmmc0_bus4>; pinctrl-1 = <&sdmmc0_clk_sleep>, <&sdmmc0_cmd_sleep>, <&sdmmc0_bus4_sleep>; };逐行说关键点:
bus-width = <4>:TF卡最多四条数据线。不要写成8,那是eMMC用的。cap-sd-highspeed:允许SD卡协商High Speed模式,是基础能力,不开它后面UHS也悬。sd-uhs-sdr104和sd-uhs-sdr50:让SDHCI驱动把能力位上报给MMC子系统,枚举时才能和卡协商对应模式。max-frequency = <150000000>:限制CLK时钟最高150MHz。这里刻意没写208MHz,原因后面信号完整性那节讲。vmmc-supply:TF卡主电源,一般3.3V。vqmmc-supply:IO参考电压,UHS切换的关键,必须能3.3V/1.8V切换。pinctrl-0/pinctrl-1:配置CLK、CMD、D0-D3引脚的工作和睡眠状态,睡眠时拉成高阻,省电也防漏电。
很多SDK里sd-uhs-sdr104默认是注释掉的,或者只有cap-sd-highspeed,所以速度一直上不去。
3.3 改完设备树还要重新编译什么
设备树不是独立于内核的,修改之后需要重新编译设备树并打包进固件。不同SDK流程不太一样,我这边用的是Rockchip Linux SDK,大致流程是重新编译boot.img,然后通过fastboot或者升级工具烧录。
./build.sh bootimg ./build.sh updateimg如果是纯设备树开发,也可以只编dtb,然后替换到boot分区。不过RK平台的boot.img一般把kernel和dtb打包在一起,所以直接重编boot.img更省事。改完后重新启动,再用同样的debugfs命令确认。
3.4 内核配置项检查,避免驱动能力缺失
设备树不是全部,内核的MMC子系统相关配置也得确认。Rockchip Linux SDK里一般默认开了:
CONFIG_MMC=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_PLTFM=y CONFIG_MMC_SDHCI_OF_ROCKCHIP=y可以用grep -i "MMC_SDHCI_OF_ROCKCHIP" .config验证一下。如果没开,需要make menuconfig勾选后重新编译内核。这一步一般没人踩坑,因为SDK默认都带,但如果你是从零配置的内核,别漏掉。
4. 从40MB/s到90MB/s:时钟频率、信号完整性与稳定性调优
4.1 修改后第一次验证:dmesg和ios的变化
设备树改完,重新编译boot.img刷进板子,启动后先看dmesg和ios。这次dmesg里出现的是:
mmc0: new ultra high speed SDR104 SD card at address 0001debugfs里的timing spec也变成了9 (sd uhs SDR104),clock是150MHz。我用同样的dd命令再测,顺序读从40MB/s跳到了68-72MB/s左右,写从20MB/s到了30MB/s出头。这一步说明SDR104已经生效,但因为max-frequency限制在150MHz,理论带宽75MB/s,实测70左右基本合理。
如果你把max-frequency改成208000000,理论带宽104MB/s,读可以到85-95MB/s。但别高兴太早,高频下如果板子信号质量不行,会出现各种偶发错误。
4.2 208MHz的坑:tuning失败与信号完整性
SDR104的208MHz时钟,加上沿口比较陡,对PCB走线的阻抗连续性、卡座接触、线长差都很敏感。走线稍长一点、过孔多两个、地平面被切断一段,眼图可能就闭合了。内核在UHS模式会做tuning,也就是扫描采样点,找一个能稳定采数据的相位。如果找不到合适的点,常见的表现是:
- dmesg里刷
mmc0: tuning execution failed - 读大文件时随机出现
mmc0: data error或I/O error - 卡有时能枚举成功,有时只能在HS模式跑
我在这块底板上试了208MHz,顺序读能到90MB/s,但往卡里写一个2GB文件,跑到一半出现一次mmc0: Timeout waiting for hardware interrupt,然后文件校验失败。这说明tuning虽然过了,但余量不够,一遇到温度变化或者卡与卡之间的差异就会掉链子。
4.3 用pinctrl驱动强度给信号“补一口氧”
驱动太弱,信号沿太缓,采样失败;驱动太强,振铃过冲,一样失败。高频不稳时,第一件事是调整SDMMC引脚组的驱动强度。Rockchip的pinctrl驱动可以通过设备树配置驱动强度档位,具体数值以你的SDK和RK3566 TRM为准,一般是一个整数档,从低到高对应不同毫安数。
我当时在默认档位下208MHz不稳,把CLK/CMD/DATA的驱动强度往上调了半档后,再跑2GB写入校验,日志干净了。不同板子结论可能不同,但思路是一样的:从默认值开始,逐级往上试,每次改完都跑一轮长时间读写,别只看一次dd的读数。信号完整性本来就是试出来的,没有捷径。
4.4 max-frequency选150MHz还是208MHz
如果你追求稳定量产,我建议150MHz。75MB/s的读速度对TF卡场景已经很够用,而且余量大很多。208MHz的收益虽然可观,但卡座老化、温度变化都可能让本来勉强工作的tuning点失效。别为了一点数字把稳定性搭进去。
如果你做的是性能测试板或者自己玩,可以试试208MHz,并配合驱动强度调整和长时间压力测试。最终我的量产配置固定在150MHz,因为连续拷3天数据没有一次报错。速度后面可以慢慢优化,数据丢了不是开玩笑。
4.5 CPU频率、DMA与文件系统的影响
有时候模式对了,数字还是难看,就要看周边因素。测速前把CPU放到performance governor,避免高负载掉频影响测试公平性:
cpufreq-set -g performance挂载TF卡时加上noatime:
mount -o noatime /dev/mmcblk0p1 /mnt/sd分区是否1MB对齐影响4K随机性能,顺序读写影响小一点。文件系统也有关系,ext4和exfat跑出来的顺序写可能相差不小,因为文件系统分配策略和卡内部FTL都会参与。我最后用的ext4,数据可靠性也更好一些。
5. 性能验证方法:dd、hdparm、iozone到底该怎么看
5.1 顺序读写的标准测试姿势
调优之后不能只靠一次dd就下结论。我的标准顺序读测试是这样做的:
sync echo 3 > /proc/sys/vm/drop_caches hdparm -t /dev/mmcblk0 dd if=/dev/mmcblk0 of=/dev/null bs=1M count=1024 iflag=direct先清页缓存,再用hdparm看一次原始读,最后用dd带direct I/O再确认一遍。顺序写测试要小心,别把数据写到系统盘,建议在TF卡上挂载后写文件:
dd if=/dev/zero of=/mnt/sd/test.bin bs=1M count=1024 oflag=direct conv=fsync rm /mnt/sd/test.bin如果只测吞吐但不管数据是否落盘,conv=fsync可以保证dd返回时数据已经写进去,而不是躺在卡里缓存。卡内部有写缓存时,第一次写会快得离谱,连续写几轮之后才会看到真实速度。
5.2 4K随机性能对嵌入式场景更重要
很多嵌入式场景不是连续拷贝大文件,而是TF卡里存数据库、Qt缓存、日志文件。顺序读再快,4K随机读写拉胯,体验一样卡。我平时会用fio测一下4K随机混合读写:
fio --filename=/mnt/sd/fio.test --size=100M --rw=randrw --bs=4k --iodepth=16 --numjobs=4 --runtime=30 --group_reporting --name=test注意fio测试文件会占空间,测完记得删。A2标称的卡在4K随机上优势明显,SDR104模式对随机性能也有帮助,因为时钟高了之后单笔传输时间缩短,队列深度下吞吐更容易上去。如果SDK里没带fio,直接用iozone也一样,关键是看随机读写的IOPS和延迟,而不是只盯着顺序吞吐。
5.3 长时间稳定性测试怎么跑
我遇到的最坑的场景是短测全好,长测偶尔失败。SDR104即使tuning通过,采样余量不足时,偶发错误会在温度升高或者卡温上来后暴露。所以最后一定要做压力测试。
我的做法是写一个简单循环,连续向TF卡写一个1GB文件,每次都回读并md5校验,循环几十轮:
for i in $(seq 1 50); do dd if=/dev/urandom of=/mnt/sd/test.bin bs=1M count=1024 conv=fsync sync md5sum /mnt/sd/test.bin done同时开着终端监控dmesg -w,如果刷出CRC error、timeout、tuning retry之类的字眼,说明信号余量不够,该降频或调驱动强度了。一个稳定的SDR104配置,连续跑一晚不应该在dmesg里产生任何MMC错误。
5.4 结果解读:别拿dd的瞬时速度当结论
dd跑一次的结果只代表当前这一瞬间的带宽,受卡缓存、文件系统状态、后台进程影响很大。我见过有人拿一次漂亮的dd数字到处说调好了,结果压力测试一跑就露馅。判断调优是否成功,至少要满足三个条件:第一,dmesg里明确显示卡工作在目标模式;第二,顺序读写的多次测试结果波动小;第三,长时间循环写读没有产生任何MMC错误日志。
如果你调完SDR104,dd读到80多MB/s,但跑几分钟就报I/O error,那这不算调好,只能算“看起来调好了”。实际产品里,稳定性优先级远高于跑分。
6. 踩过的坑和最终可复现参数
6.1 坑一:vqmmc-supply没配,UHS永远不生效
这个坑真的太常见。很多参考设计里vmmc-supply有,但vqmmc-supply缺失。结果就是枚举时内核不敢切1.8V,卡最高只能跑到HS模式,速度20MB/s。排查方法还是看debugfs的ios,signal voltage如果一直是3.3V,就算dts里写了sd-uhs-sdr104也没用。
一定要确保设备树里vqmmc-supply指向的regulator支持set_voltage,并且驱动在运行时能把它从3.3V切到1.8V。PMIC的LDO如果被固定输出,也会导致切换失败。
6.2 坑二:208MHz tuning失败,系统悄悄回退
还有一种情况是dts开了SDR104,但卡插上去之后dmesg里new ultra high speed SDR104 SD card始终不出现,只有SDR50。最常见的原因是tuning失败后内核自动降档。把内核日志级别调高,能看到:
mmc0: tuning execution failed解决方案不外乎两条:降max-frequency到150MHz,或者调整pinctrl驱动强度。如果板子已经量产不能动硬件,那就别死磕208MHz。
6.3 坑三:卡检测GPIO配置错误导致热插拔异常
这个和速度调优关系不大,但很影响调试体验。有的底板卡检测引脚没有接到GPIO,或者接反了极性,导致插卡后内核反复 remove/add mmc0。排查方法是看dmesg里有没有反复出现mmc0: card 0001 removed。如果频繁插拔检测不稳定,速度调再快也白搭。
设备树里可以用cd-gpios指定检测脚,或者直接用broken-cd声明不支持热插拔,让系统认为卡永远在槽里。对大多数嵌入式产品来说,TF卡基本是出厂装好的,broken-cd其实是更省心的选择。
6.4 最终设备树配置与性能结果
最终我留在项目里的配置就是3.2节那个片段,max-frequency保守用了150MHz。用同一张64GB A2 U3卡在不同档位下测得的结果如下:
| 模式 | 时钟 | 理论带宽 | 实测顺序读 | 实测顺序写 |
|---|---|---|---|---|
| HS | 50MHz | 25MB/s | 22MB/s | 18MB/s |
| SDR50 | 100MHz | 50MB/s | 40MB/s | 22MB/s |
| SDR104(150MHz) | 150MHz | 75MB/s | 68-72MB/s | 30-35MB/s |
| SDR104(208MHz) | 208MHz | 104MB/s | 85-93MB/s | 32-38MB/s |
读取速度的梯度很明显,从HS到SDR104 208MHz,几乎翻了四倍。写入一直偏低的原因是我这张卡写入本身一般,跟模式关系小一点。不同卡结果会不一样,但读取速度的梯度很有参考价值。
6.5 快速排查对照表
后续再遇到类似问题,我会直接按这个思路查:
| 现象 | 可能原因 | 检查方法/对策 |
|---|---|---|
| 速度一直20MB/s左右 | 跑在HS模式,vqmmc没配置 | cat /sys/kernel/debug/mmc0/ios,检查signal voltage |
| 速度40MB/s左右 | 只协商到SDR50,SDR104未开启 | dmesg确认枚举模式,设备树加 sd-uhs-sdr104 |
| SDR104模式偶发I/O error | 信号完整性余量不足 | 降max-frequency,调整pinctrl驱动强度 |
| 卡有时识别有时不识别 | 卡检测GPIO配置异常 | 检查cd-gpios极性,或改用broken-cd |
| 读速正常写入很低 | TF卡本身写性能瓶颈 | 换一张写入标称更高的卡测试 |
这张表是我这次调优过程中实际用到的排查路径,先看模式,再看供电,再看信号,最后才怀疑卡。
6.6 最后分享一点个人体会
调完这一轮,我最大的感受是:在RK3566平台上让TF卡跑上SDR104,真正卡人的往往不是SoC,而是板级的电源和走线设计。软件能做的只是把硬件能力如实报给内核,然后根据信号质量去平衡速度和稳定性。如果你手头板子的vqmmc能切1.8V,照着这个思路改设备树,大概率很快能看到dmesg里出现new ultra high speed SDR104 SD card;如果硬件不支持,也别折腾了,SDR50的40MB/s可能就是这个板子的上限。测试工具上,优先相信长时间压力测试,而不是dd那一次漂亮的结果。后面如果再调eMMC的HS400或者SDIO WiFi接口,思路也是类似的,先确认硬件能不能撑住高频,再谈模式切换。