blob: 586682434b7387a1420712e262de4e350a7319af [file] [edit]
// SPDX-License-Identifier: GPL-2.0+
/*
* Capsule update support for Qualcomm boards.
*
* Copyright (c) 2024 Linaro Ltd.
* Author: Casey Connolly <casey.connolly@linaro.org>
*/
#define pr_fmt(fmt) "QCOM-FMP: " fmt
#include <dm/device.h>
#include <dm/uclass.h>
#include <efi.h>
#include <efi_loader.h>
#include <malloc.h>
#include <mmc.h>
#include <scsi.h>
#include <part.h>
#include <linux/err.h>
#include "qcom-priv.h"
/*
* To handle different variants like chainloaded U-Boot here we need to
* build the fw_images array dynamically at runtime. These are the possible
* implementations:
*
* - Devices with U-Boot on the uefi_a/b partition
* - Devices with U-Boot on the boot (a/b) partition
* - Devices with U-Boot on the xbl (a/b) partition
*
* Which partition actually has U-Boot on it is determined based on the
* qcom_boot_source variable and additional logic in find_target_partition().
*/
struct efi_fw_image fw_images[] = {
{
.image_index = 1,
},
};
struct efi_capsule_update_info update_info = {
/* Filled in by configure_dfu_string() */
.dfu_string = NULL,
.num_images = ARRAY_SIZE(fw_images),
.images = fw_images,
};
enum target_part_type {
TARGET_PART_UEFI = 1,
TARGET_PART_XBL,
TARGET_PART_BOOT,
};
/* LSB first */
struct part_slot_status {
u16: 2;
u16 active : 1;
u16: 3;
u16 successful : 1;
u16 unbootable : 1;
u16 tries_remaining : 4;
};
enum ab_slot {
SLOT_NONE,
SLOT_A,
SLOT_B,
};
static enum ab_slot get_part_slot(const char *partname)
{
int len = strlen(partname);
if (partname[len - 2] != '_')
return SLOT_NONE;
if (partname[len - 1] == 'a')
return SLOT_A;
if (partname[len - 1] == 'b')
return SLOT_B;
return SLOT_NONE;
}
/* Shamelessly copied from lib/efi_loader/efi_device_path.c @ 33 */
/*
* Determine if an MMC device is an SD card.
*
* @desc block device descriptor
* Return: true if the device is an SD card
*/
static bool is_sd(struct blk_desc *desc)
{
struct mmc *mmc = find_mmc_device(desc->devnum);
if (!mmc)
return false;
return IS_SD(mmc) != 0U;
}
/*
* Determine which partition U-Boot is flashed to based on the boot source (ABL/XBL),
* the slot status, and prioritizing the uefi partition over xbl if found.
*/
static int find_target_partition(int *devnum, enum uclass_id *uclass,
enum target_part_type *target_part_type)
{
int ret;
int partnum, uefi_partnum = -1, xbl_partnum = -1;
struct disk_partition info;
struct part_slot_status *slot_status;
struct udevice *dev = NULL;
struct blk_desc *desc = NULL, *xbl_desc = NULL;
uchar ptn_name[32] = { 0 };
bool have_ufs = false;
/*
* Check to see if we have UFS storage, if so U-Boot MUST be on it and we can skip
* all non-UFS block devices
*/
uclass_foreach_dev_probe(UCLASS_UFS, dev) {
have_ufs = true;
break;
}
uclass_foreach_dev_probe(UCLASS_BLK, dev) {
if (device_get_uclass_id(dev) != UCLASS_BLK)
continue;
desc = dev_get_uclass_plat(dev);
/* If we have a UFS then don't look at any other block devices */
if (have_ufs) {
if (device_get_uclass_id(dev->parent->parent) != UCLASS_UFS)
continue;
}
/*
* If we don't have UFS, then U-Boot must be on the eMMC
*/
else if (IS_ENABLED(CONFIG_MMC) && is_sd(desc)) {
log_debug("skipped SD-Card (devnum %d)\n", desc->devnum);
continue;
}
if (!desc || desc->part_type == PART_TYPE_UNKNOWN)
continue;
for (partnum = 1;; partnum++) {
ret = part_get_info(desc, partnum, &info);
if (ret)
break;
slot_status = (struct part_slot_status *)&info.type_flags;
/*
* Qualcomm Linux devices have a "uefi" partition, it's A/B but the
* flags might not be set so we assume the A partition unless the B
* partition is active.
*/
if (!strncmp(info.name, "uefi", strlen("uefi"))) {
/*
* If U-Boot was chainloaded somehow we can't be flashed to
* the uefi partition
*/
if (qcom_boot_source != QCOM_BOOT_SOURCE_XBL)
continue;
*target_part_type = TARGET_PART_UEFI;
/*
* Found an active UEFI partition, this is where U-Boot is
* flashed.
*/
if (slot_status->active)
goto found;
/* Prefer A slot if it's not marked active */
if (get_part_slot(info.name) == SLOT_A) {
/*
* If we found the A slot after the B slot (both
* inactive) then we assume U-Boot is on the A slot.
*/
if (uefi_partnum >= 0)
goto found;
/* Didn't find the B slot yet */
uefi_partnum = partnum;
strlcpy(ptn_name, info.name, 32);
} else {
/*
* Found inactive B slot after inactive A slot, return
* the A slot
*/
if (uefi_partnum >= 0) {
partnum = uefi_partnum;
goto found;
}
/*
* Didn't find the A slot yet. Record that we found the
* B slot
*/
uefi_partnum = partnum;
strlcpy(ptn_name, info.name, 32);
}
/* xbl and aboot are effectively the same */
} else if ((!strncmp(info.name, "xbl", strlen("xbl")) &&
strlen(info.name) == 5) ||
!strncmp(info.name, "aboot", strlen("aboot"))) {
/*
* If U-Boot was booted via ABL, we can't be flashed to the
* XBL partition
*/
if (qcom_boot_source != QCOM_BOOT_SOURCE_XBL)
continue;
/*
* ignore xbl partition if we have uefi partitions, U-Boot will
* always be on the UEFI partition in this case.
*/
if (*target_part_type == TARGET_PART_UEFI)
continue;
/* Either non-A/B or find the active XBL partition */
if (slot_status->active || !get_part_slot(info.name)) {
/*
* No quick return since we might find a uefi partition
* later
*/
xbl_partnum = partnum;
*target_part_type = TARGET_PART_XBL;
xbl_desc = desc;
strlcpy(ptn_name, info.name, 32);
}
/*
* No fast return since we might also have a uefi partition which
* will take priority.
*/
} else if (!strncmp(info.name, "boot", strlen("boot"))) {
/* We can only be flashed to boot if we were chainloaded */
if (qcom_boot_source != QCOM_BOOT_SOURCE_ANDROID)
continue;
/*
* Either non-A/B or find the active partition. We can return
* immediately here since we've narrowed it down to a single option
*/
if (slot_status->active || !get_part_slot(info.name)) {
*target_part_type = TARGET_PART_BOOT;
goto found;
}
}
}
}
/*
* Now we've exhausted all options, if we didn't find a uefi partition
* then we are indeed flashed to the xbl partition.
*/
if (*target_part_type == TARGET_PART_XBL) {
partnum = xbl_partnum;
desc = xbl_desc;
goto found;
}
/* Found no candidate partitions */
return -1;
found:
if (desc) {
*devnum = desc->devnum;
*uclass = desc->uclass_id;
}
/* info won't match for XBL hence the copy. */
log_info("Capsule update target: %s (disk %d:%d)\n",
*target_part_type == TARGET_PART_BOOT ? info.name : ptn_name,
*devnum, partnum);
return partnum;
}
/**
* qcom_configure_capsule_updates() - Configure the DFU string for capsule updates
*
* U-Boot is flashed to the boot partition on Qualcomm boards. In most cases there
* are two boot partitions, boot_a and boot_b. As we don't currently support doing
* full A/B updates, we only support updating the currently active boot partition.
*
* So we need to find the current slot suffix and the associated boot partition.
* We do this by looking for the boot partition that has the 'active' flag set
* in the GPT partition vendor attribute bits.
*/
void qcom_configure_capsule_updates(void)
{
int ret = 0, partnum = -1, devnum;
static char dfu_string[32] = { 0 };
enum target_part_type target_part_type = 0;
enum uclass_id dev_uclass;
if (IS_ENABLED(CONFIG_SCSI)) {
/* Scan for SCSI devices */
ret = scsi_scan(false);
if (ret) {
debug("Failed to scan SCSI devices: %d\n", ret);
return;
}
}
partnum = find_target_partition(&devnum, &dev_uclass, &target_part_type);
if (partnum < 0) {
log_err("Failed to find boot partition\n");
return;
}
/*
* Set the fw_name based on the partition type. This causes the GUID to be different
* so we will never accidentally flash a U-Boot image intended for XBL to the boot
* partition.
*/
switch (target_part_type) {
case TARGET_PART_UEFI:
fw_images[0].fw_name = u"UBOOT_UEFI_PARTITION";
break;
case TARGET_PART_XBL:
fw_images[0].fw_name = u"UBOOT_XBL_PARTITION";
break;
case TARGET_PART_BOOT:
fw_images[0].fw_name = u"UBOOT_BOOT_PARTITION";
break;
}
switch (dev_uclass) {
case UCLASS_SCSI:
snprintf(dfu_string, 32, "scsi %d=u-boot.bin part %d", devnum, partnum);
break;
case UCLASS_MMC:
snprintf(dfu_string, 32, "mmc 0=u-boot.bin part %d %d", devnum, partnum);
break;
default:
debug("Unsupported storage uclass: %d\n", dev_uclass);
return;
}
log_debug("DFU string: '%s'\n", dfu_string);
update_info.dfu_string = dfu_string;
}