
最近接手了一个有点典型的问题一款基于 MTK 平台的 Android 8.1 设备客户反馈插上 512GB 的 SD 卡后系统只在“便携式存储”里识别到 128GB剩余空间怎么都看不到。卡拿到读卡器上在电脑端完全正常文件也能读到说明卡本身没问题。顺着链路查下来问题出在驱动能力和上层配置没有跟着容量走底层把容量钳在了 128GB上层 vold 和 framework 也没有对更大的存储卡做适配。最终做了 SD 卡驱动能力修改才让设备完整识别并正常使用大容量卡。这个项目听起来不复杂真正动手改的时候却牵扯到 kernel、vold、framework 三层。如果你是在做 MTK 方案的系统工程师或者自己折腾 Android 板子建议先把我下面这套排查流程过一遍。尤其是“识别到容量不对”这种问题千万别一上来就怀疑卡或者卡座也别急着改驱动代码先把信息定位准确。1. 这次项目到底在改什么别把SD卡能力修改想简单了1.1 现象背后的三个问题表面上看问题就是“512GB 卡被识别成 128GB”。但直接插上卡观察你会发现三个独立的问题同时存在第一内核块设备层把卡容量钳住了块设备总扇区数变成 268435456也就是 128GB 对应的扇区数。第二上层设置界面显示的也是这个错误容量说明 framework 拿到的数据同样被截断。第三卡本身已经格式化成 exFAT但系统对 exFAT 的支持并没有完全打开导致就算容量改对了挂载也可能失败或者变成只读。所以“SD 卡驱动能力修改”这句话不是只改驱动一个文件就能完事的。它至少包括驱动层的容量解禁、块设备的容量上报、vold 的挂载策略、framework 的容量展示以及文件系统的适配。少改任何一个环节最终表现都可能是“改了但没全好”。1.2 容量从寄存器到设置界面的完整链路要定位这个问题得先把 SD 卡容量是怎么跑到设置界面的链路理清楚。SD 卡上电后host 控制器通过 CMD0、CMD2、ACMD41 等命令和卡通信卡返回 OCR、CSD 等寄存器。内核 mmc 核心解析 CSD 里的 C_SIZE 字段得到总扇区数然后赋值给card-capacity。接着 mmc block 驱动调用set_capacity()把这票扇区数注册到块设备上于是/sys/block/mmcblk1/size里就能看到这个值。再往上vold 读/dev/block/mmcblk1p1的分区信息和文件系统类型挂载到/mnt/media_rw然后通过 binder 把磁盘信息上报给StorageManagerService最后设置界面从DiskInfo里读出 totalBytes 显示出来。这条链路上任何一环做了容量截断用户看到的就是“容量不对”。常见截断点有三个内核 mmc 核心的私有补丁、vold 里的私有判断、framework 上报时强转成低位数类型。所以我当时判断的顺序很简单先看/sys/block/mmcblk1/size如果这里已经是 268435456那问题基本锁定在内核层不用先怀疑 vold。1.3 为什么不能只改一个地方有的同事一开始提出把内核里那个 128GB 的判断注释掉不就行了我反对。原因很现实厂商SDK 里经常在上层也加了对应策略。你只改驱动vold 可能还是会拒绝超过 128GB 的卷或者 framework 在计算统计信息时用了一个int接收容量超过 2GB 就溢出。改一个地方只能在“某一次重启后碰巧看着正常”一旦用户格式化、热插拔或者写入大文件问题就又会冒出来。我习惯打一个比方SD 卡容量像水管里的水驱动决定源头能放出多少水vold 决定这个水龙头能不能接到水framework 决定水表上显示多少吨。三节管子任何一节口径小最终水表都不准。所以这次修改按“底层到上层”的顺序推进先驱动再 vold再 framework最后验证文件系统。2. 动手前先定位容量限制到底藏在哪一层2.1 先看底层证据/sys/block 和 dmesg不管拿到什么项目我先跑三条命令adb shell cat /sys/block/mmcblk1/size adb shell cat /proc/partitions adb shell dmesg | grep -i mmc/sys/block/mmcblk1/size的单位是 512 字节扇区数。128GB 换算成扇区是 128 * 1024 * 1024 * 1024 / 512 268435456。如果这个数出现且dmesg里面又写着new high speed SDXC card那说明卡本身已经被正确识别为 SDXC但是容量值被人为钳制了。我这里贴一段当时真实的日志片段mmc1: new high speed SDXC card at address 0001 mmcblk1: mmc1:0001 SD512 119.5 GiB mmcblk1: p1注意这个日志是在驱动修改之后才出现的。修改之前同样的卡显示的是mmcblk1: mmc1:0001 SD512 119.5 GiB rw有的版本会直接显示SD128有的会显示122880MB总之都不是卡的真实容量。如果dmesg已经正确识别到 512GB而/sys/block/mmcblk1/size被截到 128GB就能确定是驱动层某个地方做了 clamp。2.2 用关键词搜索定位私有补丁MTK 平台的原生内核通常不会主动限制 SD 卡容量但很多厂商 SDK 会加自己的策略。所以我不会背函数名而是直接在源码里搜关键词。rg -n capacity|CAPACITY|MAX_CAP|SD_CARD|SD_LIMIT|128 \* 1024 kernel-4.9/drivers/mmc/ rg -n 268435456 kernel-4.9/rg比grep快很多如果工程里有 grep 没有 rg就用grep -rniE代替。重点看drivers/mmc/core/下的sd.c、mmc.c以及厂商私有的drivers/mmc/host/里的文件。我这次的问题点最终落在kernel-4.9/drivers/mmc/core/sd.c因为厂商在读取 CSD 后加了一段if (card-capacity (128ULL * 1024 * 1024 * 1024) / 512) { card-capacity (128ULL * 1024 * 1024 * 1024) / 512; pr_info(limit sd card capacity to 128GB\n); }这类代码不是原生代码所以用“搜容量数值 搜容量关键词”双管齐下基本都能定位到。2.3 编译与烧录前的工具准备定位到修改点之后别急着动手。先确认你有没有完整的编译环境和烧录手段。Android 8.1 对应的 MTK 工程内核一般独立放在kernel-4.4或kernel-4.9目录。编译环境就是整套 Android 源码环境需要先source build/envsetup.sh再lunch对应产品。我习惯先把当前分支留一个备份git checkout -b feature/sd-capacity-512这样改坏了随时可以切回来。接着确认 boot 镜像的生成方式有的项目是make bootimage有的需要在 kernel 目录单独做make Image.gz-dtb再用mkbootimg打包。这些信息在项目的编译文档里一般有没有的话问一下带这个项目的同事比自己瞎猜效率高得多。烧录方面开发机直接用 fastbootadb reboot bootloader fastboot flash boot boot.img fastboot reboot量产机器千万别这么干必须用官方升级工具和签名包。这个坑我踩过用 fastboot 硬刷一台样机直接把 bootloader 分区搞坏最后只能拆机短接复位。所以工具准备不只是“能编译”还要“知道这是什么阶段的机器”。2.4 不要忽略卡的类型和文件系统现状还有一件事容易被忽略先确认手里的卡是什么卡。dmesg里如果是SDHC那这张卡标称容量本来就在 32GB 以下你去追 128GB 限制就是浪费时间。确认是SDXC之后再用读卡器接到电脑上用磁盘管理工具看一下卡的完整容量顺便确认分区表是 MBR 还是 GPT文件系统是 exFAT 还是 FAT32。我这次手里的 512GB 卡出厂是 exFAT 格式。如果内核没有开 exFAT就算容量改对了插到设备上也会显示“不支持的文件系统”。所以我把 exFAT 适配也纳入了修改范围这在后面的章节里会专门说。3. 驱动层实操找到钳制点并安全修改3.1 找到容量钳制代码并修改前面提到问题点是一段私有 clamp 代码。我的处理方式是先把这段代码完整看懂再决定是删掉还是把上限调大。如果项目只要求支持到 512GB一段简单方案是直接删掉这段 if 判断让card-capacity保持原始解析结果。但有些平台的代码里clamp 可能是为了规避某些老化卡或兼容性问题贸然删除有风险。所以我这次没有全删而是把上限提到了 1TB留了一点余量if (card-capacity (1024ULL * 1024 * 1024 * 1024) / 512) { card-capacity (1024ULL * 1024 * 1024 * 1024) / 512; pr_info(limit sd card capacity to 1TB\n); }注意这里我用的是1024ULL不是1024。32 位整数乘法很容易溢出。有的代码里写的是128 * 1024 * 1024 * 1024前面的128默认是 int四个 int 相乘的结果在 32 位平台会溢出这会导致一个很隐蔽的 bug你以为设的是 128GB实际容量钳制值可能是个很大的负数转换来的数。虽然最终结果可能碰巧还是 128GB但改参数的时候很容易算错。3.2 小心变量位宽和溢出问题容量钳制点找到之后还需要检查变量类型。card-capacity在较老的内核里是unsigned int也就是 u32。u32 最大能表示 4294967295而一个 2TB 的卡需要的扇区数是 4294967296正好溢出。这就是为什么有些老平台对超过 2TB 的 eMMC 或 SD 卡支持不好不是协议不支持而是变量放不下。如果你的项目将来可能支持 1TB 以上的卡建议顺手检查一下struct mmc_card里的capacity字段类型以及mmc_blk_alloc_req里调用set_capacity()时传入的参数类型。如果中间有一个u32强转也要改成u64或sector_t。同样在 dts 或者 platform 数据里如果存在max_capacity之类的属性也要看它的类型。我见过一个平台把容量上限写在一个u32变量里结果无论怎么改代码超过 2TB 就失效最后改成u64才正常。3.3 host 驱动和 dts 一般不用动很多人一听到“驱动能力修改”就以为要去动 MTK 的 msdc host 驱动比如mtk-msdc.c或msdc.c。实际上绝大多数“识别容量减半”的问题跟 host 驱动没有关系。host 驱动只负责发命令、收数据、控制时序和电压切换它不知道也不关心这张卡是多少 GB。容量解析在 mmc core容量钳制在厂商私有补丁这两个地方才是关键。不过有一个例外如果卡在初始化阶段就失败dmesg里连new SDXC card都看不到那才需要查 host 驱动和 dts。常见排查点包括mmc1 { max-frequency 198000000; bus-width 4; cap-sd-highspeed; sd-uhs-sdr104; sd-uhs-ddr50; };如果大容量卡插上完全没反应可以检查vmmc-supply相关电源节点调大 regulator 的max-microamp。512GB 的卡在持续读写时功耗明显比 32GB 的卡高供电不足会表现为热插拔时无法识别、写入一段时间后掉卡。这个问题和“容量被钳制”现象不同但很容易被误诊为驱动能力问题所以我顺手把 dts 检查和 host 检查放在了一起。3.4 改完先单独验证别急着连上层一起改驱动层改完我会先把 boot 编译出来单独刷一次然后插上 512GB 卡看/sys/block/mmcblk1/size。这一步的目的是把“变量”控制住底层改了之后先确认底层确实生效再动 vold 和 framework。如果你同时改了三层最终出问题的时候你根本不知道是哪一层改错了。我这次单独刷完驱动后/sys/block/mmcblk1/size变成了约 976773168 扇区dmesg里显示SD512。注意扇区数不是固定的 1,000,000,000因为不同厂商的卡实际可用扇区数会有一点差异但至少能确定块设备层已经不再截断。4. 上层配置同步vold 与 framework 的一个都不能漏4.1 vold 里的容量判断怎么查驱动层恢复容量后我插卡观察发现系统能挂载 exFAT但部分文件操作不正常。于是开始查 vold。原生 Android 8.1 的 vold 不会把 SD 卡限制在 128GB但厂商 SDK 就可能加。我的经验是搜索这些关键词grep -rniE max_sd|maxSd|MAX_SD|capacity|max_capacity system/vold/重点关注PublicVolume.cpp、Disk.cpp、VoldNativeService.cpp。有些项目会在挂载前判断磁盘大小如果超出一个预设值就拒绝挂载或者在sm list-volumes里显示为Unmountable。我这次项目里其实没有在 vold 发现容量截断但我仍然把 vold 的代码读了一遍确保没有类似逻辑这样后续问题排查才有底。4.2 framework 上报链路的容量同步vold 没问题之后看 framework。设置界面显示的容量来自StorageManagerService管理的DiskInfo对象而DiskInfo的数据是 vold 通过 binder 上报的。如果这里某一层用了int接收long容量超过 2GB 的值就会变成负数或者被截断。怎么验证用dumpsys一层层看adb shell dumpsys mount | grep -A10 Disk adb shell dumpsys diskstatsdumpsys mount里如果磁盘大小已经正确但dumpsys diskstats或者设置界面显示错误问题就在 framework 的某个转换位置。我这次是在StorageManagerService.java里看到一个私有属性mMaxSdSize默认值写的是 128GB。这个属性会在系统启动时读取然后用于限制通过 mount 服务汇报上来的外置卡大小。处理方式很简单把默认值改成 0表示不限制或者改成 1TB。同时检查一下系统属性里是否也有对应配置比如ro.storage.max.sd.size有的话一并调整。这个属性名不同项目差别很大别照抄以grep结果为准。4.3 文件系统适配exFAT 是绕不开的大容量 SD 卡基本都会格式化成 exFATFAT32 虽然理论支持到 2TB但实际兼容性差Android 也早就默认不用 FAT32 做大容量外置存储。Android 8.1 原生内核默认不开启 exFAT 支持MTK 的 SDK 有些会集成sdfat驱动有些没有。检查方法adb shell zcat /proc/config.gz | grep -i exfat adb shell ls /system/lib/modules/如果内核没开 exFAT有两个方案一个是把 exFAT 支持编进内核另一个是单独编译exfat.ko模块放进系统然后在 init.rc 里加载。我这次项目的内核 config 里已经开了CONFIG_SDFAT所以我只要确认模块能被加载就可以。如果你遇到 kernel config 里没有相关选项说明当前 kernel 源码可能根本没集成 exFAT 驱动需要从厂商的另一个内核版本移植这个工作量会大很多。4.4 大容量卡的格式化建议容量限制解除之后卡还是建议用 exFAT。你不要试图在设备上把 512GB 卡格式化成 ext4虽然能挂载但把卡拔下来插到 Windows 上就完全不识别用户会把售后电话打爆。我的做法是在电脑端先把卡格式化成 exFAT然后插回设备。观察 vold 日志vold: preparing /dev/block/mmcblk1p1 vold: mounted /mnt/media_rw/XXXX-XXXX日志里如果出现unsupported filesystem说明 exFAT 驱动没加载成功优先排查模块路径和权限。如果显示挂载成功再实际写一个超过 4GB 的大文件验证 exFAT 的单文件大小支持是否正常。这一步很重要因为有些卡挂载成功但写超过 4GB 文件会报 “File too large”。5. 编译、烧录与验证改完必须走完的完整流程5.1 内核与 boot 镜像的编译流程驱动层和上层都改完之后我开始完整编译。这个项目用的是 kernel-4.9编译命令大致如下cd $ANDROID_BUILD_TOP source build/envsetup.sh lunch product-userdebug cd kernel-4.9 make ARCHarm64 defconfig make ARCHarm64 -j16defconfig 的名字要看项目的具体配置比如mtxxxx_defconfig或full_k419_defconfig。有些项目没有独立 kernel 编译步骤直接在顶层make bootimage -j32就会自动编 kernel。编译完成后检查生成路径out/target/product/product/boot.img或者kernel-4.9/arch/arm64/boot/Image.gz-dtb确认 boot.img 时间戳是刚刚生成的再烧录。很多次我看到同事烧了半天最后发现烧的是旧的 boot.img因为编译产物路径不对。5.2 烧录前的必要检查如果是开发板fastboot 刷 boot 就行。但要注意Android 8.1 的 MTK 平台有些是 A/B 分区fastboot 命令会不一样。我这次是单分区命令是adb reboot bootloader fastboot devices fastboot flash boot out/target/product/product/boot.img fastboot reboot刷之前确认机器是 userdebug 版本不然可能刷不进去。部分厂商还会对 boot.img 做签名校验这种情况就要用厂商的升级工具把编译出来的 boot.img 放到升级包指定目录重新打包再刷。5.3 验证清单从底层到 UI 逐项确认烧录完成后我按下面这个清单逐项验证检查项命令或操作预期结果内核识别容量adb shell dmesg | grep -i mmc1显示SD512或接近 512GB 的容量块设备扇区数adb shell cat /sys/block/mmcblk1/size约 976773168 或 1,000,000,000 附近分区可见adb shell cat /proc/partitions存在mmcblk1p1挂载状态adb shell sm list-volumes all外置卡状态为mounted文件系统adb shell df -h /storage/XXXX-XXXX容量正确文件系统为 exFAT设置界面显示打开设置 存储总容量与标称容量一致不要只看设置界面因为它可能缓存旧数据。每次验证前都建议重启一次或者至少用sm unmount再sm mount重新挂载一遍。5.4 性能测试与长期稳定性容量显示正确只是第一步还要看读写是否稳定。我先拷一个大文件adb shell dd if/dev/zero of/sdcard/test.bin bs1M count10240 convfsync写入 10GB 文件观察速度。如果写入速度只有 10MB/s 以下说明卡要么本身低速要么设备没有启用 UHS-I 模式。可以检查 dts 里sd-uhs-sdr104有没有配。读取测试adb shell dd if/sdcard/test.bin of/dev/null bs1M count10240对于 512GB 的 SDXC 卡写入 30MB/s 以上、读取 60MB/s 以上属于比较正常的水平。如果速度明显偏低先换一张同容量卡对比排除卡本身批次问题。性能测试通过后再做一次热插拔测试在系统运行状态下拔卡、再插卡观察是否重新挂载、容量是否仍然正确。这个场景很容易触发 host 驱动的枚举异常提前暴露问题。6. 问题排查与避坑这些坑我替你先踩过了6.1 一眼判断问题在哪一层后期维护的时候还会有各种类似反馈。我把常见现象和对应排查方向整理成了一张速查表现象问题层下一步动作设置只显示 128GB但/sys/block/mmcblk1/size是 128GB驱动层修改 mmc core 私有 clamp/sys/block/mmcblk1/size正确但设置显示错误framework/vold用 dumpsys 对比 DiskInfo 和真实块设备容量挂载失败log 有unsupported filesystem文件系统驱动检查 exFAT/sdfat 模块挂载成功但写大文件报错文件系统格式用 exFAT 重新格式化插卡后完全没反应host/dts 初始化查 dmesg检查供电和卡检测引脚写入一段时间后掉卡供电/温控/硬件降低写入速率检查散热和 regulator这张表是我做这个项目时反复对照的遇到新问题先对号入座能省很多时间。6.2 修改后卡反而不识别的高频原因有一个现象很容易误导人改完驱动后原来能识别的 128GB 卡反而识别不到了。我先说结论大多数时候不是代码改错了而是你自己把卡格式化成 exFAT 时出了问题。比如在 Windows 上快速格式化后分区表没有完整对齐设备端 vold 解析分区失败就会表现为“完全不识别”。解决办法很简单先用读卡器在电脑上看磁盘管理里能不能看到分区如果能看到就用磁盘管理工具重新格式化一次或者干脆用diskpartclean 之后重建分区表。千万不要在没确认分区表一层层去查驱动代码浪费时间。如果是真的驱动修改导致的枚举失败优先检查是不是改动了mmc_sd_setup_card里的调用顺序。有些厂商补丁把容量 clamp 放在初始化阶段你把代码删了但后续逻辑依赖那个被钳制的值就会在初始化时触发空指针或状态异常。所以我建议尽量“调大上限”而不是“删除判断”降低对后续逻辑的冲击。6.3 热插拔、休眠与温控问题大容量卡和小容量卡在热插拔场景下表现差别很大。512GB 卡的初始化时间比 16GB 卡长不少如果系统刚开机时 CPU 还在忙着加载其他服务host 和卡之间握手超时就会导致部分开机批次识别不到卡。遇到这种问题首先要看dmesg里有没有mmc1: card lost或timeout。如果确实是超时可以在 dts 里增大卡检测的轮询间隔或者把卡检测放到系统启动后期执行。另外休眠唤醒后掉卡也是一个高频问题。系统在休眠时如果 SD 卡供电被切断唤醒后 host 驱动没有重新初始化卡就会掉卡。这个和容量修改没有直接关系但客户会把账算到“你们改了驱动之后就不稳定”上。所以我建议做完驱动能力修改之后把休眠唤醒、热插拔、长时间挂载这些场景都跑一遍。还有温控。大容量卡持续写入时发热明显卡内芯片过热会自动降速甚至掉卡。如果设备散热结构不好你会看到写入速度从 60MB/s 掉到 5MB/s然后卡消失。这问题软件能做的有限顶多在 AP 的 thermal 策略里对 SD 卡读写做个温度限制但根本解法还是要改善卡周围的散热。6.4 个人体会与建议把整个项目做完我最深的感受是SD 卡驱动能力修改真正难的不是那几行代码而是“确认问题在哪一层”。如果一开始就对着drivers/mmc/core/sd.c猛改结果发现上层还有限制就会陷入改完又复现的死循环。我现在处理类似问题顺序永远是先看/sys/block/mmcblk1/size再看dmesg然后sm list-volumes最后才决定要不要动代码。尤其在 MTK 平台厂商 SDK 的私有补丁位置差异很大跟网上任何一份教程都不可能完全一致。多用关键词搜索少背函数名是最实用的经验。还有一个建议测试卡一定要准备多个品牌和多个容量档位。我这次用了一张 512GB 的卡验证通过同事换上另一张 512GB 卡后速度慢了将近一半查了半天发现是卡本身主控方案不同。项目里对外宣称支持到 512GB最好至少拿三家主控的卡做兼容性测试不然量产之后很容易被供应商“教育”。最后再分享一个小技巧整个修改过程中每改一层就单独跑一轮验证并且把dmesg、dumpsys mount、git diff都存下来。这样万一后来出了问题你可以很快对比是哪个环节引入了新的变化。这个东西看起来麻烦但等到需要分析回归问题的时候你会感谢当时的自己。