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