mmc-utils: add check for max enhanced user area

In addition to user area, General purpose partition can be
be marked with enhanced attribute, retain enhanced attributes of
gp partition while creating enhanced user area and add
check for max enhanced area of the device.

Signed-off-by: Balaji T K <balajitk@ti.com>
Signed-off-by: Chris Ball <chris@printf.net>
diff --git a/mmc.h b/mmc.h
index 5fe5fec..7418133 100644
--- a/mmc.h
+++ b/mmc.h
@@ -63,6 +63,18 @@
 #define EXT_CSD_MAX_ENH_SIZE_MULT_0	157
 #define EXT_CSD_PARTITIONS_ATTRIBUTE	156	/* R/W */
 #define EXT_CSD_PARTITION_SETTING_COMPLETED	155	/* R/W */
+#define EXT_CSD_GP_SIZE_MULT_4_2	154
+#define EXT_CSD_GP_SIZE_MULT_4_1	153
+#define EXT_CSD_GP_SIZE_MULT_4_0	152
+#define EXT_CSD_GP_SIZE_MULT_3_2	151
+#define EXT_CSD_GP_SIZE_MULT_3_1	150
+#define EXT_CSD_GP_SIZE_MULT_3_0	149
+#define EXT_CSD_GP_SIZE_MULT_2_2	148
+#define EXT_CSD_GP_SIZE_MULT_2_1	147
+#define EXT_CSD_GP_SIZE_MULT_2_0	146
+#define EXT_CSD_GP_SIZE_MULT_1_2	145
+#define EXT_CSD_GP_SIZE_MULT_1_1	144
+#define EXT_CSD_GP_SIZE_MULT_1_0	143
 #define EXT_CSD_ENH_SIZE_MULT_2		142
 #define EXT_CSD_ENH_SIZE_MULT_1		141
 #define EXT_CSD_ENH_SIZE_MULT_0		140
@@ -111,6 +123,10 @@
 #define EXT_CSD_PART_CONFIG_ACC_ACK	  (0x40)
 #define EXT_CSD_PARTITIONING_EN		(1<<0)
 #define EXT_CSD_ENH_ATTRIBUTE_EN	(1<<1)
+#define EXT_CSD_ENH_4			(1<<4)
+#define EXT_CSD_ENH_3			(1<<3)
+#define EXT_CSD_ENH_2			(1<<2)
+#define EXT_CSD_ENH_1			(1<<1)
 #define EXT_CSD_ENH_USR			(1<<0)
 
 /* From kernel linux/mmc/core.h */
diff --git a/mmc_cmds.c b/mmc_cmds.c
index cea943f..2fc8d4a 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -498,6 +498,89 @@
 	return 0;
 }
 
+int check_enhanced_area_total_limit(const char * const device, int fd)
+{
+	__u8 ext_csd[512];
+	__u32 regl;
+	unsigned long max_enh_area_sz, user_area_sz, enh_area_sz = 0;
+	unsigned long gp4_part_sz, gp3_part_sz, gp2_part_sz, gp1_part_sz;
+	unsigned int wp_sz, erase_sz;
+	int ret;
+
+	ret = read_extcsd(fd, ext_csd);
+	if (ret) {
+		fprintf(stderr, "Could not read EXT_CSD from %s\n", device);
+		exit(1);
+	}
+	wp_sz = get_hc_wp_grp_size(ext_csd);
+	erase_sz = get_hc_erase_grp_size(ext_csd);
+
+	regl = (ext_csd[EXT_CSD_GP_SIZE_MULT_4_2] << 16) |
+		(ext_csd[EXT_CSD_GP_SIZE_MULT_4_1] << 8) |
+		ext_csd[EXT_CSD_GP_SIZE_MULT_4_0];
+	gp4_part_sz = 512l * regl * erase_sz * wp_sz;
+	if (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & EXT_CSD_ENH_4) {
+		enh_area_sz += gp4_part_sz;
+		printf("Enhanced GP4 Partition Size [GP_SIZE_MULT_4]: 0x%06x\n", regl);
+		printf(" i.e. %lu KiB\n", gp4_part_sz);
+	}
+
+	regl = (ext_csd[EXT_CSD_GP_SIZE_MULT_3_2] << 16) |
+		(ext_csd[EXT_CSD_GP_SIZE_MULT_3_1] << 8) |
+		ext_csd[EXT_CSD_GP_SIZE_MULT_3_0];
+	gp3_part_sz = 512l * regl * erase_sz * wp_sz;
+	if (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & EXT_CSD_ENH_3) {
+		enh_area_sz += gp3_part_sz;
+		printf("Enhanced GP3 Partition Size [GP_SIZE_MULT_3]: 0x%06x\n", regl);
+		printf(" i.e. %lu KiB\n", gp3_part_sz);
+	}
+
+	regl = (ext_csd[EXT_CSD_GP_SIZE_MULT_2_2] << 16) |
+		(ext_csd[EXT_CSD_GP_SIZE_MULT_2_1] << 8) |
+		ext_csd[EXT_CSD_GP_SIZE_MULT_2_0];
+	gp2_part_sz = 512l * regl * erase_sz * wp_sz;
+	if (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & EXT_CSD_ENH_2) {
+		enh_area_sz += gp2_part_sz;
+		printf("Enhanced GP2 Partition Size [GP_SIZE_MULT_2]: 0x%06x\n", regl);
+		printf(" i.e. %lu KiB\n", gp2_part_sz);
+	}
+
+	regl = (ext_csd[EXT_CSD_GP_SIZE_MULT_1_2] << 16) |
+		(ext_csd[EXT_CSD_GP_SIZE_MULT_1_1] << 8) |
+		ext_csd[EXT_CSD_GP_SIZE_MULT_1_0];
+	gp1_part_sz = 512l * regl * erase_sz * wp_sz;
+	if (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & EXT_CSD_ENH_1) {
+		enh_area_sz += gp1_part_sz;
+		printf("Enhanced GP1 Partition Size [GP_SIZE_MULT_1]: 0x%06x\n", regl);
+		printf(" i.e. %lu KiB\n", gp1_part_sz);
+	}
+
+	regl = (ext_csd[EXT_CSD_ENH_SIZE_MULT_2] << 16) |
+		(ext_csd[EXT_CSD_ENH_SIZE_MULT_1] << 8) |
+		ext_csd[EXT_CSD_ENH_SIZE_MULT_0];
+	user_area_sz = 512l * regl * erase_sz * wp_sz;
+	if (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & EXT_CSD_ENH_USR) {
+		enh_area_sz += user_area_sz;
+		printf("Enhanced User Data Area Size [ENH_SIZE_MULT]: 0x%06x\n", regl);
+		printf(" i.e. %lu KiB\n", user_area_sz);
+	}
+
+	regl = (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT_2] << 16) |
+		(ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT_1] << 8) |
+		ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT_0];
+	max_enh_area_sz = 512l * regl * erase_sz * wp_sz;
+	printf("Max Enhanced Area Size [MAX_ENH_SIZE_MULT]: 0x%06x\n", regl);
+	printf(" i.e. %lu KiB\n", max_enh_area_sz);
+	if (enh_area_sz > max_enh_area_sz) {
+		fprintf(stderr,
+			"Programmed total enhanced size %lu KiB cannot exceed max enhanced area %lu KiB %s\n",
+			enh_area_sz, max_enh_area_sz, device);
+		return 1;
+	}
+
+	return 0;
+}
+
 int do_enh_area_set(int nargs, char **argv)
 {
 	__u8 value;
@@ -619,8 +702,8 @@
 			EXT_CSD_ENH_SIZE_MULT_0, device);
 		exit(1);
 	}
-
-	ret = write_extcsd_value(fd, EXT_CSD_PARTITIONS_ATTRIBUTE, EXT_CSD_ENH_USR);
+	value = ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] | EXT_CSD_ENH_USR;
+	ret = write_extcsd_value(fd, EXT_CSD_PARTITIONS_ATTRIBUTE, value);
 	if (ret) {
 		fprintf(stderr, "Could not write EXT_CSD_ENH_USR to "
 			"EXT_CSD[%d] in %s\n",
@@ -628,6 +711,10 @@
 		exit(1);
 	}
 
+	ret = check_enhanced_area_total_limit(device, fd);
+	if (ret)
+		exit(1);
+
 	printf("Done setting ENH_USR area on %s\n", device);
 
 	if (!set_partitioning_setting_completed(dry_run, device, fd))