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 <bjorn.andersson@oss.qualcomm.com>
This commit is contained in:
Bjorn Andersson
2025-09-07 21:18:14 -05:00
committed by Bjorn Andersson
parent dff2ed7a72
commit 60db50966d
10 changed files with 388 additions and 16 deletions

View File

@@ -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

View File

@@ -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;

281
gpt.c Normal file
View File

@@ -0,0 +1,281 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <stdlib.h>
#include <string.h>
#define _FILE_OFFSET_BITS 64
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <assert.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#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;
}

10
gpt.h Normal file
View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

6
qdl.h
View File

@@ -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;

30
read.c
View File

@@ -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;
}

4
read.h
View File

@@ -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

25
util.c
View File

@@ -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;
}