[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