[vim3] add unbootable reason fastboot var

Adds `slot-unbootable-reason:{a,b}` fastboot variables to report the
A/B/R unbootable reason, or "N/A" if the slot is not unbootable.

Bug: b/378736089
Test: corrupted slot B and tried to boot, unbootable reason correctly
      reported "3 (verification failure)"
Change-Id: I22fb52a085f3c3b492d0873e21844a39f68ef27b
Reviewed-on: https://turquoise-internal-review.googlesource.com/c/third_party/u-boot/+/919651
Reviewed-by: Yecheng Zhao <zyecheng@google.com>
GitOrigin-RevId: 74f1413fc26635c3ddea0741a96185794a89b4a6
diff --git a/drivers/usb/gadget/f_fastboot.c b/drivers/usb/gadget/f_fastboot.c
index 7ac2768..b0b1032 100644
--- a/drivers/usb/gadget/f_fastboot.c
+++ b/drivers/usb/gadget/f_fastboot.c
@@ -708,6 +708,8 @@
 	"slot-suffixes",
 	"slot-unbootable:a",
 	"slot-unbootable:b",
+	"slot-unbootable-reason:a",
+	"slot-unbootable-reason:b",
 	"version",
 	"version-bootloader",
 	"vx-locked",
@@ -981,6 +983,47 @@
 				slot_info->is_marked_successful ? "yes" : "no",
 				chars_left);
 		}
+	// Note: this must come before `slot-unbootable` because we use prefix
+	// matching to identify the command w/o any args, so `slot-unbootable`
+	// would match here if it came first.
+	} else if (!strcmp_l1("slot-unbootable-reason", cmd)) {
+		strsep(&cmd, ":");
+		FB_DBG("cmd is %s\n", cmd);
+
+		AbrSlotInfo *slot_info = get_slot_info(cmd);
+		if (slot_info == NULL) {
+			fastboot_fail("Invalid slot suffix");
+		} else if (slot_info->is_bootable) {
+			strncat(response, "N/A", chars_left);
+		} else {
+			const char *str = "unknown";
+			// Cast to `AbrUnbootableReason` so the compiler checks
+			// that we've handled all the known cases (but it still
+			// might not be one of these values if the metadata
+			// writer had a newer libabr version with additional
+			// reasons we don't know about).
+			AbrUnbootableReason reason =
+				slot_info->unbootable_reason;
+			switch (reason) {
+			case kAbrUnbootableReasonNone:
+				str = "none given";
+				break;
+			case kAbrUnbootableReasonNoMoreTries:
+				str = "no more attempts";
+				break;
+			case kAbrUnbootableReasonOsRequested:
+				str = "OS requested";
+				break;
+			case kAbrUnbootableReasonVerificationFailure:
+				str = "verification failure";
+				break;
+			}
+			// Give a human-readable string as well as the raw value
+			// for programmatic parsing and in case of unknown
+			// values.
+			snprintf(response + strlen(response), chars_left,
+				 "%d (%s)", slot_info->unbootable_reason, str);
+		}
 	} else if (!strcmp_l1("slot-unbootable", cmd)) {
 		strsep(&cmd, ":");
 		FB_DBG("cmd is %s\n", cmd);