mmc-utils: Expand 'writeprotect boot'

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.

BUG=b:151202634
TEST=Enabled different kinds of protection on my Trogdor.

Signed-off-by: Julius Werner <jwerner@chromium.org>
Change-Id: I327fff23675b267a9f75d754ccc47db3f84c294c
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/mmc-utils/+/2103512
Reviewed-by: Gwendal Grignou <gwendal@chromium.org>
diff --git a/mmc.c b/mmc.c
index fe3f479..85e0dbd 100644
--- a/mmc.c
+++ b/mmc.c
@@ -69,8 +69,12 @@
 	  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.",
+	  "writeprotect boot set", "[-p] " "<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"
+		"  -p  Protect partition permanently instead. WARNING: THIS IS IRREVERSIBLE!\n",
 	  NULL
 	},
 	{ do_writeprotect_user_set, -4,
diff --git a/mmc.h b/mmc.h
index 53f5b10..d82e182 100644
--- a/mmc.h
+++ b/mmc.h
@@ -77,6 +77,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 */
@@ -147,9 +148,16 @@
 #define EXT_CSD_HPI_SUPP		(1<<0)
 #define EXT_CSD_HPI_IMPL		(1<<1)
 #define EXT_CSD_CMD_SET_NORMAL		(1<<0)
+#define EXT_CSD_BOOT_WP_S_AREA_2_PERM	(0x08)
+#define EXT_CSD_BOOT_WP_S_AREA_2_PWR	(0x04)
+#define EXT_CSD_BOOT_WP_S_AREA_1_PERM	(0x02)
+#define EXT_CSD_BOOT_WP_S_AREA_1_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 6229292..04ffeab 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -213,11 +213,21 @@
 		else
 			printf("possible\n");
 
-		printf(" ro lock status: ");
-		if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_EN)
-			printf("locked until next power on\n");
-		else if (reg & EXT_CSD_BOOT_WP_B_PERM_WP_EN)
+		/* Note: We call the partitions 0 and 1 (like Linux does), but
+		 * the eMMC spec calls them AREA 1 and AREA 2. */
+		reg = ext_csd[EXT_CSD_BOOT_WP_STATUS];
+		printf(" partition 0 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");
+		printf(" partition 1 ro lock status: ");
+		if (reg & EXT_CSD_BOOT_WP_S_AREA_2_PERM)
+			printf("locked permanently\n");
+		else if (reg & EXT_CSD_BOOT_WP_S_AREA_2_PWR)
+			printf("locked until next power on\n");
 		else
 			printf("not locked\n");
 	}
@@ -271,13 +281,22 @@
 	__u8 ext_csd[EXT_CSD_SIZE], 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");
+	if (!strcmp(argv[argi], "-p")){
+		permanent = 1;
+		argi++;
+	}
+
+	if (nargs < 1 + argi ||  nargs > 2 + argi) {
+		fprintf(stderr, "Usage: mmc writeprotect boot set [-p] </path/to/mmcblkX> [0|1]\n");
 		exit(1);
 	}
 
-	device = argv[1];
+	device = argv[argi++];
 
 	fd = open(device, O_RDWR);
 	if (fd < 0) {
@@ -285,14 +304,49 @@
 		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_2_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 "