嵌入式设备如何获取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;
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐