From 60db50966d4e30b76db3b38eef0b3704eec25b77 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Sun, 7 Sep 2025 21:18:14 -0500 Subject: [PATCH] qdl: Extend read/write support to accept GPT partition names While already powerful, it's quite often one wants to read and write some specific GPT partition, and manually resolving the sectors and plugging these into either a XML file or the command line is tedious and error prone. Allow partition names in the address specifier of the "read" and "write" command line actions, and when these are used read the GPTs across all physical partitions to resolve the physical partition, start sector and sector count for the operation. This allow us to do things like: qdl prog_firehose.elf write abl_a abl2esp.elf write abl_b abl2esp.elf Signed-off-by: Bjorn Andersson --- Makefile | 2 +- firehose.c | 12 ++- gpt.c | 281 +++++++++++++++++++++++++++++++++++++++++++++++++++++ gpt.h | 10 ++ program.c | 29 +++++- program.h | 5 +- qdl.h | 6 +- read.c | 30 +++++- read.h | 4 +- util.c | 25 ++++- 10 files changed, 388 insertions(+), 16 deletions(-) create mode 100644 gpt.c create mode 100644 gpt.h diff --git a/Makefile b/Makefile index ddd06c9..6f3a187 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ LDFLAGS += -lws2_32 endif prefix := /usr/local -QDL_SRCS := firehose.c io.c qdl.c sahara.c util.c patch.c program.c read.c sha2.c sim.c ufs.c usb.c ux.c oscompat.c vip.c sparse.c +QDL_SRCS := firehose.c io.c qdl.c sahara.c util.c patch.c program.c read.c sha2.c sim.c ufs.c usb.c ux.c oscompat.c vip.c sparse.c gpt.c QDL_OBJS := $(QDL_SRCS:.c=.o) RAMDUMP_SRCS := ramdump.c sahara.c io.c sim.c usb.c util.c ux.c oscompat.c diff --git a/firehose.c b/firehose.c index 5772d82..b1b109d 100644 --- a/firehose.c +++ b/firehose.c @@ -33,8 +33,6 @@ enum { FIREHOSE_NAK, }; -static int firehose_read_buf(struct qdl_device *qdl, struct read_op *read_op, void *out_buf, size_t out_size); - static void xml_setpropf(xmlNode *node, const char *attr, const char *fmt, ...) { xmlChar buf[128]; @@ -680,7 +678,7 @@ out: return ret; } -static int firehose_read_buf(struct qdl_device *qdl, struct read_op *read_op, void *out_buf, size_t out_size) +int firehose_read_buf(struct qdl_device *qdl, struct read_op *read_op, void *out_buf, size_t out_size) { return firehose_issue_read(qdl, read_op, -1, out_buf, out_size, true); } @@ -917,6 +915,14 @@ int firehose_run(struct qdl_device *qdl, const char *incdir, if (ret) return ret; + ret = read_resolve_gpt_deferrals(qdl); + if (ret) + return ret; + + ret = program_resolve_gpt_deferrals(qdl); + if (ret) + return ret; + ret = erase_execute(qdl, firehose_erase); if (ret) return ret; diff --git a/gpt.c b/gpt.c new file mode 100644 index 0000000..b680bd9 --- /dev/null +++ b/gpt.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include +#include +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qdl.h" +#include "gpt.h" + +struct gpt_guid { + uint32_t data1; + uint16_t data2; + uint16_t data3; + uint8_t data4[8]; +} __attribute__((packed)); + +static const struct gpt_guid gpt_zero_guid = {0}; + +struct gpt_header { + uint8_t signature[8]; + uint32_t revision; + uint32_t header_size; + uint32_t header_crc32; + uint32_t reserved; + uint64_t current_lba; + uint64_t backup_lba; + uint64_t first_usable_lba; + uint64_t last_usable_lba; + struct gpt_guid disk_guid; + uint64_t part_entry_lba; + uint32_t num_part_entries; + uint32_t part_entry_size; + uint32_t part_array_crc32; + uint8_t reserved2[420]; +} __attribute__((packed)); + +struct gpt_entry { + struct gpt_guid type_guid; + struct gpt_guid unique_guid; + uint64_t first_lba; + uint64_t last_lba; + uint64_t attrs; + uint16_t name_utf16le[36]; +} __attribute__((packed)); + +struct gpt_partition { + const char *name; + unsigned int partition; + unsigned int start_sector; + unsigned int num_sectors; + + struct gpt_partition *next; +}; + +static struct gpt_partition *gpt_partitions; +static struct gpt_partition *gpt_partitions_last; + +static void utf16le_to_utf8(uint16_t *in, size_t in_len, uint8_t *out, size_t out_len) +{ + uint32_t codepoint; + uint16_t high; + uint16_t low; + uint16_t w; + size_t i; + size_t j = 0; + + for (i = 0; i < in_len; i++) { + w = in[i]; + + if (w >= 0xd800 && w <= 0xdbff) { + high = w - 0xd800; + + if (i < in_len) { + w = in[++i]; + if (w >= 0xdc00 && w <= 0xdfff) { + low = w - 0xdc00; + codepoint = (((uint32_t)high << 10) | low) + 0x10000; + } else { + /* Surrogate without low surrogate */ + codepoint = 0xfffd; + } + } else { + /* Lone high surrogate at end of string */ + codepoint = 0xfffd; + } + } else if (w >= 0xdc00 && w <= 0xdfff) { + /* Low surrogate without high */ + codepoint = 0xfffd; + } else { + codepoint = w; + } + + if (codepoint == 0) + break; + + if (codepoint <= 0x7f) { + if (j + 1 >= out_len) + break; + out[j++] = (uint8_t)codepoint; + } else if (codepoint <= 0x7ff) { + if (j + 2 >= out_len) + break; + out[j++] = 0xc0 | ((codepoint >> 6) & 0x1f); + out[j++] = 0x80 | (codepoint & 0x3f); + } else if (codepoint <= 0xffff) { + if (j + 3 >= out_len) + break; + out[j++] = 0xe0 | ((codepoint >> 12) & 0x0f); + out[j++] = 0x80 | ((codepoint >> 6) & 0x3f); + out[j++] = 0x80 | (codepoint & 0x3f); + } else if (codepoint <= 0x10ffff) { + if (j + 4 >= out_len) + break; + out[j++] = 0xf0 | ((codepoint >> 18) & 0x07); + out[j++] = 0x80 | ((codepoint >> 12) & 0x3f); + out[j++] = 0x80 | ((codepoint >> 6) & 0x3f); + out[j++] = 0x80 | (codepoint & 0x3f); + } + } + + out[j] = '\0'; +} + +static int gpt_load_table_from_partition(struct qdl_device *qdl, unsigned int phys_partition, bool *eof) +{ + struct gpt_partition *partition; + struct gpt_entry *entry; + struct gpt_header gpt; + uint8_t buf[4096]; + struct read_op op; + unsigned int offset; + unsigned int lba; + char lba_buf[10]; + uint16_t name_utf16le[36]; + char name[36 * 4]; + int ret; + int i; + + memset(&op, 0, sizeof(op)); + + op.sector_size = qdl->sector_size; + op.start_sector = "1"; + op.num_sectors = 1; + op.partition = phys_partition; + + memset(&buf, 0, sizeof(buf)); + ret = firehose_read_buf(qdl, &op, &gpt, sizeof(gpt)); + if (ret) { + /* Assume that we're beyond the last partition */ + *eof = true; + return -1; + } + + if (memcmp(gpt.signature, "EFI PART", 8)) { + ux_err("partition %d has not GPT header\n", phys_partition); + return 0; + } + + if (gpt.part_entry_size > qdl->sector_size || gpt.num_part_entries > 1024) { + ux_debug("partition %d has invalid GPT header\n", phys_partition); + return -1; + } + + ux_debug("Loading GPT table from physical partition %d\n", phys_partition); + for (i = 0; i < gpt.num_part_entries; i++) { + offset = (i * gpt.part_entry_size) % qdl->sector_size; + + if (offset == 0) { + lba = gpt.part_entry_lba + i * gpt.part_entry_size / qdl->sector_size; + sprintf(lba_buf, "%u", lba); + op.start_sector = lba_buf; + + memset(buf, 0, sizeof(buf)); + ret = firehose_read_buf(qdl, &op, buf, sizeof(buf)); + if (ret) { + ux_err("failed to read GPT partition entries from %d:%u\n", phys_partition, lba); + return -1; + } + } + + entry = (struct gpt_entry *)(buf + offset); + + if (!memcmp(&entry->type_guid, &gpt_zero_guid, sizeof(struct gpt_guid))) + continue; + + memcpy(name_utf16le, entry->name_utf16le, sizeof(name_utf16le)); + utf16le_to_utf8(name_utf16le, 36, (uint8_t *)name, sizeof(name)); + + partition = calloc(1, sizeof(*partition)); + partition->name = strdup(name); + partition->partition = phys_partition; + partition->start_sector = entry->first_lba; + partition->num_sectors = entry->last_lba - entry->first_lba; + + ux_debug(" %3d: %s sector %u to %u\n", i, partition->name, + partition->start_sector, partition->start_sector + partition->num_sectors); + + if (gpt_partitions) { + gpt_partitions_last->next = partition; + gpt_partitions_last = partition; + } else { + gpt_partitions = partition; + gpt_partitions_last = partition; + } + } + + return 0; +} + +static int gpt_load_tables(struct qdl_device *qdl) +{ + unsigned int i; + bool eof = false; + int ret = 0; + + if (gpt_partitions) + return 0; + + for (i = 0; ; i++) { + ret = gpt_load_table_from_partition(qdl, i, &eof); + if (ret) + break; + } + + return eof ? 0 : ret; +} + +int gpt_find_by_name(struct qdl_device *qdl, const char *name, int *phys_partition, + unsigned int *start_sector, unsigned int *num_sectors) +{ + struct gpt_partition *gpt_part; + bool found = false; + int ret; + + if (qdl->dev_type == QDL_DEVICE_SIM) + return 0; + + ret = gpt_load_tables(qdl); + if (ret < 0) + return -1; + + for (gpt_part = gpt_partitions; gpt_part; gpt_part = gpt_part->next) { + if (*phys_partition >= 0 && gpt_part->partition != *phys_partition) + continue; + + if (strcmp(gpt_part->name, name)) + continue; + + if (found) { + ux_err("duplicate candidates for partition \"%s\" found\n", name); + return -1; + } + + *phys_partition = gpt_part->partition; + *start_sector = gpt_part->start_sector; + *num_sectors = gpt_part->num_sectors; + + found = true; + } + + if (!found) { + if (*phys_partition >= 0) + ux_err("no partition \"%s\" found on physical partition %d\n", name, *phys_partition); + else + ux_err("no partition \"%s\" found\n", name); + return -1; + } + + return 0; +} diff --git a/gpt.h b/gpt.h new file mode 100644 index 0000000..33e36be --- /dev/null +++ b/gpt.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef __GPT_H__ +#define __GPT_H__ + +struct qdl_device; + +int gpt_find_by_name(struct qdl_device *qdl, const char *name, int *partition, + unsigned int *start_sector, unsigned int *num_sectors); + +#endif diff --git a/program.c b/program.c index 41237b2..7ec2df1 100644 --- a/program.c +++ b/program.c @@ -18,6 +18,7 @@ #include "qdl.h" #include "oscompat.h" #include "sparse.h" +#include "gpt.h" static struct program *programes; static struct program *programes_last; @@ -417,6 +418,7 @@ void free_programs(void) free((void *)program->filename); free((void *)program->label); free((void *)program->start_sector); + free((void *)program->gpt_partition); free(program); } @@ -428,11 +430,12 @@ int program_cmd_add(const char *address, const char *filename) unsigned int start_sector; unsigned int num_sectors; struct program *program; + char *gpt_partition; int partition; char buf[20]; int ret; - ret = parse_storage_address(address, &partition, &start_sector, &num_sectors); + ret = parse_storage_address(address, &partition, &start_sector, &num_sectors, &gpt_partition); if (ret < 0) return ret; @@ -450,6 +453,7 @@ int program_cmd_add(const char *address, const char *filename) program->last_sector = 0; program->is_nand = false; program->is_erase = false; + program->gpt_partition = gpt_partition; if (programes) { programes_last->next = program; @@ -461,3 +465,26 @@ int program_cmd_add(const char *address, const char *filename) return 0; } + +int program_resolve_gpt_deferrals(struct qdl_device *qdl) +{ + struct program *program; + unsigned int start_sector; + char buf[20]; + int ret; + + for (program = programes; program; program = program->next) { + if (!program->gpt_partition) + continue; + + ret = gpt_find_by_name(qdl, program->gpt_partition, &program->partition, + &start_sector, &program->num_sectors); + if (ret < 0) + return -1; + + sprintf(buf, "%u", start_sector); + program->start_sector = strdup(buf); + } + + return 0; +} diff --git a/program.h b/program.h index b3e53c3..b72b7f2 100644 --- a/program.h +++ b/program.h @@ -14,7 +14,7 @@ struct program { const char *filename; const char *label; unsigned int num_sectors; - unsigned int partition; + int partition; bool sparse; const char *start_sector; unsigned int last_sector; @@ -26,6 +26,8 @@ struct program { uint32_t sparse_fill_value; off_t sparse_offset; + const char *gpt_partition; + struct program *next; }; @@ -36,6 +38,7 @@ int erase_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, s int program_find_bootable_partition(bool *multiple_found); int program_is_sec_partition_flashed(void); int program_cmd_add(const char *address, const char *filename); +int program_resolve_gpt_deferrals(struct qdl_device *qdl); void free_programs(void); diff --git a/qdl.h b/qdl.h index e8ab75e..0282238 100644 --- a/qdl.h +++ b/qdl.h @@ -69,6 +69,7 @@ struct qdl_device *usb_init(void); struct qdl_device *sim_init(void); int firehose_run(struct qdl_device *qdl, const char *incdir, const char *storage, bool allow_missing); +int firehose_read_buf(struct qdl_device *qdl, struct read_op *read_op, void *out_buf, size_t out_size); int sahara_run(struct qdl_device *qdl, char *img_arr[], bool single_image, const char *ramdump_path, const char *ramdump_filter); void print_hex_dump(const char *prefix, const void *buf, size_t len); @@ -85,8 +86,9 @@ void ux_progress(const char *fmt, unsigned int value, unsigned int size, ...); void print_version(void); -int parse_storage_address(const char *address, int *_partition, - unsigned int *_sector, unsigned int *_length); +int parse_storage_address(const char *address, int *physical_partition, + unsigned int *start_sector, unsigned int *num_sectors, + char **gpt_partition); extern bool qdl_debug; diff --git a/read.c b/read.c index fd04d7d..44b150a 100644 --- a/read.c +++ b/read.c @@ -14,6 +14,7 @@ #include "read.h" #include "qdl.h" #include "oscompat.h" +#include "gpt.h" static struct read_op *read_ops; static struct read_op *read_ops_last; @@ -112,15 +113,16 @@ int read_cmd_add(const char *address, const char *filename) unsigned int start_sector; unsigned int num_sectors; struct read_op *read_op; + char *gpt_partition; int partition; char buf[20]; int ret; - ret = parse_storage_address(address, &partition, &start_sector, &num_sectors); + ret = parse_storage_address(address, &partition, &start_sector, &num_sectors, &gpt_partition); if (ret < 0) return ret; - if (num_sectors == 0) { + if (num_sectors == 0 && !gpt_partition) { ux_err("read command without length specifier not supported\n"); return -1; } @@ -133,6 +135,7 @@ int read_cmd_add(const char *address, const char *filename) read_op->num_sectors = num_sectors; sprintf(buf, "%u", start_sector); read_op->start_sector = strdup(buf); + read_op->gpt_partition = gpt_partition; if (read_ops) { read_ops_last->next = read_op; @@ -144,3 +147,26 @@ int read_cmd_add(const char *address, const char *filename) return 0; } + +int read_resolve_gpt_deferrals(struct qdl_device *qdl) +{ + struct read_op *read_op; + unsigned int start_sector; + char buf[20]; + int ret; + + for (read_op = read_ops; read_op; read_op = read_op->next) { + if (!read_op->gpt_partition) + continue; + + ret = gpt_find_by_name(qdl, read_op->gpt_partition, &read_op->partition, + &start_sector, &read_op->num_sectors); + if (ret < 0) + return -1; + + sprintf(buf, "%u", start_sector); + read_op->start_sector = strdup(buf); + } + + return 0; +} diff --git a/read.h b/read.h index cd56e14..1ef9e5a 100644 --- a/read.h +++ b/read.h @@ -9,9 +9,10 @@ struct qdl_device; struct read_op { unsigned int sector_size; const char *filename; - unsigned int partition; + int partition; unsigned int num_sectors; const char *start_sector; + const char *gpt_partition; struct read_op *next; }; @@ -20,5 +21,6 @@ int read_op_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, struct read_op *read_op, int fd), const char *incdir); int read_cmd_add(const char *source, const char *filename); +int read_resolve_gpt_deferrals(struct qdl_device *qdl); #endif diff --git a/util.c b/util.c index 8dfb9b4..4b25be7 100644 --- a/util.c +++ b/util.c @@ -123,6 +123,7 @@ bool attr_as_bool(xmlNode *node, const char *attr, int *errors) * @physical_partition: physical partition * @start_sector: start_sector * @num_sectors: number of sectors + * @gpt_partition: GPT name * * This function parses the provided address specifier and detects the * following patterns: @@ -130,22 +131,33 @@ bool attr_as_bool(xmlNode *node, const char *attr, int *errors) * N => physical partition N, sector 0 * N/S => physical partition N, sector S * N/S+L => physical partition N, L sectors at sector S + * name => GPT partition name match across all physical partitions + * N/name => GPT partition name match within physical partition N + * + * @physical_partition is either the requested physical partition, or -1 if + * none is specified. Either @start_sector and @num_sectors, or @gpt_partition + * will represent the equested address, the other(s) will be zeroed. * * Returns: 0 on success, -1 on failure */ int parse_storage_address(const char *address, int *physical_partition, - unsigned int *start_sector, unsigned int *num_sectors) + unsigned int *start_sector, unsigned int *num_sectors, + char **gpt_partition) { unsigned long length = 0; const char *ptr = address; unsigned long sector = 0; long partition; char *end; + char *gpt = NULL; errno = 0; partition = strtol(ptr, &end, 10); - if (end == ptr) - return -1; + if (end == ptr) { + partition = -1; + gpt = strdup(ptr); + goto done; + } if ((errno == ERANGE && partition == LONG_MAX) || partition < 0) return -1; @@ -158,8 +170,10 @@ int parse_storage_address(const char *address, int *physical_partition, errno = 0; sector = strtoul(ptr, &end, 10); - if (end == ptr) - return -1; + if (end == ptr) { + gpt = strdup(ptr); + goto done; + } if (errno == ERANGE && sector == ULONG_MAX) return -1; @@ -186,6 +200,7 @@ done: *physical_partition = partition; *start_sector = sector; *num_sectors = length; + *gpt_partition = gpt; return 0; }