mmc-utils: Expand 'writeprotect boot'

Message ID 20200316202221.107714-1-jwerner@chromium.org
State New
Headers show
Series
  • mmc-utils: Expand 'writeprotect boot'
Related show

Commit Message

Julius Werner March 16, 2020, 8:22 p.m.
This patch updates 'mmc writeprotect boot set' with a few more optional
parameters, so that it can be used to enable permanent write-protection
and so that the two boot partitions can be protected independently. It
also splits protection information output by 'mmc writeprotect boot get'
by partition.

(Note: eMMC boot partitions are named "Area 1" and "Area 2" by the eMMC
spec, but mmcblk0boot0 and mmcblk0boot1 by Linux. To avoid confusion
between the two numbering schemes, this patch uses 0 and 1 throughout,
even when defining EXT_CSD register bits.)

Signed-off-by: Julius Werner <jwerner@chromium.org>
---
 mmc.c      | 17 ++++++++++---
 mmc.h      | 11 ++++++++
 mmc_cmds.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++------
 3 files changed, 91 insertions(+), 11 deletions(-)

Patch

diff --git a/mmc.c b/mmc.c
index 50c9c9e..9e46072 100644
--- a/mmc.c
+++ b/mmc.c
@@ -64,9 +64,20 @@  static struct Command commands[] = {
 	  NULL
 	},
 	{ do_writeprotect_boot_set, -1,
-	  "writeprotect boot set", "<device>\n"
-		"Set the boot partitions write protect status for <device>.\nThis sets the eMMC boot partitions to be write-protected until\nthe next boot.",
-	  NULL
+	  "writeprotect boot set",
+#ifdef DANGEROUS_COMMANDS_ENABLED
+		"[-p] "
+#endif /* DANGEROUS_COMMANDS_ENABLED */
+		"<device> [<number>]\n"
+		"Set the boot partition write protect status for <device>.\n"
+		"If <number> is passed (0 or 1), only protect that particular\n"
+		"eMMC boot partition, otherwise protect both. It will be\n"
+		"write-protected until the next boot.\n"
+#ifdef DANGEROUS_COMMANDS_ENABLED
+		"  -p  Protect partition permanently instead.\n"
+		"      NOTE! -p is a one-time programmable (unreversible) change.\n"
+#endif /* DANGEROUS_COMMANDS_ENABLED */
+	  , NULL
 	},
 	{ do_writeprotect_user_set, -4,
 	  "writeprotect user set", "<type>" "<start block>" "<blocks>" "<device>\n"
diff --git a/mmc.h b/mmc.h
index 648fb26..e3cb621 100644
--- a/mmc.h
+++ b/mmc.h
@@ -74,6 +74,7 @@ 
 #define EXT_CSD_PART_CONFIG		179
 #define EXT_CSD_BOOT_BUS_CONDITIONS	177
 #define EXT_CSD_ERASE_GROUP_DEF		175
+#define EXT_CSD_BOOT_WP_STATUS		174
 #define EXT_CSD_BOOT_WP			173
 #define EXT_CSD_USER_WP			171
 #define EXT_CSD_FW_CONFIG		169	/* R/W */
@@ -143,9 +144,19 @@ 
 #define EXT_CSD_HPI_SUPP		(1<<0)
 #define EXT_CSD_HPI_IMPL		(1<<1)
 #define EXT_CSD_CMD_SET_NORMAL		(1<<0)
+/* NOTE: The eMMC spec calls the partitions "Area 1" and "Area 2", but Linux
+ * calls them mmcblk0boot0 and mmcblk0boot1. To avoid confustion between the two
+ * numbering schemes, this tool uses 0 and 1 throughout. */
+#define EXT_CSD_BOOT_WP_S_AREA_1_PERM	(0x08)
+#define EXT_CSD_BOOT_WP_S_AREA_1_PWR	(0x04)
+#define EXT_CSD_BOOT_WP_S_AREA_0_PERM	(0x02)
+#define EXT_CSD_BOOT_WP_S_AREA_0_PWR	(0x01)
+#define EXT_CSD_BOOT_WP_B_SEC_WP_SEL	(0x80)
 #define EXT_CSD_BOOT_WP_B_PWR_WP_DIS	(0x40)
 #define EXT_CSD_BOOT_WP_B_PERM_WP_DIS	(0x10)
+#define EXT_CSD_BOOT_WP_B_PERM_WP_SEC_SEL (0x08)
 #define EXT_CSD_BOOT_WP_B_PERM_WP_EN	(0x04)
+#define EXT_CSD_BOOT_WP_B_PWR_WP_SEC_SEL (0x02)
 #define EXT_CSD_BOOT_WP_B_PWR_WP_EN	(0x01)
 #define EXT_CSD_BOOT_INFO_HS_MODE	(1<<2)
 #define EXT_CSD_BOOT_INFO_DDR_DDR	(1<<1)
diff --git a/mmc_cmds.c b/mmc_cmds.c
index fb37189..c230127 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -202,11 +202,19 @@  static void print_writeprotect_boot_status(__u8 *ext_csd)
 		else
 			printf("possible\n");
 
-		printf(" ro lock status: ");
-		if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_EN)
+		reg = ext_csd[EXT_CSD_BOOT_WP_STATUS];
+		printf(" partition 0 ro lock status: ");
+		if (reg & EXT_CSD_BOOT_WP_S_AREA_0_PERM)
+			printf("locked permanently\n");
+		else if (reg & EXT_CSD_BOOT_WP_S_AREA_0_PWR)
 			printf("locked until next power on\n");
-		else if (reg & EXT_CSD_BOOT_WP_B_PERM_WP_EN)
+		else
+			printf("not locked\n");
+		printf(" partition 1 ro lock status: ");
+		if (reg & EXT_CSD_BOOT_WP_S_AREA_1_PERM)
 			printf("locked permanently\n");
+		else if (reg & EXT_CSD_BOOT_WP_S_AREA_1_PWR)
+			printf("locked until next power on\n");
 		else
 			printf("not locked\n");
 	}
@@ -260,13 +268,28 @@  int do_writeprotect_boot_set(int nargs, char **argv)
 	__u8 ext_csd[512], value;
 	int fd, ret;
 	char *device;
+	char *end;
+	int argi = 1;
+	int permanent = 0;
+	int partition = -1;
 
-	if (nargs != 2) {
-		fprintf(stderr, "Usage: mmc writeprotect boot set </path/to/mmcblkX>\n");
+#ifdef DANGEROUS_COMMANDS_ENABLED
+	if (!strcmp(argv[argi], "-p")){
+		permanent = 1;
+		argi++;
+	}
+#endif
+
+	if (nargs < 1 + argi ||  nargs > 2 + argi) {
+		fprintf(stderr, "Usage: mmc writeprotect boot set "
+#ifdef DANGEROUS_COMMANDS_ENABLED
+			"[-p] "
+#endif
+			"</path/to/mmcblkX> [0|1]\n");
 		exit(1);
 	}
 
-	device = argv[1];
+	device = argv[argi++];
 
 	fd = open(device, O_RDWR);
 	if (fd < 0) {
@@ -274,14 +297,49 @@  int do_writeprotect_boot_set(int nargs, char **argv)
 		exit(1);
 	}
 
+	if (nargs == 1 + argi) {
+		partition = strtoul(argv[argi], &end, 0);
+		if (*end != '\0' || !(partition == 0 || partition == 1)) {
+			fprintf(stderr, "Invalid partition number (must be 0 or 1): %s\n",
+				argv[argi]);
+			exit(1);
+		}
+	}
+
 	ret = read_extcsd(fd, ext_csd);
 	if (ret) {
 		fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
 		exit(1);
 	}
 
-	value = ext_csd[EXT_CSD_BOOT_WP] |
-		EXT_CSD_BOOT_WP_B_PWR_WP_EN;
+	value = ext_csd[EXT_CSD_BOOT_WP];
+	/*
+	 * If permanent protection is already on for one partition and we're
+	 * trying to enable power-reset protection for the other we need to make
+	 * sure the selection bit for permanent protection still points to the
+	 * former or we'll accidentally permanently protect the latter.
+	 */
+	if ((value & EXT_CSD_BOOT_WP_B_PERM_WP_EN) && !permanent) {
+		if (ext_csd[EXT_CSD_BOOT_WP_STATUS] &
+		    EXT_CSD_BOOT_WP_S_AREA_1_PERM) {
+			value |= EXT_CSD_BOOT_WP_B_PERM_WP_SEC_SEL;
+			if (partition != 1)
+				partition = 0;
+		} else {
+			/* PERM_WP_SEC_SEL cleared -> pointing to partition 0 */
+			if (partition != 0)
+				partition = 1;
+		}
+	}
+	if (partition != -1) {
+		value |= EXT_CSD_BOOT_WP_B_SEC_WP_SEL;
+		if (partition == 1)
+			value |= permanent ? EXT_CSD_BOOT_WP_B_PERM_WP_SEC_SEL
+					   : EXT_CSD_BOOT_WP_B_PWR_WP_SEC_SEL;
+	}
+	value |= permanent ? EXT_CSD_BOOT_WP_B_PERM_WP_EN
+			   : EXT_CSD_BOOT_WP_B_PWR_WP_EN;
+
 	ret = write_extcsd_value(fd, EXT_CSD_BOOT_WP, value);
 	if (ret) {
 		fprintf(stderr, "Could not write 0x%02x to "