[nelson] 14.20230831.4.72 u-boot source

GitOrigin-RevId: 83c221b24156bcacd9f748212ea6f2df82d15607
Change-Id: I5379517a151ff3114a44f6970ee61c516c50ea56
Reviewed-on: https://turquoise-internal-review.googlesource.com/c/third_party/u-boot/+/801610
Reviewed-by: David Pursell <dpursell@google.com>
diff --git a/drivers/usb/gadget/f_fastboot.c b/drivers/usb/gadget/f_fastboot.c
index cfaaa92..fc89a15 100644
--- a/drivers/usb/gadget/f_fastboot.c
+++ b/drivers/usb/gadget/f_fastboot.c
@@ -670,6 +670,82 @@
 	return getvar_response_buffer;
 }
 
+static const char *get_emmc_wear_eol(const char *arg)
+{
+	struct mmc *mmc = find_mmc_device(CONFIG_FASTBOOT_FLASH_MMC_DEV);
+	if (!mmc || !mmc->ext_csd) {
+		return "no eMMC info";
+	}
+
+	const uint8_t value = mmc->ext_csd[EXT_CSD_PRE_EOL_INFO];
+
+	// Convert raw value into something human-readable.
+	// See eMMC spec v5.1 section 7.4.24.
+	const char *readable;
+	switch (value) {
+	case 0x00:
+		readable = "undefined";
+		break;
+	case 0x01:
+		readable = "normal";
+		break;
+	case 0x02:
+		readable = "warning";
+		break;
+	case 0x03:
+		readable = "urgent";
+		break;
+	default:
+		readable = "unknown";
+		break;
+	}
+
+	snprintf(getvar_response_buffer, GETVAR_RESPONSE_BUFFER_LEN, "%s (%d)",
+		 readable, value);
+	return getvar_response_buffer;
+}
+
+static const char *s_emmc_wear_level_list[] = { "A", "B", NULL };
+static const char *get_emmc_wear_level(const char *arg)
+{
+	if (!arg) {
+		return NULL;
+	}
+
+	struct mmc *mmc = find_mmc_device(CONFIG_FASTBOOT_FLASH_MMC_DEV);
+	if (!mmc || !mmc->ext_csd) {
+		return "no eMMC info";
+	}
+
+	uint value = 0;
+	if (strcmp(arg, "A") == 0) {
+		value = mmc->dev_lifetime_est_typ_a;
+	} else if (strcmp(arg, "B") == 0) {
+		value = mmc->dev_lifetime_est_typ_b;
+	} else {
+		return NULL;
+	}
+
+	// Convert raw value into something human-readable.
+	// See eMMC spec v5.1 sections 7.4.22 and 7.4.23.
+	char readable[16];
+	if (value == 0x00) {
+		strncpy(readable, "undefined", sizeof(readable));
+	} else if (value <= 0x0A) {
+		uint low = value * 10;
+		uint high = low + 10;
+		snprintf(readable, sizeof(readable), "%d%%-%d%%", low, high);
+	} else if (value == 0x0B) {
+		strncpy(readable, "EOL", sizeof(readable));
+	} else {
+		strncpy(readable, "unknown", sizeof(readable));
+	}
+
+	snprintf(getvar_response_buffer, GETVAR_RESPONSE_BUFFER_LEN, "%s (%d)",
+		 readable, value);
+	return getvar_response_buffer;
+}
+
 static const char *get_bootloader_board(const char *arg)
 {
 	const char *s = "unknown";
@@ -1023,6 +1099,15 @@
 		.func = get_emmc_vendor,
 	},
 	{
+		.name = "emmc-wear-eol",
+		.func = get_emmc_wear_eol,
+	},
+	{
+		.name = "emmc-wear-level",
+		.func = get_emmc_wear_level,
+		.default_args = s_emmc_wear_level_list,
+	},
+	{
 		.name = "hw-revision",
 		.func = get_hw_revision,
 	},
@@ -1506,14 +1591,26 @@
 		return;
 	}
 
-	char *part_name = req->buf;
+	char buf[FASTBOOT_RESPONSE_LEN];
+	char *command = req->buf;
 
 	// skip past "oem stage-partition"
-	if (!(strsep(&part_name, " ") && strsep(&part_name, " ") &&
-	      part_name)) {
+	if (!(strsep(&command, " ") && strsep(&command, " ") && command)) {
 		fb_fail("No partition given");
 		return;
 	}
+	const char *part_name = command;
+
+	// Optional offset and size params. Mostly useful if a partition is too
+	// big to fit in the fastboot buffer at once.
+	uint64_t offset = 0;
+	uint64_t size = 0;
+	if (strsep(&command, " ")) {
+		offset = simple_strtoul(command, NULL, 0);
+		if (strsep(&command, " ")) {
+			size = simple_strtoul(command, NULL, 0);
+		}
+	}
 
 	zircon_partition *part = zircon_get_partition(part_name);
 
@@ -1524,21 +1621,49 @@
 		return;
 	};
 
-	if (part->size > CONFIG_FASTBOOT_BUF_SIZE || part->size > U32_MAX) {
-		FB_ERR("Partition too large\n");
-		fb_fail("Partition too large");
+	if (offset + size < offset) {
+		fb_fail("(Offset + size) overflow");
 		zircon_free_partition(part);
 		return;
 	}
 
-	if (part->read(part, 0, (void *)CONFIG_FASTBOOT_BUF_ADDR, part->size)) {
-		FB_ERR("Unable to read partition: %s\n", part_name);
+	if (offset >= part->size) {
+		snprintf(buf, sizeof(buf),
+			 "Offset exceeds partition size (%llu)", part->size);
+		fb_fail(buf);
+		zircon_free_partition(part);
+		return;
+	}
+
+	// If size isn't given or overflows, default to the rest of the partition.
+	// Clamping overflow is important for usability, since callers may not
+	// know the partition size. This way they can just request fixed-size chunks
+	// until they receive a smaller piece back then they know they've reached
+	// the end.
+	if (size == 0 || offset + size > part->size) {
+		size = part->size - offset;
+	}
+
+	// Trying to request more than we can fit in the buffer is an error.
+	// If we also clamped here, it would be impossible to distinguish an
+	// end-of-partition short read from a too-large read.
+	if (size > CONFIG_FASTBOOT_BUF_SIZE || size > U32_MAX) {
+		snprintf(buf, sizeof(buf),
+			 "Read too large (max 0x%llX), use offset/size params",
+			 min((uint64_t)CONFIG_FASTBOOT_BUF_SIZE,
+			     (uint64_t)U32_MAX));
+		fb_fail(buf);
+		zircon_free_partition(part);
+		return;
+	}
+
+	if (part->read(part, offset, (void *)CONFIG_FASTBOOT_BUF_ADDR, size)) {
 		fb_fail("Unable to read partition");
 		zircon_free_partition(part);
 		return;
 	}
 
-	upload_size = part->size;
+	upload_size = size;
 	zircon_free_partition(part);
 	fb_okay(NULL);
 }
diff --git a/include/mmc.h b/include/mmc.h
index 2772e63..a146656 100644
--- a/include/mmc.h
+++ b/include/mmc.h
@@ -237,6 +237,7 @@
 #define EXT_CSD_HC_WP_GRP_SIZE		221	/* RO */
 #define EXT_CSD_HC_ERASE_GRP_SIZE	224	/* RO */
 #define EXT_CSD_BOOT_MULT		226	/* RO */
+#define EXT_CSD_PRE_EOL_INFO		267	/* RO */
 #define EXT_CSD_DEV_LIFETIME_EST_TYP_A	268	/* RO */
 #define EXT_CSD_DEV_LIFETIME_EST_TYP_B	269	/* RO */
 #define EXT_CSD_BKOPS_SUPPORT		502	/* RO */