[PATCH] mmc: Set write protect for eMMC user area

Роман rour at inbox.ru
Tue Jul 15 17:04:07 CEST 2025


From 29246e776674a73e4dd93ee15f7c94cb5d99a797 Mon Sep 17 00:00:00 2001
From: Urovsky R <urovsky at swemel.ru>
Date: Sat, 12 Jul 2025 19:29:53 +0300
Subject: [PATCH] mmc: Set write protect for eMMC user area
 
Set write protect configuration for the specified region of the user area for the device.
Available modes for region are: 'none', 'temp', 'poweron'.
'perm' mode not realized. You better use mmc-utils from Linux for that mode.
The idea and most of the source code take from project "mmc-utils"
 
Signed-off-by: Urovsky Roman <rour at inbox.ru>
---
 cmd/mmc.c         | 373 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/mmc/mmc.c |   2 +-
 include/mmc.h     |  16 ++
 3 files changed, 390 insertions(+), 1 deletion(-)
 
diff --git a/cmd/mmc.c b/cmd/mmc.c
index 5340a58be8e..d8c6def4a1a 100644
--- a/cmd/mmc.c
+++ b/cmd/mmc.c
@@ -18,6 +18,26 @@
 #include <vsprintf.h>
 #include <linux/ctype.h>
 
+extern int mmc_poll_for_busy(struct mmc *mmc, int timeout_ms);
+extern int mmc_send_cmd_retry(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data, uint retries);
+
+#define WP_BLKS_PER_QUERY 32
+
+#define WPTYPE_NONE 0
+#define WPTYPE_TEMP 1
+#define WPTYPE_PWRON 2
+#define WPTYPE_PERM 3
+
+static int do_writeprotect_user_get(void);
+static int do_writeprotect_user_set(int argc, char *const argv[]);
+
+static char *prot_desc[] = {
+        "No",
+        "Temporary",
+        "Power-on",
+        "Permanent"
+};
+
 static int curr_device = -1;
 
 static void print_mmcinfo(struct mmc *mmc)
@@ -1228,10 +1248,33 @@ static int do_mmc_reg(struct cmd_tbl *cmdtp, int flag,
 }
 #endif
 
+int do_mmc_user_wp(struct cmd_tbl *cmdtp, int flag,
+                   int argc, char * const argv[])
+{
+    int err;
+/*
+    printf("argc = %d\n", argc);
+    for (int i = 0; i < argc; i++) {
+        printf("argv[%d] = %s\n", i, argv[i]);
+    }
+*/
+    if ( argc >= 2 ) {
+        err = do_writeprotect_user_set(argc, argv);
+
+        if (err)
+            return CMD_RET_FAILURE;
+    }
+
+    do_writeprotect_user_get();
+
+    return CMD_RET_SUCCESS;
+}
+
 static struct cmd_tbl cmd_mmc[] = {
     U_BOOT_CMD_MKENT(info, 1, 0, do_mmcinfo, "", ""),
     U_BOOT_CMD_MKENT(read, 4, 1, do_mmc_read, "", ""),
     U_BOOT_CMD_MKENT(wp, 2, 0, do_mmc_boot_wp, "", ""),
+    U_BOOT_CMD_MKENT(user-wp, 4, 0, do_mmc_user_wp, "", ""),
 #if CONFIG_IS_ENABLED(MMC_WRITE)
     U_BOOT_CMD_MKENT(write, 4, 0, do_mmc_write, "", ""),
     U_BOOT_CMD_MKENT(erase, 3, 0, do_mmc_erase, "", ""),
@@ -1315,6 +1358,15 @@ U_BOOT_CMD(
     "   PART - [0|1]\n"
     "       : 0 - first boot partition, 1 - second boot partition\n"
     "         if not assigned, write protect all boot partitions\n"
+    "mmc user-wp - Get the write protect info for user area\n"
+    "mmc user-wp [type] all - Set the write protect for the full region of the user area\n"
+    "mmc user-wp [type] [start-block] [blocks] - Set the write protect for the specified region of the user area\n"
+    "   type: \n"
+    "      none  - Clear temporary write protection\n"
+    "      temp  - Set temporary write protection\n"
+    "      pwron - Set write protection until the next power on\n"
+    "   start-block - specifies the first block of the protected area. 'all' - to protect all user area\n"
+    "   blocks      - specifies the size of the protected area in blocks \n"
 #if CONFIG_IS_ENABLED(MMC_HW_PARTITIONING)
     "mmc hwpartition <USER> <GP> <MODE> - does hardware partitioning\n"
     "  arguments (sizes in 512-byte blocks):\n"
@@ -1366,3 +1418,324 @@ U_BOOT_CMD(
     "display MMC info",
     "- display info of the current MMC device"
 );
+
+
+static int get_wp_group_size_in_blks(u8 *ext_csd, u32 *size)
+{
+    u8 ext_csd_rev = ext_csd[EXT_CSD_REV];
+
+    if ((ext_csd_rev < 5) || (ext_csd[EXT_CSD_ERASE_GROUP_DEF] == 0)) {
+        printf("EXT_CSD_REV = %d\n", ext_csd[EXT_CSD_REV]);
+        printf("EXT_CSD_ERASE_GROUP_DEF = %d\n", ext_csd[EXT_CSD_ERASE_GROUP_DEF]);
+        return 1;
+    }
+
+    *size = ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] *
+            ext_csd[EXT_CSD_HC_WP_GRP_SIZE] * 1024;
+    return 0;
+}
+
+
+static int send_write_protect_type(struct mmc *mmc, u32 blk_addr, u64 *group_bits)
+{
+    int ret = 0;
+
+    struct mmc_cmd cmd;
+    struct mmc_data data;
+
+    u8 buf[8] = {0};
+    u64 bits = 0;
+    int x;
+
+
+    /* Get the Card Status Register */
+    cmd.cmdidx = MMC_SEND_WRITE_PROT_TYPE;
+    cmd.resp_type = MMC_RSP_R1 | MMC_CMD_ADTC;
+    cmd.cmdarg = blk_addr;
+
+    data.dest = (char *)buf;
+    data.blocks = 1;
+    data.blocksize = 8;
+    data.flags = MMC_DATA_READ;
+
+    ret = mmc_send_cmd(mmc, &cmd, &data);
+    if ( ret != 0 ) {
+        printf("Error: mmc_send_cmd()\n");
+        return 1;
+    }
+
+
+    for (x = 0; x < sizeof(buf); x++)
+        bits |= (u64)(buf[7 - x]) << (x * 8);
+    *group_bits = bits;
+
+    return 0;
+}
+
+
+
+static void print_wp_status(__u32 wp_sizeblks, __u32 start_group,
+                            __u32 end_group, int rptype)
+{
+    printf("Write Protect Groups %d-%d (Blocks %d-%d), ",
+           start_group, end_group,
+           start_group * wp_sizeblks, ((end_group + 1) * wp_sizeblks) - 1);
+    printf("%s Write Protection\n", prot_desc[rptype]);
+}
+
+
+static int do_writeprotect_user_get(void)
+{
+    int ret;
+    int x;
+    int y = 0;
+    u32 wp_sizeblks;
+    u32 cnt;
+    u64 bits;
+    u32 wpblk;
+    u32 last_wpblk = 0;
+    u32 prot;
+    u32 last_prot = -1;
+    int remain;
+
+    struct mmc *mmc;
+    ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN);
+
+    mmc = init_mmc_device(curr_device, false);
+    if (!mmc) {
+        printf("Error: init_mmc_device()\n");
+        return CMD_RET_FAILURE;
+    }
+    if (IS_SD(mmc)) {
+        printf("It is not an eMMC device\n");
+        return CMD_RET_FAILURE;
+    }
+
+    ret = mmc_send_ext_csd(mmc, ext_csd);
+    if (ret) {
+        printf("Could not read EXT_CSD from eMMC\n");
+        return CMD_RET_FAILURE;
+    }
+
+    /*
+     * Host needs to enable ERASE_GRP_DEF bit if device is
+     * partitioned. This bit will be lost every time after a reset
+     * or power off. This will affect erase size.
+     */
+    ext_csd[EXT_CSD_ERASE_GROUP_DEF] = 1;
+
+    ret = get_wp_group_size_in_blks(ext_csd, &wp_sizeblks);
+    if (ret) {
+        printf("Error: get_wp_group_size_in_blks()\n");
+        return CMD_RET_FAILURE;
+    }
+
+    printf("   Write Protect Group size in blocks/bytes: %d/%d\n",
+           wp_sizeblks, wp_sizeblks * 512);
+
+    struct blk_desc *bdesc = mmc_get_blk_desc(mmc);
+
+    cnt = bdesc->lba / wp_sizeblks;
+    printf("   Write Protect Groups total: %d\n", cnt);
+
+
+    for (x = 0; x < cnt; x += WP_BLKS_PER_QUERY) {
+        ret = send_write_protect_type(mmc, x * wp_sizeblks, &bits);
+        if (ret)
+            break;
+
+        remain = cnt - x;
+        if (remain > WP_BLKS_PER_QUERY) {
+            remain = WP_BLKS_PER_QUERY;
+        }
+        for (y = 0; y < remain; y++) {
+            prot = (bits >> (y * 2)) & 0x3;
+            if (prot != last_prot) {
+                /* not first time */
+                if (last_prot != -1) {
+                    wpblk = x + y;
+                    print_wp_status(wp_sizeblks, last_wpblk, wpblk - 1, last_prot);
+                    last_wpblk = wpblk;
+                }
+                last_prot = prot;
+            }
+        }
+    }
+    if (last_wpblk != (x + y - 1))
+        print_wp_status(wp_sizeblks, last_wpblk, cnt - 1, last_prot);
+
+    return ret;
+}
+
+
+int write_extcsd_value(struct mmc *mmc, u8 index, u8 value, unsigned int timeout_ms)
+{
+    struct mmc_cmd cmd = {0};
+    int ret;
+
+    cmd.cmdidx = MMC_CMD_SWITCH;
+    cmd.resp_type = MMC_RSP_R1b | MMC_CMD_AC;
+    cmd.cmdarg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
+                 (index << 16) |
+                 (value << 8) |
+                 EXT_CSD_CMD_SET_NORMAL;
+
+    ret = mmc_send_cmd_retry(mmc, &cmd, NULL, 3);
+    if (ret)
+        return ret;
+
+    return ret;
+}
+
+static int set_write_protect(struct mmc *mmc, u32 blk_addr, int on_off)
+{
+    struct mmc_cmd cmd = {0};
+    int ret = 0;
+
+    if (on_off)
+        cmd.cmdidx = MMC_SET_WRITE_PROT;
+    else
+        cmd.cmdidx = MMC_CLEAR_WRITE_PROT;
+    cmd.cmdarg = blk_addr;
+    cmd.resp_type = MMC_RSP_R1b | MMC_CMD_AC;
+
+    ret = mmc_send_cmd(mmc, &cmd, NULL);
+    if ( ret != 0 ) {
+        printf("Error: mmc_send_cmd()\n");
+        return 1;
+    };
+
+    /* Waiting for the ready status */
+    int tmp_timeout_ms = 100;
+    if (mmc_poll_for_busy(mmc, tmp_timeout_ms))
+        return 0;
+
+    return ret;
+}
+
+
+static int do_writeprotect_user_set(int argc, char *const argv[])
+{
+    int ret;
+    int blk_start;
+    int blk_cnt;
+    u32 cnt;
+    u32 wp_sizeblks;
+    u8 user_wp;
+    int x;
+    int wptype;
+
+    struct mmc *mmc;
+    ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN);
+
+    if ( strcmp(argv[2], "all") == 0 )
+        goto norm_continue;
+
+    if ( argc != 4 )
+        goto usage;
+
+norm_continue:
+    mmc = init_mmc_device(curr_device, false);
+    if (!mmc) {
+        printf("Error: init_mmc_device()\n");
+        return CMD_RET_FAILURE;
+    }
+    if (IS_SD(mmc)) {
+        printf("It is not an eMMC device\n");
+        return CMD_RET_FAILURE;
+    }
+
+
+    if (!strcmp(argv[1], "none")) {
+        wptype = WPTYPE_NONE;
+    } else if (!strcmp(argv[1], "temp")) {
+        wptype = WPTYPE_TEMP;
+    } else if (!strcmp(argv[1], "pwron")) {
+        wptype = WPTYPE_PWRON;
+    } else {
+        printf("Error, invalid \"type\"\n");
+        goto usage;
+    }
+
+
+    ret = mmc_send_ext_csd(mmc, ext_csd);
+    if (ret) {
+        printf("Could not read EXT_CSD from eMMC\n");
+        return CMD_RET_FAILURE;
+    }
+
+    /*
+     * Host needs to enable ERASE_GRP_DEF bit if device is
+     * partitioned. This bit will be lost every time after a reset
+     * or power off. This will affect erase size.
+     */
+    ext_csd[EXT_CSD_ERASE_GROUP_DEF] = 1;
+
+    ret = get_wp_group_size_in_blks(ext_csd, &wp_sizeblks);
+    if (ret) {
+        printf("Error: get_wp_group_size_in_blks()\n");
+        return CMD_RET_FAILURE;
+    }
+
+    struct blk_desc *bdesc = mmc_get_blk_desc(mmc);
+    cnt = bdesc->lba / wp_sizeblks;
+
+    if ( strcmp(argv[2], "all") == 0 ) {
+        blk_start = 0;
+        blk_cnt = cnt * wp_sizeblks;
+        //printf("--->   blk_cnt =  %u\n", blk_cnt);
+    } else {
+        blk_start = dectoul(argv[2], NULL);
+        blk_cnt = dectoul(argv[3], NULL);
+    }
+    if ((blk_start % wp_sizeblks) || (blk_cnt % wp_sizeblks)) {
+        printf("<start block> and <blocks> must be a ");
+        printf("multiple of the Write Protect Group (%d)\n", wp_sizeblks);
+        return 1;
+    }
+
+    if ( wptype != WPTYPE_NONE ) {
+        user_wp = ext_csd[EXT_CSD_USER_WP];
+        user_wp &= ~USER_WP_CLEAR;
+        switch (wptype) {
+            case WPTYPE_TEMP:
+                break;
+            case WPTYPE_PWRON:
+                user_wp |= USER_WP_US_PWR_WP_EN;
+                break;
+            case WPTYPE_PERM:
+                user_wp |= USER_WP_US_PERM_WP_EN;
+                break;
+        }
+
+        if (user_wp != ext_csd[EXT_CSD_USER_WP]) {
+            ret = write_extcsd_value(mmc, EXT_CSD_USER_WP, user_wp, 0);
+            if (ret) {
+                printf("Error setting EXT_CSD\n");
+                return 1;
+            }
+        }
+    }
+    for (x = 0; x < blk_cnt; x += wp_sizeblks) {
+        //printf("Set protection at: %d\n", blk_start + x);
+        ret = set_write_protect(mmc, blk_start + x, wptype != WPTYPE_NONE);
+        if (ret) {
+            printf("Could not set write protect for eMMC \n");
+            return 1;
+        }
+    }
+    if (wptype != WPTYPE_NONE) {
+        ret = write_extcsd_value(mmc, EXT_CSD_USER_WP, ext_csd[EXT_CSD_USER_WP], 0);
+        if (ret) {
+            printf("Error restoring EXT_CSD\n");
+            return 1;
+        }
+    }
+
+    return ret;
+
+usage:
+    printf("Usage: mmc user-wp <type> all\n");
+    printf("Usage: mmc user-wp <type> <start block> <blocks>\n");
+    return 1;
+}
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c
index 2c1f4f9c336..2bbe222cda8 100644
--- a/drivers/mmc/mmc.c
+++ b/drivers/mmc/mmc.c
@@ -258,7 +258,7 @@ int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
  *              once
  * Return: 0 if ok, -ve on error
  */
-static int mmc_send_cmd_retry(struct mmc *mmc, struct mmc_cmd *cmd,
+int mmc_send_cmd_retry(struct mmc *mmc, struct mmc_cmd *cmd,
                   struct mmc_data *data, uint retries)
 {
     int ret;
diff --git a/include/mmc.h b/include/mmc.h
index c6b2ab4a29f..626cf8485e6 100644
--- a/include/mmc.h
+++ b/include/mmc.h
@@ -202,6 +202,11 @@ static inline bool mmc_is_tuning_cmd(uint cmdidx)
                         1 in value field */
 #define MMC_SWITCH_MODE_WRITE_BYTE    0x03 /* Set target byte to value */
 
+#define MMC_SWITCH                 6   /* ac    [31:0] See below    R1b */
+#define MMC_SET_WRITE_PROT       28  /* ac   [31:0] data addr   R1b */
+#define MMC_CLEAR_WRITE_PROT     29  /* ac   [31:0] data addr   R1b */
+#define MMC_SEND_WRITE_PROT_TYPE 31  /* ac   [31:0] data addr   R1  */
+
 #define SD_SWITCH_CHECK        0
 #define SD_SWITCH_SWITCH    1
 
@@ -309,6 +314,15 @@ static inline bool mmc_is_tuning_cmd(uint cmdidx)
 #define EXT_CSD_BOOT_WP_B_PWR_WP_SEC_SEL (0x02)    /* partition selector to protect */
 #define EXT_CSD_BOOT_WP_B_PWR_WP_EN    (0x01)    /* power-on write-protect */
 
+#define USER_WP_PERM_PSWD_DIS    0x80
+#define USER_WP_CD_PERM_WP_DIS    0x40
+#define USER_WP_US_PERM_WP_DIS    0x10
+#define USER_WP_US_PWR_WP_DIS    0x08
+#define USER_WP_US_PERM_WP_EN    0x04
+#define USER_WP_US_PWR_WP_EN    0x01
+#define USER_WP_CLEAR (USER_WP_US_PERM_WP_DIS | USER_WP_US_PWR_WP_DIS    \
+            | USER_WP_US_PERM_WP_EN | USER_WP_US_PWR_WP_EN)
+
 #define EXT_CSD_WR_DATA_REL_USR        (1 << 0)    /* user data area WR_REL */
 #define EXT_CSD_WR_DATA_REL_GP(x)    (1 << ((x)+1))    /* GP part (x+1) WR_REL */
 
@@ -322,6 +336,8 @@ static inline bool mmc_is_tuning_cmd(uint cmdidx)
 #define MMC_RSP_CRC    (1 << 2)        /* expect valid crc */
 #define MMC_RSP_BUSY    (1 << 3)        /* card may send busy */
 #define MMC_RSP_OPCODE    (1 << 4)        /* response contains opcode */
+#define MMC_CMD_AC        (0 << 5)
+#define MMC_CMD_ADTC    (1 << 5)
 
 #define MMC_RSP_NONE    (0)
 #define MMC_RSP_R1    (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
--
2.39.5
 
 
 
 
 


More information about the U-Boot mailing list