Caleb Connolly | 2bdb252 | 2024-10-12 15:57:19 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * Capsule update support for Qualcomm boards. |
| 4 | * |
| 5 | * Copyright (c) 2024 Linaro Ltd. |
| 6 | * Author: Caleb Connolly <caleb.connolly@linaro.org> |
| 7 | */ |
| 8 | |
| 9 | #define pr_fmt(fmt) "QCOM-FMP: " fmt |
| 10 | |
| 11 | #include <dm/device.h> |
| 12 | #include <dm/uclass.h> |
| 13 | #include <efi.h> |
| 14 | #include <efi_loader.h> |
| 15 | #include <malloc.h> |
| 16 | #include <scsi.h> |
| 17 | #include <part.h> |
| 18 | #include <linux/err.h> |
| 19 | |
| 20 | #include "qcom-priv.h" |
| 21 | |
| 22 | /* |
| 23 | * NOTE: for now this implementation only supports the rb3gen2. Supporting other |
| 24 | * boards that boot in different ways (e.g. chainloaded from ABL) will require |
| 25 | * additional complexity to properly create the dfu string and fw_images array. |
| 26 | */ |
| 27 | |
| 28 | /* |
| 29 | * To handle different variants like chainloaded U-Boot here we'll need to |
| 30 | * build the fw_images array dynamically at runtime. It looks like |
| 31 | * mach-rockchip is a good example for how to do this. |
| 32 | * Detecting which image types a board uses is TBD, hence for now we only |
| 33 | * support the one new board that runs U-Boot as its primary bootloader. |
| 34 | */ |
| 35 | struct efi_fw_image fw_images[] = { |
| 36 | { |
| 37 | /* U-Boot flashed to the uefi_X partition (e.g. rb3gen2) */ |
| 38 | .fw_name = u"UBOOT_UEFI_PARTITION", |
| 39 | .image_index = 1, |
| 40 | }, |
| 41 | }; |
| 42 | |
| 43 | struct efi_capsule_update_info update_info = { |
| 44 | /* Filled in by configure_dfu_string() */ |
| 45 | .dfu_string = NULL, |
| 46 | .num_images = ARRAY_SIZE(fw_images), |
| 47 | .images = fw_images, |
| 48 | }; |
| 49 | |
| 50 | /* LSB first */ |
| 51 | struct part_slot_status { |
| 52 | u16: 2; |
| 53 | u16 active : 1; |
| 54 | u16: 3; |
| 55 | u16 successful : 1; |
| 56 | u16 unbootable : 1; |
| 57 | u16 tries_remaining : 4; |
| 58 | }; |
| 59 | |
| 60 | static int find_boot_partition(const char *partname, struct blk_desc *blk_dev, char *name) |
| 61 | { |
| 62 | int ret; |
| 63 | int partnum; |
| 64 | struct disk_partition info; |
| 65 | struct part_slot_status *slot_status; |
| 66 | |
| 67 | for (partnum = 1;; partnum++) { |
| 68 | ret = part_get_info(blk_dev, partnum, &info); |
| 69 | if (ret) |
| 70 | return ret; |
| 71 | |
| 72 | slot_status = (struct part_slot_status *)&info.type_flags; |
| 73 | log_io("%16s: Active: %1d, Successful: %1d, Unbootable: %1d, Tries left: %1d\n", |
| 74 | info.name, slot_status->active, |
| 75 | slot_status->successful, slot_status->unbootable, |
| 76 | slot_status->tries_remaining); |
| 77 | /* |
| 78 | * FIXME: eventually we'll want to find the active/inactive variant of the partition |
| 79 | * but on the rb3gen2 these values might all be 0 |
| 80 | */ |
| 81 | if (!strncmp(info.name, partname, strlen(partname))) { |
| 82 | log_debug("Found active %s partition: '%s'!\n", partname, info.name); |
| 83 | strlcpy(name, info.name, sizeof(info.name)); |
| 84 | return partnum; |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | return -1; |
| 89 | } |
| 90 | |
| 91 | /** |
| 92 | * qcom_configure_capsule_updates() - Configure the DFU string for capsule updates |
| 93 | * |
| 94 | * U-Boot is flashed to the boot partition on Qualcomm boards. In most cases there |
| 95 | * are two boot partitions, boot_a and boot_b. As we don't currently support doing |
| 96 | * full A/B updates, we only support updating the currently active boot partition. |
| 97 | * |
| 98 | * So we need to find the current slot suffix and the associated boot partition. |
| 99 | * We do this by looking for the boot partition that has the 'active' flag set |
| 100 | * in the GPT partition vendor attribute bits. |
| 101 | */ |
| 102 | void qcom_configure_capsule_updates(void) |
| 103 | { |
| 104 | struct blk_desc *desc; |
| 105 | int ret = 0, partnum = -1, devnum; |
| 106 | static char dfu_string[32] = { 0 }; |
| 107 | char name[32]; /* GPT partition name */ |
| 108 | char *partname = "uefi_a"; |
| 109 | struct udevice *dev = NULL; |
| 110 | |
| 111 | if (IS_ENABLED(CONFIG_SCSI)) { |
| 112 | /* Scan for SCSI devices */ |
| 113 | ret = scsi_scan(false); |
| 114 | if (ret) { |
| 115 | debug("Failed to scan SCSI devices: %d\n", ret); |
| 116 | return; |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | uclass_foreach_dev_probe(UCLASS_BLK, dev) { |
| 121 | if (device_get_uclass_id(dev) != UCLASS_BLK) |
| 122 | continue; |
| 123 | |
| 124 | desc = dev_get_uclass_plat(dev); |
| 125 | if (!desc || desc->part_type == PART_TYPE_UNKNOWN) |
| 126 | continue; |
| 127 | devnum = desc->devnum; |
| 128 | partnum = find_boot_partition(partname, desc, |
| 129 | name); |
| 130 | if (partnum >= 0) |
| 131 | break; |
| 132 | } |
| 133 | |
| 134 | if (partnum < 0) { |
| 135 | log_err("Failed to find boot partition\n"); |
| 136 | return; |
| 137 | } |
| 138 | |
| 139 | switch (desc->uclass_id) { |
| 140 | case UCLASS_SCSI: |
| 141 | snprintf(dfu_string, 32, "scsi %d=u-boot.bin part %d", devnum, partnum); |
| 142 | break; |
| 143 | case UCLASS_MMC: |
| 144 | snprintf(dfu_string, 32, "mmc 0=u-boot.bin part %d %d", devnum, partnum); |
| 145 | break; |
| 146 | default: |
| 147 | debug("Unsupported storage uclass: %d\n", desc->uclass_id); |
| 148 | return; |
| 149 | } |
| 150 | log_debug("boot partition is %s, DFU string: '%s'\n", name, dfu_string); |
| 151 | |
| 152 | update_info.dfu_string = dfu_string; |
| 153 | } |