news 2026/9/24 12:54:32

eMMC调试利器mmc_utils:20+实用命令全面解析与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
eMMC调试利器mmc_utils:20+实用命令全面解析与实战

1. 为什么工程师手里该常备mmc_utils

先讲个真实场景。有次我拿到一批号称“全新原装”的eMMC芯片,上机后系统能启动,但跑存储压力测试时总有几片掉盘,要么就是写入速度忽快忽慢。用常规手段根本查不出问题,因为从Linux内核上层看,这些设备都报“正常”。后来我直接用mmc_utils把每片芯片的extcsd读出来,一眼就看到其中一片的life time estimation字段已经显示接近磨损极限,还有一片的revocation flag状态异常。这玩意儿要是不读底层数据,光靠跑应用层测试,排查周期至少翻三倍。

这就是我想写这篇东西的原因。mmc_utils是Linux内核社区维护的一套eMMC用户态工具,专门用来直接访问MMC设备的管理功能。它做的事情,说白了两类:一类是“读”,把eMMC内部那些寄存器、分区信息、健康状态全部拉出来给你看;另一类是“写”,像分区切换、安全擦除、RPMB密钥写入这些操作,全靠它执行。在Android设备上,特别是在做系统移植、存储调试、产线老化验证、售后返修分析的工程师手里,这是绕不开的利器。

标题里写的“20+个实用命令”,不是凑数。我按实际使用频率把这些命令分成四个维度:基础信息读取、extcsd深度解析、性能与健康检验、RPMB安全操作。从最初拿到一颗芯片“什么都不知道”,到最后能亲手完成RPMB安全分区写入,这条路走通了,eMMC在你的项目里就不再是黑盒。

这篇文章适合三类人:正在做Android平台BSP或者存储驱动的工程师、负责产线测试与售后分析的质量工程师、以及那些对eMMC底层机制感兴趣的进阶玩家。看完之后,你能把mmc_utils的命令串成一套完整的排查方法,而不是零散地抄几个命令用。

2. 环境准备:把mmc_utils塞进Android设备

2.1 源码获取与交叉编译的两种姿势

mmc_utils的源码托管在Linux内核官方仓库的tools/mmc目录下,它不依赖复杂的库,编译极其轻量。你在内核源码树里直接make就能出来,也可以单独拉取镜像仓库。

获取源码之后,交叉编译是最常见的需求。在Android项目里,通常有两种做法。

第一种,用Android NDK直接编译静态可执行文件。这样的好处是产出的二进制不依赖目标设备的动态链接库,push进去就能跑,特别适合在eng或者userdebug版本的设备上做临时调试。NDK工具链准备好之后,写一个简单的编译脚本:

#!/bin/bash NDK_PATH=/path/to/your/ndk API_LEVEL=android-28 ARCH=aarch64-linux-android export CC=$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin/${ARCH}${API_LEVEL}-clang make clean make CC=$CC

注意,如果目标Android设备是32位ARM,把ARCH换成armv7a-linux-androideabi即可。静态链接做完之后,文件体积会比动态版大不少,但胜在省心。

第二种,如果设备上已经有完整的root权限和可写的system分区,你想把mmc_utils当作系统工具来用,那就直接放进Android源码树里,用Android.bp或者Android.mk写进编译系统。举个例子,在device目录下新建一个mmc_utils模块目录,Android.mk里这样写:

LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := mmc_utils LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_SRC_FILES := mmc.c mmc_cmds.c mmc_cmds.h mmc.h LOCAL_CFLAGS := -Wno-unused-parameter LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_EXECUTABLES) include $(BUILD_EXECUTABLE)

编译完会直接装到vendor/bin目录下,作为系统的一部分长期存在。我自己的习惯是:工程调试阶段用NDK静态编译版本,稳定量产之后再决定要不要集成进系统。

2.2 权限模型与设备节点:命令能不能跑通的第一步

mmc_utils能工作,依赖两样东西:合适的设备节点和足够高的权限。

Android设备上,eMMC对应的块设备节点通常是/dev/block/mmcblk0,有些平台可能是mmcblk1,具体要看硬件拓扑。建议先用下面的命令确认:

ls -l /dev/block/mmcblk* for i in /sys/class/mmc_host/mmc*/mmc*; do echo $i; cat $i/name; done

第二行命令会枚举出系统识别到的所有MMC控制器和设备名称,比如某台设备的输出可能是:

/sys/class/mmc_host/mmc0/mmc0:0001 AGND3R

这个“AGND3R”就是eMMC内部存储的设备名称,配合/proc/partitions可以看到完整的分区布局。

权限方面,很多mmc_utils命令需要打开设备节点并执行IOCTL,普通shell用户访问/dev/block/mmcblk0会报Permission denied。这种情况下,要么确保设备已经root,要么直接切到adb root模式再执行。实际操作中,我的建议是始终先跑一条最简单的命令验证权限链:

adb root adb shell mmc extcsd read /dev/block/mmcblk0

如果这条能正常输出大段十六进制数据,说明设备和权限都没问题,后面的操作可以放心折腾。如果报错,九成是设备节点路径不对,还有一成是SELinux策略把IOCTL调用给拦了,需要临时把SELinux切到Permissive模式验证一下:

adb shell setenforce 0

总结一下环境准备的核心结论:交叉编译选静态链接最省心、确认设备节点用sysfs枚举最可靠、权限问题先root再放SELinux。这三步走完,命令本身才不会成为后续的拦路虎。

3. 基础信息读取:认识你手里的eMMC芯片

3.1 CID、CSD与固件版本:给芯片“验明正身”

我刚接触eMMC调试时,最常被问到的一个问题是:“我怎么知道这颗eMMC是哪个厂家的?容量对不对?走的是什么总线模式?”这些问题,用mmc_utils的几兄弟命令就能在几十毫秒内给出答案。

读CID,获取芯片唯一标识:

mmc cid read /dev/block/mmcblk0

输出会包含三大部分信息:制造商ID(Manufacturer ID)、产品名(Product Name)、序列号(Serial Number)。制造商ID是JEDEC标准指定的,比如0x11就是三星,0x15就是东芝铠侠,0x45就是闪迪。看完这个,A厂B厂混用导致的问题一眼就能发现。我在产线抽检时就遇到过,BOM表里报的是三星,实际贴片的芯片读出来是另一家,要不是用cid read把序列号拉出来核对,整批货就漏过去了。

读CSD,获取容量和速度等级:

mmc csd read /dev/block/mmcblk0

CSD里有几个字段特别重要:READ_BL_LEN决定了单次可读块的字节数,C_SIZE和C_SIZE_MULT组合计算出来的就是总容量,TRAN_SPEED表示最大传输速率。CSD里的容量计算方式和extcsd不太一样,它是老一代的“固定几何参数”,对于现代大容量eMMC来说,extcsd里的SEC_COUNT才是更精确的容量来源。所以CSD适合快速验证,extcsd适合精确分析。

查看制造商与产品版本信息:

mmc fwrev read /dev/block/mmcblk0 mmc hwrev read /dev/block/mmcblk0

一条命令显示固件版本(FW Revision),一条显示硬件版本(HW Revision)。这两个值在排查“同批次芯片表现不一致”时非常好用。比如某段时间产线反馈写入性能下降,读一下hwrev发现这批芯片的硬件版本和之前的不同,就知道要找供应商核对版本切换记录,问题定位效率提升好几个量级。

3.2 status get与bootpart get:快速判断设备当前状态

有时候你需要的不是一长串寄存器描述,而是“这设备现在到底处于什么状态”。mmc_utils提供了一组快速状态查询命令。

mmc status get /dev/block/mmcblk0

这条命令读取设备当前的32位状态寄存器。最高关注的就是bit 12-13的CURRENT_STATE字段,它直接告诉你eMMC正处在kernel自举阶段、数据传输状态、还是休眠状态。有一次我调一个低功耗唤醒异常的问题,怀疑eMMC没进入sleep状态导致功耗居高不下,就是用这条命令发现设备一直停留在transfer状态,后来定位到是驱动里缺少sleep命令的下发逻辑,跟mmc_utils配合把问题坐实了。

查看启动分区配置:

mmc bootpart get /dev/block/mmcblk0

输出会展示当前boot partition的使能状态、访问权限配置、以及boot ACK是否开启。这个命令在做Android启动流程调试时几乎是必备操作——如果你发现设备从bootloader阶段就无法启动,第一件事就是确认boot partition的配置是否被意外篡改过。

我还想补充一个技巧:上面这些命令里,你可以把/dev/block/mmcblk0替换成实际的分区设备节点,比如/dev/block/mmcblk0boot0、/dev/block/mmcblk0boot1,它们分别对应eMMC的两个硬件启动分区。后续读RPMB时,设备节点就变成/dev/block/mmcblk0rpmb,不要搞混了。

4. 深入extcsd:一张表读懂eMMC的“体检报告”

4.1 extcsd read命令与关键字段索引表

extcsd(Extended CSD Register)是eMMC内部一片512字节的扩展寄存器区域,里面记录了大量设备出厂参数、运行状态、健康信息、以及可配置项。它是eMMC调试最核心的信息来源。

mmc extcsd read /dev/block/mmcblk0

这条命令输出的十六进制数据按字节偏移排列。新手看到一长串hex就头大,但只要掌握几个关键偏移量,就能像查字典一样读取你想要的信息。下面是我多年调试中频繁查阅的字段清单,整理出来供你保存:

字节偏移字段名称关键信息
0x00S_CMD_SET命令集版本,通常为0(标准MMC命令集)
0x08SEC_COUNT用户数据区总扇区数(512B/扇区),决定实际容量
0x0DTRIM_MULTTRIM操作的粒度倍数
0x10MAX_ENH_SIZE_MULT增强分区最大尺寸倍数
0x1BPARTITION_CONFIG当前分区配置,含BOOT_PARTITION_ENABLE、PARTITION_ACCESS等位
0x1CBOOT_BUS_CONDITIONSBoot总线宽度与时序配置
0x1DBOOT_CONFIG_PROTBoot配置写保护标志
0x22ERASE_GROUP_DEF擦除组定义使能标志
0x24ERASED_MEM_CONT擦除后内存默认值
0x2ABOOT_WP_STATUSBoot分区写保护状态
0x2BBOOT_WP_BOOT_REGBoot分区写保护配置寄存器
0x30USER_WP用户区写保护配置
0x31FW_CONFIG固件配置
0x32RPMB_SIZE_MULTRPMB分区大小倍数
0x33WR_REL_SET写可靠性设置
0x34WR_REL_PARAM写可靠性参数
0x35SANITIZE_START启动Sanitize操作
0x3CBKOPS_EN后台操作(Background Operations)使能标志
0x3DBKOPS_START手动触发BKOPS
0x3ESANITIZE_STATESanitize操作状态
0x40EXT_CSD_TRIM_UNITTRIM单元大小
0x49MAX_PRE_LOADING_DATA_SIZE预加载数据最大尺寸
0x50CMDQ_MODE_ENCommand Queue模式使能
0x52CMDQ_DEPTHCommand Queue深度
0x56FFU_FEATURES固件更新特性支持
0x5APRODUCTION_STATE_AWARE生产状态感知
0x5CDEVICE_LIFE_TIME_EST_TYP_A设备生命周期估算(典型使用场景A)
0x5DDEVICE_LIFE_TIME_EST_TYP_B设备生命周期估算(典型使用场景B)
0x60eMMC_POWER_OPTIMIZATION功耗优化配置
0x61PACKED_FAILURE_INDEX打包命令失败索引
0x62PACKED_CMD_STATUS打包命令状态
0x63PRE_EOL_INFO预寿命终止信息
0x64SECURE_REMOVAL_TYPE安全移除类型
0x66SECURE_TRIM_MULT安全TRIM倍数
0x67SECURE_ERASE_MULT安全擦除倍数
0x68SECURE_FEATURE_SUPPORT安全特性支持
0x69TRIM_MULTTRIM倍数(用户分区)
0x6AMIN_PREFETCH最小预取数据量
0x9ABKOPS_STATUS后台操作状态
0xA5CONTROL控制寄存器
0xA6POWER_OFF_LONG_TIME长时间断电时间
0xA7POWER_OFF_SHORT_TIME短时间断电时间

这张表不需要死记,重点是学会“什么场景查什么字段”。比如怀疑设备快报废了,直接看0x63、0x5C、0x5D;怀疑启动分区配置被改坏了,看0x1B;想知道支不支持Command Queue,看0x50。

4.2 通过extcsd判断器件寿命与健康度

eMMC内部其实有一套健康监测机制,不像有些人以为的“存储芯片哪有什么健康度可言”。上面表格里的PRE_EOL_INFO和DEVICE_LIFE_TIME_EST就是关键。

PRE_EOL_INFO位于偏移0x63,它指示设备距离寿命终止还有多久,取值含义如下:

  • 0x00:正常,设备寿命未出现明显消耗
  • 0x01:已消耗80%的保留块(Reserved Blocks),进入警告区
  • 0x02:已消耗90%的保留块,属于严重警告
  • 0x03:已消耗100%的保留块,设备即将无法保证可靠写入

DEVICE_LIFE_TIME_EST_TYP_A和TYP_B则用0到0x0A的数值估算设备生命周期消耗程度。这两个值分别对应两种不同的“典型使用温度场景”,A类通常是高温场景估算,B类代表常温场景估算。数值10意味着设备寿命已经估算耗尽。

我在老化和返修分析时,会把这几个字段输出打印出来,再配合mmc status get的状态值,可以快速判断“返修设备的eMMC是不是罪魁祸首”。有次售后反馈设备频繁死机,拿回来读extcsd,PRE_EOL_INFO已经到0x02,寿命都快干涸了,死在写入时段的概率当然高。这类判断,输出一行命令就能让硬件部门无话可说。

4.3 SEC_COUNT容量校验与分区布局的坑

容量校验是产线测试里绕不开的一环。extcsd的0x08偏移处,SEC_COUNT字段描述了设备用户数据区的总扇区数。但是注意,这个字段是4字节小端序,逐字节读出来之后要拼成完整的32位值。

比如十六进制输出里,0x08到0x0B的四个字节为:

00 80 3A 00

拼出来的值就是0x003A8000,换算成十进制是3833856,乘以每扇区512字节,得到总容量约1.96GB。再对比采购规格书里的标称容量,就能判断容量是否缩水。

不过这里藏着一个非常深的坑:很多Android设备会用extcsd的USER_WP或者BOOT_WP字段做硬件写保护,也有平台会修改ERASE_GROUP_DEF。如果你在做容量相关的自动化脚本,一定要把0x1B这个PARTITION_CONFIG字段读出来,确认当前是不是处于增强用户数据区(Enhanced User Data Area)模式,因为增强区的容量计算方式和普通用户区不同。我碰到过一次,产线脚本把增强区和普通区的容量加在一起跟标称值比,结果差了100多MB怎么都对不上,查了整整半天,最后发现是分区配置把一部分容量划给了增强区。这个坑,写自动化测试的同学务必提前预判。

5. 性能与健康快速检验:不用跑benchmark也能发现问题

5.1 bsr读改写:临时改总线速度的调试技巧

eMMC的工作速度由总线模式、时钟频率和数据线宽度共同决定。测试过程中临时调总线速度,mmc_utils里有BCK(Back-End Check)相关命令,但我更常用的是直接读写Bus Speed Mode寄存器。

查看当前总线速度模式:

mmc bsr read /dev/block/mmcblk0

这条命令会返回设备当前工作的总线速度模式,一般包括:

  • 0:默认速度(Default Speed,DS),时钟不超过26MHz
  • 1:高速模式(High Speed,HS),时钟不超过52MHz
  • 2:HS200,时钟不超过200MHz
  • 3:HS400,时钟不超过400MHz

改写总线速度模式:

mmc bsr write /dev/block/mmcblk0 2

这里有个务必记住的原则:bsr write切换模式之后,通常需要重新初始化设备才能完全生效,切到更高的HS400模式时尤其如此,不是随便改个数字就能跑起来。所以这个命令更适合在uboot或者kernel启动阶段配合驱动初始化流程做验证,而不是在系统运行中“热切换”。我有一次在Android运行状态下把模式从HS200切成HS400,结果设备直接不响应了,重启才恢复正常。从此之后,我调整总线速度都是先确认设备支持,再配合软复位流程来操作。

5.2 bist命令:从主机侧对数据总线做自检

BIST(Built-In Self-Test)是eMMC 5.0开始加入的功能,它允许主机引导设备内部的数据总线进行自检。mmc_utils封装了这个能力:

mmc bist read /dev/block/mmcblk0 mmc bist write /dev/block/mmcblk0 1

在排查硬件信号完整性问题时,这个命令比拿示波器量信号快得多。它能验证host controller到eMMC之间的数据线有没有虚焊、短路、或者时序问题。产线第一轮贴片回来,先用bist跑一遍,可以把大部分焊接不良的板子在功能测试之前就筛掉。

bist命令的使用需要硬件支持,不是每颗eMMC都有。跑之前先查extcsd里有没有对应的BIST模式支持位。如果设备不支持,命令会直接报错退出,这本身也是一个有效的筛选信息。

5.3 erase、trim、secure erase与sanitize的正确使用场景

这几个命令是eMMC运维里最容易被滥用的一组。先看它们各自的作用:

mmc erase 0x0 0x1000 /dev/block/mmcblk0

这是最基本的擦除命令,参数分别是起始地址和结束地址(按扇区计)。它会将指定的用户数据区域擦除为全1或全0,具体取决于extcsd里的ERASED_MEM_CONT字段。

mmc trim 0x0 0x1000 /dev/block/mmcblk0

trim是逻辑层面的“标记失效”,把那些操作系统认为已经删除但物理上还被占用的块标记为可回收,后续垃圾回收机制会真正释放它们。这跟SSD上的trim概念一样,不会立即清空数据,但能改善后续写入性能。

mmc secure erase 0x0 0x1000 /dev/block/mmcblk0 mmc secure trim 0x0 0x1000 /dev/block/mmcblk0

secure版本会执行物理销毁或加扰处理,这样即使拆下芯片做数据恢复也恢复不出有效信息。适合处理含敏感信息的数据区。

mmc sanitize /dev/block/mmcblk0

sanitize是对整个设备做一次彻底擦除,会把所有存储单元重置到出厂默认状态。它的特点是耗时很长,一次操作可能几十秒到几分钟不等,期间会阻塞该设备的IO。

这组命令里,我的经验总结是:系统升级、数据清除场景优先用trim;返修报废设备彻底清数据,用secure erase或者sanitize;日常调试验证性能时,erase就够了,别动不动就secure erase,因为安全擦除的耗时是普通擦除的数倍甚至数十倍,产线节奏会很难扛。

6. RPMB安全分区:从读计数器到写入密钥

6.1 RPMB是什么?为什么不能像普通分区一样写

RPMB(Replay Protected Memory Block)是eMMC里一块带有重放保护机制的安全存储区域。它跟普通用户分区最大区别在于:每次读写都需要一次基于HMAC-SHA256的认证握手,密钥是出厂时写在设备内部的,主机侧不知道密钥就无法写入任何数据。

这种机制天然适合保存防回滚计数器、密钥材料、安全启动校验值这类数据。Android设备里的keymaster、widevine、以及bootloader的rollback index,很多就存放在RPMB分区里。

mmc_utils对RPMB的访问依赖内核的MMC子系统支持,同时设备节点需要指向对应的RPMB块设备,一般就是/dev/block/mmcblk0rpmb。不过在Android设备上,RPMB节点经常被上层权限策略隐藏,如果你ls看不到,就先从sysfs里确认设备是否存在:

ls -l /sys/class/block/mmcblk0rpmb

能读到这个节点,RPMB的访问通道才可能打开。

6.2 rpmb read-counter、rpmb write-key与rpmb read:先读后写的基本功

对于一块还没烧录过认证密钥的全新RPMB分区,写入密钥是第一步,且这一步是不可逆的。一旦烧录了密钥,后续所有写入操作都要用这把密钥计算MAC,密钥遗忘就等于RPMB分区永久锁定。实际操作中,很多厂商会为同一批次设备烧录同一个测试密钥,量产固件里再通过安全通道写入各自的唯一密钥。

查看当前写计数器:

mmc rpmb read-counter /dev/block/mmcblk0rpmb

写计数器是RPMB实现重放保护的核心——每次写入成功,计数器递增,回放旧的认证消息会因为计数器不匹配而被拒绝。所以排查RPMB写入失败时,第一步永远是读计数器,确认计数器的值是否符合预期。

烧录认证密钥:

mmc rpmb write-key /dev/block/mmcblk0rpmb <key_file>

key_file是一个32字节的二进制文件,就是你要写入的HMAC密钥。如果设备已经有密钥了,再执行写密钥操作会报错并拒绝执行。这也是RPMB机制的一部分——密钥只能写一次。

读RPMB数据:

mmc rpmb read /dev/block/mmcblk0rpmb <address> <blocks> <output_file>

RPMB的地址单位是256字节(一个RPMB frame的大小),所以读之前要搞清你要访问的帧号。我第一次用的时候习惯性按512字节扇区去算地址,结果怎么读都不对,后来才意识到RPMB的寻址粒度不是普通块设备的512字节。这个细节,踩过的人应该都懂。

6.3 rpmb write实操:完整流程与必须注意的陷阱

执行RPMB写操作的完整流程,我按实际经验拆成五步。

第一步,确认当前密钥状态。对一块新RPMB分区,先用read-counter确认计数器为0,说明还没写入过数据,密钥大概率也没烧录。如果计数器不为0,说明已经初始化过了,你后续写入操作得匹配原有密钥。

第二步,准备32字节密钥文件。可以用openssl生成:

openssl rand -out rpmb_key.bin 32

务必保存好这个文件,它的丢失等于RPMB分区永久失效。

第三步,写入密钥:

mmc rpmb write-key /dev/block/mmcblk0rpmb rpmb_key.bin

写入成功后会返回OK。再次执行read-counter,如果计数器变为非0,说明设备与密钥已完成绑定。

第四步,准备待写入的数据文件。RPMB每个frame是256字节,写入数据必须是256字节的整数倍。用dd生成一个测试数据:

dd if=/dev/urandom of=test_data.bin bs=256 count=1

第五步,执行写入:

mmc rpmb write /dev/block/mmcblk0rpmb <address> <key_file> test_data.bin

这里的address就是你要写入的帧号。写入完成后,再用6.2节里的rpmb read命令把数据读回来比对(前提是你的设备支持读取,有些安全策略会把读也锁住)。比对一致,说明整条链路已经打通。

这里有几个血泪教训要单独列出来:

  • 写密钥之前,确认目标设备不是返修机。返修机可能已经有旧密钥,盲目烧新密钥会直接把RPMB锁死。
  • 写操作期间绝对不能断电。RPMB写入一旦中断,轻则计数器错乱,重则整个RPMB分区访问异常。产线要写RPMB,务必加UPS和防掉电机制。
  • 部分平台的RPMB需要先使能才能访问,命令是mmc rpmb enable /dev/block/mmcblk0。如果你的rpmb read/write命令报出“operation not supported”之类的错误,先跑一下enable再试。

7. RPMB之外的“写入类”命令:bootpart、gp add、cmdq enable

7.1 bootpart enable与set:切换启动分区别手滑

Android设备的bootloader通常从eMMC的boot0或boot1分区加载。判断从哪个boot分区启动,就是靠BOOT_PARTITION_ENABLE这几位。

查看当前配置:

mmc bootpart get /dev/block/mmcblk0

使能boot0为启动分区:

mmc bootpart enable 1 1 /dev/block/mmcblk0

这条命令的两个数字参数,第一个是分区编号(1表示boot0,2表示boot1),第二个是boot ACK的使能标志(设为1时,eMMC会在boot操作完成后发送确认信号)。

bootpart操作最需要警惕的是:改动之后eMMC在下次上电就会按新配置执行引导。如果你当前系统就是从boot0启动的,而你在调试时把启动分区切到了boot1,而boot1里恰好没有可用bootloader,那么这块板子直接变砖。我早期吃过一次亏,从那以后每次切换之前都先把两个boot分区的镜像都备份好,并且准备海选工具随时救砖。

7.2 gp add与硬件分区管理:定制化的存储布局

eMMC除用户分区和boot分区外,还支持最多四个通用目的分区(General Purpose Partitions),简称GP分区。通过GP分区可以独立管理一部分存储空间,常用于安全启动镜像、日志存储或者车机系统的特定数据区。

创建GP分区:

mmc gp add <gp_partition_index> <size_in_blocks> /dev/block/mmcblk0

其中gp_partition_index取值1到4。大小参数同样遵循分区粒度的对齐要求,建议先用extcsd里的MAX_ENH_SIZE_MULT计算好可用空间再动手。

GP分区一旦划分,想要收回是很麻烦的,所以生产项目上基本是“预先规划好,生产时就分好”。调试平台时可以随便折腾,真机上要按需求书的布局来。

7.3 cmdq enable与cache enable:性能开关不能乱开

Command Queue和Cache是两个能显著影响eMMC性能的特性,但它们的启用必须遵循硬件逻辑。

打开Command Queue:

mmc cmdq enable /dev/block/mmcblk0

打开Cache:

mmc cache enable /dev/block/mmcblk0

这两个命令操作之后,如果驱动不配套,设备可能在下次IO请求时直接报错,严重时挂起整个存储栈。以CMDQ为例,它要求内核的MMC子系统完整支持命令队列协议,还要适配的host controller,Android平台kernel版本过旧的话,贸然enable只会给驱动带来额外的兼容性压力。我的建议是:需要验证性能收益时,在产品测试固件里先enable跑一轮完整测试,确认稳定再合入默认配置,不要贪图省事直接在产品固件里打开。

8. 命令执行失败排查:我从报错堆里总结的经验

8.1 设备节点与权限问题:最常见的失败原因

报错信息“open /dev/block/mmcblk0 failed: Permission denied”,十有八九是权限问题。先确认当前是不是root:id命令输出UID为0就是root。如果已经是root,再检查SELinux,临时放行用setenforce 0试跑一条命令。如果两条都排除,那就看节点是否存在:ls -l /dev/block/mmcblk0,不存在的话可能是设备节点没创建,需要手动mknod,或者内核配置里没打开MMC支持。

8.2 命令不支持与参数格式错误:别急着怀疑eMMC坏了

有时候你执行mmc extcsd read能正常输出,但执行mmc rpmb read却报“Invalid argument”。这种情况先别怀疑芯片坏了,优先确认设备是否支持RPMB:通过extcsd的0x32偏移查一下RPMB_SIZE_MULT,如果这个字段是0,说明RPMB分区本身就不存在,命令当然不可用。

还有一种典型情况:地址或块数参数传得不对。RPMB按帧(256B)寻址,而普通erase命令按扇区(512B)寻址,两者混用会直接得到Invalid argument错误。碰到参数类报错,先拿mmc --help或者mmc cmd --help查看参数格式的绝对值,别靠猜。

8.3 内核版本与mmc_utils版本不匹配

mmc_utils更新迭代很快,老内核上的某些命令实现可能与新版本工具不兼容。典型例子是早期的mmc_utils不支持CMDQ相关命令,新内核新增的FFU(固件更新)等功能也需要对应版本的工具才支持。遇到“Operation not supported”或“Unknown command”时,去内核tools/mmc目录看看当前版本,或者直接拉一份最新的mmc_utils源码重新编译,能解决大部分奇奇怪怪的兼容问题。

9. 高频实用命令速查:我把它们按使用频率分了梯队

为了让你后续排查时能快速找到对应命令,我把本文涉及的命令整理成一张速查表,按我的使用频率从高到低排列。

使用频率命令用途
极高mmc extcsd read读取扩展寄存器全部字段,健康与配置分析的起点
极高mmc status get获取设备当前状态
极高mmc cid read读取芯片唯一标识与厂家信息
极高mmc csd read读取CSD寄存器,获取基础容量与速度参数
mmc bootpart get查询当前启动分区配置
mmc bootpart enable切换启动分区使能状态
mmc rpmb read-counter查询RPMB写计数器
mmc rpmb read读取RPMB数据
mmc erase用户分区数据擦除
mmc trim逻辑块标记失效
mmc rpmb write写入RPMB数据
mmc rpmb write-key烧录RPMB认证密钥
mmc rpmb enable使能RPMB访问
mmc bsr read读取总线速度模式
mmc bsr write改写总线速度模式
mmc sanitize全设备物理擦除
mmc secure erase安全擦除指定区域
mmc secure trim安全TRIM指定区域
mmc fwrev read读取固件版本
mmc hwrev read读取硬件版本
mmc bist read/write内部数据总线自检
mmc gp add创建通用目的分区
mmc cmdq enable使能命令队列
mmc cache enable使能设备缓存
mmc sleep/awake手动控制设备休眠与唤醒
mmc hwreset enable使能硬件复位功能
mmc bkops enable使能后台操作

最后两条命令展开说明一下。mmc sleepmmc awake在低功耗调试时非常有用,通过在shell里手动控制eMMC进入和退出休眠状态,可以快速验证功耗电流的波形变化,不用反复开关机。mmc bkops enable则用于触发设备自动后台整理,能减少前台访问时的卡顿感,但需要驱动和固件都完整支持。

10. 个人踩坑记录与经验总结

写到最后,我想分享几个在真实项目里反复踩过的坑,这些经验比命令本身更值钱。

第一,任何会改变设备状态的写操作,执行前都要有“救砖预案”。我见过不止一个新手工程师,拿着mmc_utils在样机上把bootpart切了、把RPMB密钥烧了,然后设备变成一块砖头,只能返厂重新替换eMMC。正确做法是:先备份完整的分区镜像,同时确保手里有可以进入刷机模式的工具链,再碰写类操作。

第二,RPMB密钥管理是产线的核心风险点。如果同一批设备烧录相同密钥,一旦密钥泄露,整批设备的安全防护形同虚设。如果每台设备烧录独立密钥,密钥的生成、分发、存储流程会变得异常复杂。从工程角度,我建议至少做到“不同项目、不同批次、不同密钥”,密钥文件加权限控制并留好安全备份。

第三,eMMC是个“无限逼近物理极限”的器件。读extcsd时看到的生命周期估算、剩余寿命这些参数,不是精确的物理测量值,而是设备内部固件基于磨损均衡算法的估算。所以它们适合用来做趋势分析和异常预警,不适合作为“绝对阈值”来卡控。比如PRE_EOL_INFO到了0x01,不一定代表立刻会挂,但你的测试策略就要从常规测试切到加强老化测试了。

第四,mmc_utils输出的hex数据,建议直接用脚本做二次解析。手工盯着一堆十六进制看,效率太低。我平时会用一段Python脚本,把extcsd的原始输出转成结构化字段表,自动高亮那些异常标志位。建议你也建立一套自己的解析工具链,能把大量设备的extcsd批量收集起来,做横向对比、筛选异常,这在产线抽检中特别管用。

mmc_utils只是打开eMMC这扇大门的一把钥匙,真正值钱的是你对它背后工作机制的理解。一次次把命令跑通、把字段查明白、把报错理清楚之后,eMMC在你手上就不再是一个按下电源键就自动工作的黑盒,而是每一个状态、每一段时序、每一条安全机制都可解释、可控制、可预期的存储系统。这就是底层工程师最爽的时刻。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 12:53:48

YT8521SH网络调试实战:RGMII时序与LED配置避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 12:53:29

STM32上搭建Zephyr RTOS开发环境:从零开始点亮板载LED

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 12:52:29

AI编程工具选型指南:Cursor、Trae、OpenCode核心定位与实战边界

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 12:51:50

UPS配电三要素匹配:空开、线缆、蓄电池闭环校验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 12:50:46

重复IP冲突排查指南:从ARP、DHCP到Nmap检测与预防

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 12:49:12

AMS1117-3.3实战指南:5V转3.3V的LDO电源设计全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华