firehose: support VIP extension

This extends the Firehose protocol implementation to support the VIP
extension. It implements a state machine that counts the number of
packets sent, then injects the VIP table as the next RAW packet when
the Firehose programmer runs out of provided digests and needs a new table
of digests to validate the next packets. For example:

Packet 0: DigestsTableToSign.bin.mbn (53 digest + 1 digest of next table)
Packet 1: <configure>
Packet 2: <program>
Packet 3: ...
...
Packet 54: ChainedTableOfDigests0.bin (255 digests + digest of next table)
Packet 55: <program>
...
Packet 309: ChainedTableOfDigests1.bin

To enable VIP extension provide a path where previously generated VIP
tables are stored using "--vip-table-path" param:

$ qdl --vip-table-path "<vip-table-path>" prog_firehose_ddr.elf \
  rawprogram*.xml patch*.xml

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
This commit is contained in:
Igor Opaniuk
2025-06-11 09:28:55 +02:00
committed by Konrad Dybcio
parent 6454ab46f2
commit 8818b98e85
7 changed files with 257 additions and 16 deletions

View File

@@ -159,6 +159,23 @@ static int firehose_write(struct qdl_device *qdl, xmlDoc *doc)
xmlDocDumpMemory(doc, &s, &len);
ret = vip_transfer_handle_tables(qdl);
if (ret) {
ux_err("VIP: error occurred during VIP table transmission\n");
return -1;
}
if (vip_transfer_status_check_needed(qdl)) {
ret = firehose_read(qdl, 30000, firehose_generic_parser, NULL);
if (ret) {
ux_err("VIP: sending of digest table failed\n");
return -1;
}
ux_info("VIP: digest table has been sent successfully\n");
vip_transfer_clear_status(qdl);
}
vip_gen_chunk_init(qdl);
for (;;) {
@@ -184,8 +201,6 @@ static int firehose_write(struct qdl_device *qdl, xmlDoc *doc)
return ret < 0 ? -saved_errno : 0;
}
static size_t max_payload_size = 1048576;
/**
* firehose_configure_response_parser() - parse a configure response
* @node: response xmlNode
@@ -270,7 +285,8 @@ static int firehose_configure(struct qdl_device *qdl, bool skip_storage_init,
size_t size = 0;
int ret;
ret = firehose_send_configure(qdl, max_payload_size, skip_storage_init, storage, &size);
ret = firehose_send_configure(qdl, qdl->max_payload_size, skip_storage_init,
storage, &size);
if (ret < 0) {
ux_err("configure request failed\n");
return -1;
@@ -284,24 +300,21 @@ static int firehose_configure(struct qdl_device *qdl, bool skip_storage_init,
return 0;
/* Retry if remote proposed different size */
if (size != max_payload_size) {
if (size != qdl->max_payload_size) {
ret = firehose_send_configure(qdl, size, skip_storage_init, storage, &size);
if (ret != FIREHOSE_ACK) {
ux_err("configure request with updated payload size failed\n");
return -1;
}
max_payload_size = size;
qdl->max_payload_size = size;
}
ux_debug("accepted max payload size: %zu\n", max_payload_size);
ux_debug("accepted max payload size: %zu\n", qdl->max_payload_size);
return 0;
}
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define ROUND_UP(x, a) (((x) + (a) - 1) & ~((a) - 1))
static int firehose_erase(struct qdl_device *qdl, struct program *program)
{
xmlNode *root;
@@ -370,7 +383,7 @@ static int firehose_program(struct qdl_device *qdl, struct program *program, int
num_sectors = program->num_sectors;
}
buf = malloc(max_payload_size);
buf = malloc(qdl->max_payload_size);
if (!buf)
err(1, "failed to allocate sector buffer");
@@ -417,7 +430,7 @@ static int firehose_program(struct qdl_device *qdl, struct program *program, int
* not for the whole binary.
*/
vip_gen_chunk_init(qdl);
chunk_size = MIN(max_payload_size / program->sector_size, left);
chunk_size = MIN(qdl->max_payload_size / program->sector_size, left);
n = read(fd, buf, chunk_size * program->sector_size);
if (n < 0) {
@@ -425,10 +438,27 @@ static int firehose_program(struct qdl_device *qdl, struct program *program, int
goto out;
}
if (n < max_payload_size)
memset(buf + n, 0, max_payload_size - n);
if (n < qdl->max_payload_size)
memset(buf + n, 0, qdl->max_payload_size - n);
vip_gen_chunk_update(qdl, buf, chunk_size * program->sector_size);
ret = vip_transfer_handle_tables(qdl);
if (ret) {
ux_err("VIP: error occurred during VIP table transmission\n");
return -1;
}
if (vip_transfer_status_check_needed(qdl)) {
ret = firehose_read(qdl, 30000, firehose_generic_parser, NULL);
if (ret) {
ux_err("VIP: sending of digest table failed\n");
return -1;
}
ux_info("VIP: digest table has been sent successfully\n");
vip_transfer_clear_status(qdl);
}
n = qdl_write(qdl, buf, chunk_size * program->sector_size);
if (n < 0) {
ux_err("USB write failed for data chunk\n");
@@ -486,7 +516,7 @@ static int firehose_read_op(struct qdl_device *qdl, struct read_op *read_op, int
int n;
bool expect_empty;
buf = malloc(max_payload_size);
buf = malloc(qdl->max_payload_size);
if (!buf)
err(1, "failed to allocate sector buffer");
@@ -519,7 +549,7 @@ static int firehose_read_op(struct qdl_device *qdl, struct read_op *read_op, int
left = read_op->num_sectors;
expect_empty = false;
while (left > 0 || expect_empty) {
chunk_size = MIN(max_payload_size / read_op->sector_size, left);
chunk_size = MIN(qdl->max_payload_size / read_op->sector_size, left);
n = qdl_read(qdl, buf, chunk_size * read_op->sector_size, 30000);
if (n < 0) {

18
qdl.c
View File

@@ -84,7 +84,7 @@ static void print_usage(void)
extern const char *__progname;
fprintf(stderr,
"%s [--debug] [--dry-run] [--version] [--allow-missing] [--storage <emmc|nand|ufs>] [--finalize-provisioning] [--include <PATH>] [--serial <NUM>] [--out-chunk-size <SIZE>] [--create-digests <PATH>] <prog.mbn> [<program> <patch> ...]\n",
"%s [--debug] [--dry-run] [--version] [--allow-missing] [--storage <emmc|nand|ufs>] [--finalize-provisioning] [--include <PATH>] [--serial <NUM>] [--out-chunk-size <SIZE>] [--create-digests <PATH>] [--vip_table_path <PATH>] <prog.mbn> [<program> <patch> ...]\n",
__progname);
}
@@ -98,6 +98,7 @@ int main(int argc, char **argv)
char *incdir = NULL;
char *serial = NULL;
const char *vip_generate_dir = NULL;
const char *vip_table_path = NULL;
int type;
int ret;
int opt;
@@ -115,6 +116,7 @@ int main(int argc, char **argv)
{"finalize-provisioning", no_argument, 0, 'l'},
{"out-chunk-size", required_argument, 0, OPT_OUT_CHUNK_SIZE },
{"serial", required_argument, 0, 'S'},
{"vip-table-path", required_argument, 0, 'D'},
{"storage", required_argument, 0, 's'},
{"allow-missing", no_argument, 0, 'f'},
{"allow-fusing", no_argument, 0, 'c'},
@@ -160,6 +162,9 @@ int main(int argc, char **argv)
case 'S':
serial = optarg;
break;
case 'D':
vip_table_path = optarg;
break;
default:
print_usage();
return 1;
@@ -178,6 +183,14 @@ int main(int argc, char **argv)
goto out_cleanup;
}
if (vip_table_path) {
if (vip_generate_dir)
errx(1, "VIP mode and VIP table generation can't be enabled together\n");
ret = vip_transfer_init(qdl, vip_table_path);
if (ret)
errx(1, "VIP initialization failed\n");
}
if (out_chunk_size)
qdl_set_out_chunk_size(qdl, out_chunk_size);
@@ -251,6 +264,9 @@ out_cleanup:
free_programs();
free_patches();
if (qdl->vip_data.state != VIP_DISABLED)
vip_transfer_deinit(qdl);
qdl_deinit(qdl);
return !!ret;

10
qdl.h
View File

@@ -7,6 +7,7 @@
#include "program.h"
#include "read.h"
#include <libxml/tree.h>
#include "vip.h"
#define container_of(ptr, typecast, member) ({ \
void *_ptr = (void *)(ptr); \
@@ -14,6 +15,9 @@
#define MAPPING_SZ 64
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define ROUND_UP(x, a) (((x) + (a) - 1) & ~((a) - 1))
enum QDL_DEVICE_TYPE {
QDL_DEVICE_USB,
QDL_DEVICE_SIM,
@@ -22,14 +26,19 @@ enum QDL_DEVICE_TYPE {
struct qdl_device {
enum QDL_DEVICE_TYPE dev_type;
int fd;
size_t max_payload_size;
int (*open)(struct qdl_device *qdl, const char *serial);
int (*read)(struct qdl_device *qdl, void *buf, size_t len, unsigned int timeout);
int (*write)(struct qdl_device *qdl, const void *buf, size_t nbytes);
void (*close)(struct qdl_device *qdl);
void (*set_out_chunk_size)(struct qdl_device *qdl, long size);
void (*set_vip_transfer)(struct qdl_device *qdl, const char *signed_table,
const char *chained_table);
char *mappings[MAPPING_SZ]; // array index is the id from the device
struct vip_transfer_data vip_data;
};
struct libusb_device_handle;
@@ -41,6 +50,7 @@ void qdl_close(struct qdl_device *qdl);
int qdl_read(struct qdl_device *qdl, void *buf, size_t len, unsigned int timeout);
int qdl_write(struct qdl_device *qdl, const void *buf, size_t len);
void qdl_set_out_chunk_size(struct qdl_device *qdl, long size);
int qdl_vip_transfer_enable(struct qdl_device *qdl, const char *vip_table_path);
struct qdl_device *usb_init(void);
struct qdl_device *sim_init(void);

1
sim.c
View File

@@ -55,6 +55,7 @@ struct qdl_device *sim_init(void)
qdl->write = sim_write;
qdl->close = sim_close;
qdl->set_out_chunk_size = sim_set_out_chunk_size;
qdl->max_payload_size = 1048576;
return qdl;
}

3
usb.c
View File

@@ -293,12 +293,15 @@ struct qdl_device *usb_init(void)
if (!qdl)
return NULL;
memset(qdl, 0, sizeof(struct qdl_device_usb));
qdl->dev_type = QDL_DEVICE_USB;
qdl->open = usb_open;
qdl->read = usb_read;
qdl->write = usb_write;
qdl->close = usb_close;
qdl->set_out_chunk_size = usb_set_out_chunk_size;
qdl->max_payload_size = 1048576;
return qdl;
}

153
vip.c
View File

@@ -2,10 +2,13 @@
/*
* Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "sim.h"
@@ -14,6 +17,7 @@
#define CHAINED_TABLE_FILE_PREF "ChainedTableOfDigests"
#define CHAINED_TABLE_FILE_MAX_NAME 64
#define DIGEST_TABLE_TO_SIGN_FILE "DigestsToSign.bin"
#define DIGEST_TABLE_TO_SIGN_FILE_MBN (DIGEST_TABLE_TO_SIGN_FILE ".mbn")
#define MAX_DIGESTS_PER_SIGNED_FILE 54
#define MAX_DIGESTS_PER_SIGNED_TABLE (MAX_DIGESTS_PER_SIGNED_FILE - 1)
#define MAX_DIGESTS_PER_CHAINED_FILE 256
@@ -346,3 +350,152 @@ void vip_gen_finalize(struct qdl_device *qdl)
free(vip_gen);
sim_set_digest_generation(false, qdl, NULL);
}
int vip_transfer_init(struct qdl_device *qdl, const char *vip_table_path)
{
char fullpath[PATH_MAX];
snprintf(fullpath, sizeof(fullpath), "%s/%s",
vip_table_path, DIGEST_TABLE_TO_SIGN_FILE_MBN);
qdl->vip_data.signed_table_fd = open(fullpath, O_RDONLY);
if (!qdl->vip_data.signed_table_fd) {
ux_err("Can't open signed table %s\n", fullpath);
return -1;
}
qdl->vip_data.chained_num = 0;
for (int i = 0; i < MAX_CHAINED_FILES; ++i) {
snprintf(fullpath, sizeof(fullpath), "%s/%s%d%s",
vip_table_path, CHAINED_TABLE_FILE_PREF, i, ".bin");
int fd = open(fullpath, O_RDONLY);
if (fd == -1) {
if (errno == ENOENT)
break;
ux_err("Can't open signed table %s\n", fullpath);
goto out_cleanup;
}
qdl->vip_data.chained_fds[qdl->vip_data.chained_num++] = fd;
}
qdl->vip_data.state = VIP_INIT;
qdl->vip_data.chained_cur = 0;
return 0;
out_cleanup:
close(qdl->vip_data.signed_table_fd);
for (int i = 0; i < qdl->vip_data.chained_num - 1; ++i)
close(qdl->vip_data.chained_fds[i]);
return -1;
}
void vip_transfer_deinit(struct qdl_device *qdl)
{
close(qdl->vip_data.signed_table_fd);
for (int i = 0; i < qdl->vip_data.chained_num - 1; ++i)
close(qdl->vip_data.chained_fds[i]);
}
static int vip_transfer_send_raw(struct qdl_device *qdl, int table_fd)
{
struct stat sb;
int ret;
void *buf;
size_t n;
ret = fstat(table_fd, &sb);
if (ret < 0) {
ux_err("Failed to stat digest table file\n");
return -1;
}
buf = malloc(sb.st_size);
if (!buf) {
ux_err("Failed to allocate transfer buffer\n");
return -1;
}
n = read(table_fd, buf, sb.st_size);
if (n < 0) {
ux_err("failed to read binary\n");
ret = -1;
goto out;
}
n = qdl_write(qdl, buf, sb.st_size);
if (n < 0) {
ux_err("USB write failed for data chunk\n");
ret = -1;
goto out;
}
out:
free(buf);
return ret;
}
int vip_transfer_handle_tables(struct qdl_device *qdl)
{
struct vip_transfer_data *vip_data = &qdl->vip_data;
int ret = 0;
if (vip_data->state == VIP_DISABLED)
return 0;
if (vip_data->state == VIP_INIT) {
/* Send initial signed table */
ret = vip_transfer_send_raw(qdl, vip_data->signed_table_fd);
if (ret) {
ux_err("VIP: failed to send the Signed VIP table\n");
return ret;
}
ux_debug("VIP: successfully sent the Initial VIP table\n");
vip_data->state = VIP_SEND_DATA;
vip_data->frames_sent = 0;
vip_data->frames_left = MAX_DIGESTS_PER_SIGNED_TABLE;
vip_data->fh_parse_status = true;
}
if (vip_data->state == VIP_SEND_NEXT_TABLE) {
if (vip_data->chained_cur >= vip_data->chained_num) {
ux_err("VIP: the required quantity of chained tables is missing\n");
return -1;
}
ret = vip_transfer_send_raw(qdl, vip_data->chained_fds[vip_data->chained_cur]);
if (ret) {
ux_err("VIP: failed to send the chained VIP table\n");
return ret;
}
ux_debug("VIP: successfully sent " CHAINED_TABLE_FILE_PREF "%lu.bin\n",
vip_data->chained_cur);
vip_data->state = VIP_SEND_DATA;
vip_data->frames_sent = 0;
vip_data->frames_left = MAX_DIGESTS_PER_CHAINED_TABLE;
vip_data->fh_parse_status = true;
vip_data->chained_cur++;
}
vip_data->frames_sent++;
if (vip_data->frames_sent >= vip_data->frames_left)
vip_data->state = VIP_SEND_NEXT_TABLE;
return 0;
}
bool vip_transfer_status_check_needed(struct qdl_device *qdl)
{
return qdl->vip_data.fh_parse_status;
}
void vip_transfer_clear_status(struct qdl_device *qdl)
{
qdl->vip_data.fh_parse_status = false;
}

28
vip.h
View File

@@ -9,6 +9,34 @@
struct vip_table_generator;
enum vip_state {
VIP_DISABLED,
VIP_INIT,
VIP_SEND_NEXT_TABLE,
VIP_SEND_DATA,
VIP_MAX,
};
#define MAX_CHAINED_FILES 32
struct vip_transfer_data {
enum vip_state state;
int signed_table_fd;
int chained_fds[MAX_CHAINED_FILES];
size_t chained_num;
size_t chained_cur;
size_t digests;
size_t frames_sent;
size_t frames_left;
size_t chained_table_size;
bool fh_parse_status;
};
int vip_transfer_init(struct qdl_device *qdl, const char *vip_table_path);
void vip_transfer_deinit(struct qdl_device *qdl);
int vip_transfer_handle_tables(struct qdl_device *qdl);
bool vip_transfer_status_check_needed(struct qdl_device *qdl);
void vip_transfer_clear_status(struct qdl_device *qdl);
int vip_gen_init(struct qdl_device *qdl, const char *path);
void vip_gen_chunk_init(struct qdl_device *qdl);
void vip_gen_chunk_update(struct qdl_device *qdl, const void *buf, size_t len);