如何获取emmc 的健康状态?
3、如果是需要在程序内读取则通过下面的代码进行查询。嵌入式设备如何获取emmc 的健康状态?1、首先挂载debug 文件系统。查询到的值对应下方的寿命状态。
·
嵌入式设备如何获取emmc 的健康状态?
EXT_CSD 寄存器的内容与布局是标准化的,但其中某些保留位或厂商特定字段的解释可能是非标准的。
例如以下几个常用字段的偏移是固定不变的(JEDEC B51 标准节选):
| 偏移 | 字段名 | 大小 | 类型 | 描述 |
|---|---|---|---|---|
| 192 | EXT_CSD_REV | 1 | R | eMMC 版本号(0x08 = v5.1) |
| 212–215 | SEC_COUNT | 4 | R | 用户区扇区总数(512B 单位) |
| 226 | MIN_PERF_W_8_52 | 1 | R | 8-bit@52MHz 写性能等级 |
| 241 | TRIM_MULT | 1 | R | TRIM 命令倍率 |
| 267 | PRE_EOL_INFO | 1 | R | 寿命终止预警信息 |
| 268 | DEVICE_LIFE_TIME_EST_TYP_A | 1 | R | 类型A 寿命估计 |
| 269 | DEVICE_LIFE_TIME_EST_TYP_B | 1 | R | 类型B 寿命估计 |
这些偏移在所有 JEDEC 兼容 eMMC 上都完全相同。
因此你读取 /sys/kernel/debug/mmc*/ext_csd 后的第 268/269 字节,无论厂商是谁,
都代表同一个含义(寿命估计值)。
下面是操作步骤和方法
1、首先挂载
debug 文件系统
mount -t debugfs none /sys/kernel/debug
2、通过命令查询
echo $(cat /sys/kernel/debug/mmc0/mmc0:0001/ext_csd)| cut -c537-538
查询到的值对应下方的寿命状态
3、如果是需要在程序内读取则通过下面的代码进行查询
// 获取emmc 健康状态
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <inttypes.h>
#define EXT_CSD_SIZE 512
/* 关键字节偏移(来自 linux include/linux/mmc/mmc.h & JEDEC) */
#define EXT_CSD_SEC_COUNT 212 /* 4 bytes: 212..215 (小端) */
#define EXT_CSD_PRE_EOL_INFO 267 /* 1 byte */
#define EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A 268 /* 1 byte */
#define EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B 269 /* 1 byte */
#define EXT_CSD_EXT_CSD_REV 192 /* 1 byte */
static int hexval(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
/* 将 ASCII hex(可能带空格、换行)解析为字节 */
static ssize_t parse_hex_to_bytes(const char *in, ssize_t in_len, uint8_t *out, size_t out_len) {
int hi = -1;
size_t out_i = 0;
for (ssize_t i = 0; i < in_len && out_i < out_len; ++i) {
int v = hexval(in[i]);
if (v < 0) continue; /* skip non-hex (space, newline, etc) */
if (hi < 0) {
hi = v;
} else {
out[out_i++] = (uint8_t)((hi << 4) | v);
hi = -1;
}
}
return (ssize_t)out_i;
}
static inline uint8_t get_u8(const uint8_t *e, int idx) { return e[idx]; }
static inline uint32_t get_u32_le(const uint8_t *e, int idx) {
return (uint32_t)e[idx] |
((uint32_t)e[idx+1] << 8) |
((uint32_t)e[idx+2] << 16) |
((uint32_t)e[idx+3] << 24);
}
/* 将 DEVICE_LIFE 字段值转成“已用百分比区间” */
static void life_estimate_range(uint8_t v, int *used_min, int *used_max) {
*used_min = *used_max = -1;
if (v >= 1 && v <= 10) {
*used_min = (v - 1) * 10;
*used_max = v * 10;
} else if (v == 0x0F) { /* 0x0F 通常表示接近或超过寿命(vendor-specific) */
*used_min = 100; *used_max = 100;
} else if (v == 0x00) {
/* 未定义/不可用 */
*used_min = *used_max = -1;
} else {
/* 其它保留/厂商定义值 */
*used_min = *used_max = -1;
}
}
static void human_readable_size(uint64_t bytes, char *buf, size_t bufsz) {
const char *units[] = {"B","KB","MB","GB","TB"};
double d = (double)bytes;
int u = 0;
while (d >= 1024.0 && u < 4) { d /= 1024.0; u++; }
snprintf(buf, bufsz, "%.2f %s", d, units[u]);
}
int main(int argc, char **argv) {
const char *path = "/sys/kernel/debug/mmc0/mmc0:0001/ext_csd";
if (argc > 1) path = argv[1];
int fd = open(path, O_RDONLY);
if (fd < 0) {
perror("open ext_csd");
return 1;
}
/* 读取文件到临时缓冲(兼容二进制和 ASCII-hex) */
char tmp[4096];
ssize_t r = read(fd, tmp, sizeof(tmp));
close(fd);
if (r <= 0) {
perror("read ext_csd");
return 1;
}
uint8_t ext[EXT_CSD_SIZE];
if (r == EXT_CSD_SIZE) {
/* 直接是 512 字节原始二进制 */
memcpy(ext, tmp, EXT_CSD_SIZE);
} else {
/* 很可能是 ASCII hex,需要解析 */
ssize_t got = parse_hex_to_bytes(tmp, r, ext, EXT_CSD_SIZE);
if (got != EXT_CSD_SIZE) {
fprintf(stderr, "无法解析 EXT_CSD:期望 %d 字节,解析到 %zd 字节\n", EXT_CSD_SIZE, got);
return 2;
}
}
/* 解析关键字段 */
uint8_t ext_csd_rev = get_u8(ext, EXT_CSD_EXT_CSD_REV);
uint32_t sec_count = get_u32_le(ext, EXT_CSD_SEC_COUNT);
uint8_t pre_eol = get_u8(ext, EXT_CSD_PRE_EOL_INFO);
uint8_t life_a = get_u8(ext, EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A);
uint8_t life_b = get_u8(ext, EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B);
uint64_t capacity_bytes = (uint64_t)sec_count * 512ULL;
char size_str[64];
human_readable_size(capacity_bytes, size_str, sizeof(size_str));
int a_min, a_max, b_min, b_max;
life_estimate_range(life_a, &a_min, &a_max);
life_estimate_range(life_b, &b_min, &b_max);
printf("EXT_CSD_REV: %u\n", ext_csd_rev);
printf("SEC_COUNT (bytes %d..%d): %" PRIu32 " sectors\n", EXT_CSD_SEC_COUNT, EXT_CSD_SEC_COUNT+3, sec_count);
printf("容量: %" PRIu64 " bytes (%s)\n", (uint64_t)capacity_bytes, size_str);
printf("PRE_EOL_INFO (offset %d): 0x%02X --> ", EXT_CSD_PRE_EOL_INFO, pre_eol);
switch (pre_eol) {
case 0x00: printf("未定义\n"); break;
case 0x01: printf("Normal:预留块消耗 < 80%%(正常)\n"); break;
case 0x02: printf("Warning:预留块消耗 >= 80%%(警告)\n"); break;
case 0x03: printf("Urgent:预留块消耗 >= 90%%(紧急)\n"); break;
default: printf("厂商保留/未知值\n"); break;
}
if (a_min >= 0) printf("DEVICE_LIFE_TIME_EST_TYP_A (offset %d): 0x%02X -> 已用约 %d%% - %d%%\n",
EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A, life_a, a_min, a_max);
else printf("DEVICE_LIFE_TIME_EST_TYP_A (offset %d): 0x%02X -> 未定义/厂商保留\n",
EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A, life_a);
if (b_min >= 0) printf("DEVICE_LIFE_TIME_EST_TYP_B (offset %d): 0x%02X -> 已用约 %d%% - %d%%\n",
EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B, life_b, b_min, b_max);
else printf("DEVICE_LIFE_TIME_EST_TYP_B (offset %d): 0x%02X -> 未定义/厂商保留\n",
EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B, life_b);
return 0;
}
更多推荐



所有评论(0)