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_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",
};