mmc-utils: Add blockprotect command set
This patch adds a group of commands that allow controlling an eMMC's
user partition write-protect features, which allow temporary, permanent
or until-next-power-on write-protection of a write-protect block (which
is a device-dependent multiple of an erase block).
BUG=None
TEST=Manual enabling, disabling and writing of a bunch of blocks on my
Minnie.
Change-Id: I75ff069f7f9cf94cdb3adfa0a5f4574f912cad9f
Signed-off-by: Julius Werner <jwerner@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/340252
Reviewed-by: Gwendal Grignou <gwendal@google.com>
diff --git a/mmc.c b/mmc.c
index fa872d5..019f5c3 100644
--- a/mmc.c
+++ b/mmc.c
@@ -204,6 +204,31 @@
"The device path should specify the scr file directory.",
NULL
},
+ { do_blockprotect_enable, -2, "blockprotect enable",
+ "[-p|-r] <device> <sector>\n"
+ "Enable block protection for a given sector. Will write protect all\n"
+ "sectors within the target sector's write protect block. Write protect\n"
+ "block size is a multiple of erase block size and device dependent.\n"
+ "Run mmc blockprotect info to query write protect block size.\n"
+ " -p Protect block permanently. WARNING: THIS IS IRREVERSIBLE!\n"
+ " -r Protect block until next power-on.",
+ NULL
+ },
+ { do_blockprotect_disable, -2, "blockprotect disable",
+ "<device> <sector>\n"
+ "Disable block protection for a given sector. Will disable protection\n"
+ "for all sectors within the target sector's write protect block.\n"
+ "Cannot disable permanent or power-on write protection.",
+ NULL
+ },
+ { do_blockprotect_read, -2, "blockprotect read", "<device> <sector>\n"
+ "Query the current block protection status of a given sector.",
+ NULL
+ },
+ { do_blockprotect_info, -1, "blockprotect info", "<device>\n"
+ "Query information about device's block protect capabilities.",
+ NULL
+ },
{ 0, 0, 0, 0 }
};
diff --git a/mmc.h b/mmc.h
index f89cf28..95e7c96 100644
--- a/mmc.h
+++ b/mmc.h
@@ -31,6 +31,9 @@
#define MMC_SWITCH 6 /* ac [31:0] See below R1b */
#define MMC_SEND_EXT_CSD 8 /* adtc R1 */
#define MMC_SEND_STATUS 13 /* ac [31:16] RCA R1 */
+#define MMC_SET_WRITE_PROT 28 /* ac [31:0] block number R1b */
+#define MMC_CLR_WRITE_PROT 29 /* ac [31:0] block number R1b */
+#define MMC_SEND_WRITE_PROT_TYPE 31 /* adtc [31:0] block number R1 */
#define R1_SWITCH_ERROR (1 << 7) /* sx, c */
#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */
#define MMC_READ_MULTIPLE_BLOCK 18 /* adtc [31:0] data addr R1 */
@@ -58,6 +61,7 @@
#define EXT_CSD_BOOT_BUS_CONDITIONS 177
#define EXT_CSD_ERASE_GROUP_DEF 175
#define EXT_CSD_BOOT_WP 173
+#define EXT_CSD_USER_WP 171
#define EXT_CSD_WR_REL_SET 167
#define EXT_CSD_WR_REL_PARAM 166
#define EXT_CSD_SANITIZE_START 165
@@ -92,6 +96,7 @@
#define EXT_CSD_NATIVE_SECTOR_SIZE 63 /* R */
#define EXT_CSD_USE_NATIVE_SECTOR 62 /* R/W */
#define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */
+#define EXT_CSD_CLASS_6_CTRL 59
#define EXT_CSD_EXT_PARTITIONS_ATTRIBUTE_1 53
#define EXT_CSD_EXT_PARTITIONS_ATTRIBUTE_0 52
#define EXT_CSD_CACHE_CTRL 33
@@ -140,6 +145,10 @@
#define EXT_CSD_ENH_2 (1<<2)
#define EXT_CSD_ENH_1 (1<<1)
#define EXT_CSD_ENH_USR (1<<0)
+#define EXT_CSD_US_PERM_WP_DIS (1<<4)
+#define EXT_CSD_US_PWR_WP_DIS (1<<3)
+#define EXT_CSD_US_PERM_WP_EN (1<<2)
+#define EXT_CSD_US_PWR_WP_EN (1<<0)
#define EXT_CSD_REV_V5_1 8
#define EXT_CSD_REV_V5_0 7
#define EXT_CSD_REV_V4_5 6
diff --git a/mmc_cmds.c b/mmc_cmds.c
index 3c4c937..faa8df3 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -40,6 +40,9 @@
#define FFU_DATA_SIZE 512
#define CID_SIZE 16
+/* Sending several commands too close together seems to cause timeouts. */
+#define INTER_COMMAND_GAP_US (50 * 1000)
+
#include "3rdparty/hmac_sha/hmac_sha2.h"
int read_extcsd(int fd, __u8 *ext_csd)
@@ -1621,6 +1624,223 @@
}
+enum blockprotect_mode {
+ BLOCKPROTECT_TEMPORARY = 0,
+ BLOCKPROTECT_POWERON,
+ BLOCKPROTECT_PERMANENT,
+};
+
+int write_blockprotect(int fd, __u32 sector, int enable)
+{
+ struct mmc_ioc_cmd cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.write_flag = 1;
+ cmd.opcode = enable ? MMC_SET_WRITE_PROT : MMC_CLR_WRITE_PROT;
+ cmd.arg = sector;
+ cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
+
+ ret = ioctl(fd, MMC_IOC_CMD, &cmd);
+ if (ret)
+ perror("SET/CLR_WRITE_PROT command");
+ return ret;
+}
+
+int do_blockprotect_enable(int nargs, char **argv)
+{
+ __u8 ext_csd[EXT_CSD_SIZE];
+ __u8 user_wp;
+ __u32 sector;
+ char *end;
+ int ret, fd;
+ int arg_index = 0;
+ enum blockprotect_mode mode = BLOCKPROTECT_TEMPORARY;
+
+ if (nargs > 0 && !strcmp(argv[1], "-r")) {
+ arg_index++;
+ mode = BLOCKPROTECT_POWERON;
+ } else if (nargs > 0 && !strcmp(argv[1], "-p")) {
+ arg_index++;
+ mode = BLOCKPROTECT_PERMANENT;
+ }
+
+ CHECK(nargs != 3 + arg_index, "Usage: mmc blockprotect enable [-p|-r] <device> <write protect block>\n", exit(1));
+
+ sector = strtoul(argv[2 + arg_index], &end, 0);
+ if (*end != '\0') {
+ fprintf(stderr, "Not a block number: %s\n",
+ argv[2 + arg_index]);
+ exit(1);
+ }
+
+ fd = open(argv[1 + arg_index], O_RDWR);
+ if (fd < 0) {
+ perror("open");
+ exit(1);
+ }
+
+ if (read_extcsd(fd, ext_csd))
+ exit(1);
+
+ user_wp = ext_csd[EXT_CSD_USER_WP];
+ user_wp &= ~(EXT_CSD_US_PERM_WP_EN | EXT_CSD_US_PWR_WP_EN);
+ if (mode == BLOCKPROTECT_POWERON)
+ user_wp |= EXT_CSD_US_PWR_WP_EN;
+ else if (mode == BLOCKPROTECT_PERMANENT)
+ user_wp |= EXT_CSD_US_PERM_WP_EN;
+
+ ret = write_extcsd_value(fd, EXT_CSD_USER_WP, user_wp);
+ if (ret) {
+ perror("update EXT_CSD[USER_WP]");
+ exit(1);
+ }
+
+ usleep(INTER_COMMAND_GAP_US);
+
+ ret = write_blockprotect(fd, sector, 1);
+
+ usleep(INTER_COMMAND_GAP_US);
+
+ user_wp &= ~(EXT_CSD_US_PERM_WP_EN | EXT_CSD_US_PWR_WP_EN);
+ if (write_extcsd_value(fd, EXT_CSD_USER_WP, user_wp)) {
+ perror("reset EXT_CSD[USER_WP]");
+ if (!ret)
+ ret = -1;
+ }
+
+ return ret;
+}
+
+int do_blockprotect_disable(int nargs, char **argv)
+{
+ __u32 sector;
+ char *end;
+ int fd;
+
+ CHECK(nargs != 3, "Usage: mmc blockprotect disable <device> <write protect block>\n", exit(1));
+
+ sector = strtoul(argv[2], &end, 0);
+ if (*end != '\0') {
+ fprintf(stderr, "Not a block number: %s\n", argv[2]);
+ exit(1);
+ }
+
+
+ fd = open(argv[1], O_RDWR);
+ if (fd < 0) {
+ perror("open");
+ exit(1);
+ }
+
+ return write_blockprotect(fd, sector, 0);
+}
+
+int do_blockprotect_read(int nargs, char **argv)
+{
+ __u8 wp_bits[8];
+ __u32 sector;
+ char *end;
+ int fd;
+ struct mmc_ioc_cmd cmd;
+
+ CHECK(nargs != 3, "Usage: mmc blockprotect read <device> <write protect block>\n", exit(1));
+
+ fd = open(argv[1], O_RDWR);
+ if (fd < 0) {
+ perror("open");
+ exit(1);
+ }
+
+ sector = strtoul(argv[2], &end, 0);
+ if (*end != '\0') {
+ fprintf(stderr, "Not a block number: %s\n", argv[2]);
+ exit(1);
+ }
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.write_flag = 0;
+ cmd.opcode = MMC_SEND_WRITE_PROT_TYPE;
+ cmd.arg = sector;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+ cmd.blksz = sizeof(wp_bits);
+ cmd.blocks = 1;
+ mmc_ioc_cmd_set_data(cmd, wp_bits);
+
+ if (ioctl(fd, MMC_IOC_CMD, &cmd)) {
+ perror("SEND_WRITE_PROT_TYPE command");
+ exit(1);
+ }
+
+ printf("Sector %u write protection: ", sector);
+ switch (wp_bits[7] & 3) {
+ case 0:
+ printf("NONE\n");
+ break;
+ case 1:
+ printf("TEMPORARY\n");
+ break;
+ case 2:
+ printf("POWER-ON\n");
+ break;
+ case 3:
+ printf("PERMANENT\n");
+ break;
+ }
+
+ return 0;
+}
+
+int do_blockprotect_info(int nargs, char **argv)
+{
+ __u8 ext_csd[EXT_CSD_SIZE];
+ __u8 user_wp;
+ int fd, wp_sz, erase_sz;
+
+ CHECK(nargs != 2, "Usage: mmc blockprotect info <device>\n", exit(1));
+
+ fd = open(argv[1], O_RDWR);
+ if (fd < 0) {
+ perror("open");
+ exit(1);
+ }
+
+ if (read_extcsd(fd, ext_csd))
+ exit(1);
+
+ if (ext_csd[EXT_CSD_CLASS_6_CTRL] != 0) {
+ fprintf(stderr, "Block protection commands not supported: "
+ "CLASS_6_CTRL set.\n");
+ exit(1);
+ }
+
+ if ((ext_csd[EXT_CSD_ERASE_GROUP_DEF] & 0x1) != 0x1) {
+ fprintf(stderr, "Block protection commands not supported: "
+ "high-capacity sizes not enabled.\n");
+ exit(1);
+ }
+
+ wp_sz = get_hc_wp_grp_size(ext_csd);
+ erase_sz = get_hc_erase_grp_size(ext_csd);
+
+ if (erase_sz == 0 || wp_sz == 0) {
+ fprintf(stderr, "Block protection commands not supported: "
+ "no high-capacity size for erase or WP blocks.\n");
+ exit(1);
+ }
+
+ printf("Write protect block size in sectors: %d\n",
+ erase_sz * wp_sz * 1024);
+
+ user_wp = ext_csd[EXT_CSD_USER_WP];
+ printf("Permanent write protection: %s\n",
+ user_wp & EXT_CSD_US_PERM_WP_DIS ? "forbidden" : "allowed");
+ printf("Power-on write protection: %s\n",
+ user_wp & EXT_CSD_US_PWR_WP_DIS ? "forbidden" : "allowed");
+
+ return 0;
+}
+
static const char* const mmc_ffu_hack_names[] = {
[MMC_OVERRIDE_FFU_ARG] = "ffu_arg",
};
diff --git a/mmc_cmds.h b/mmc_cmds.h
index ff60383..959f370 100644
--- a/mmc_cmds.h
+++ b/mmc_cmds.h
@@ -41,3 +41,7 @@
int do_read_scr(int argc, char **argv);
int do_read_cid(int argc, char **argv);
int do_read_csd(int argc, char **argv);
+int do_blockprotect_enable(int nargs, char **argv);
+int do_blockprotect_disable(int nargs, char **argv);
+int do_blockprotect_read(int nargs, char **argv);
+int do_blockprotect_info(int nargs, char **argv);