Files
qdl/file.c
T
Igor Opaniuk 702abb441f qdl: add --skipblock=sha256 to skip programs already on flash
Automated flashing pipelines often re-program identical content on every
run, which dominates total flash time on slow storage like spinor. With
the <getsha256digest> primitive now exposed, qdl can ask the device for
the SHA-256 of the target region before each <program> and skip the
write when it already matches what would have been sent.

Add --skipblock=<none|sha256>, defaulting to none. When sha256 is
selected, firehose_program() hashes the bytes it would otherwise stream
to the device - reading num_partition_sectors * SECTOR_SIZE_IN_BYTES
from the file with zero-padding for the trailing partial sector, exactly
matching what the existing chunked write path puts on the wire - then
asks the device for the digest of the same region via
firehose_getsha256digest(), reusing the op->digest field that the
sha256 subcommand path also reads. On a match, the <program> is skipped
entirely and "skipped \"<label>\" (sha256 match)" is logged in place of
the usual flashed-at-rate line.

The optimization is gated on three conditions, all enforced silently:

  - !program->sparse: each sparse chunk would need its own digest;
    keeping sparse images on the normal program path avoids per-chunk
    round trips and the bookkeeping that would come with them.
  - !program->is_nand: NAND spare/OOB byte semantics differ across
    programmers, so the digest the device returns is not guaranteed to
    correspond to the bytes qdl would compute locally.
  - VIP disabled: <getsha256digest> is not part of pre-built VIP
    digest tables, so issuing it under VIP would cause a hash mismatch
    on the device.

Any failure on the optimization path - file read error, query failure,
digest absent, or digest mismatch - falls through to the normal program
flow. The file position is restored unconditionally by the existing
qdl_file_seek() at the top of the write loop, so the fall-through path
is byte-identical to --skipblock=none.

[1] https://github.com/linux-msm/qdl/issues/237

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
2026-05-16 16:35:15 -05:00

242 lines
4.6 KiB
C

// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <zip.h>
#include "oscompat.h"
#include "qdl.h"
#include "file.h"
struct qdl_zip {
zip_t *zip;
unsigned int refcount;
};
int qdl_file_open(struct qdl_zip *qdl_zip, const char *filename, struct qdl_file *file)
{
struct zip_stat st;
zip_int64_t idx;
zip_file_t *zf;
off_t len;
zip_t *zip;
int fd;
if (qdl_zip) {
zip = qdl_zip->zip;
idx = zip_name_locate(zip, filename, 0);
if (idx < 0) {
ux_err("unable to locate \"%s\" in zip archive\n", filename);
return -1;
}
if (zip_stat_index(zip, idx, 0, &st) < 0) {
ux_err("unable to stat \"%s\" in zip archive\n", filename);
return -1;
}
zf = zip_fopen_index(zip, idx, 0);
if (!zf) {
ux_err("unable to open \"%s\" in zip archive\n", filename);
return -1;
}
file->type = QDL_FILE_TYPE_ZIP;
file->fd = -1;
file->size = st.size;
file->zip_file = zf;
} else {
fd = open(filename, O_RDONLY | O_BINARY);
if (fd < 0) {
ux_err("failed to open \"%s\" for reading\n", filename);
return -1;
}
len = lseek(fd, 0, SEEK_END);
if (len < 0) {
ux_err("failed to find end of \"%s\"\n", filename);
close(fd);
return -1;
}
lseek(fd, 0, SEEK_SET);
file->type = QDL_FILE_TYPE_POSIX;
file->fd = fd;
file->size = len;
file->zip_file = NULL;
}
return 0;
}
void *qdl_file_load(struct qdl_file *file, size_t *len)
{
ssize_t n;
void *buf;
buf = malloc(file->size);
if (!buf) {
ux_err("unable to allocate memory to load file\n");
return NULL;
}
switch (file->type) {
case QDL_FILE_TYPE_UNKNOWN:
ux_err("internal error: attempting to load unknown file type\n");
return NULL;
case QDL_FILE_TYPE_POSIX:
n = read(file->fd, buf, file->size);
if ((size_t)n != file->size) {
ux_err("failed to load content of file\n");
goto err_free_buf;
}
break;
case QDL_FILE_TYPE_ZIP:
n = zip_fread(file->zip_file, buf, file->size);
if ((size_t)n != file->size) {
ux_err("failed to load zip file member\n");
goto err_free_buf;
}
break;
};
*len = file->size;
return buf;
err_free_buf:
free(buf);
return NULL;
}
void qdl_file_close(struct qdl_file *file)
{
switch (file->type) {
case QDL_FILE_TYPE_UNKNOWN:
break;
case QDL_FILE_TYPE_POSIX:
close(file->fd);
file->fd = -1;
break;
case QDL_FILE_TYPE_ZIP:
zip_fclose(file->zip_file);
file->zip_file = NULL;
break;
};
file->type = QDL_FILE_TYPE_UNKNOWN;
}
size_t qdl_file_getsize(struct qdl_file *file)
{
return file->size;
}
ssize_t qdl_file_read(struct qdl_file *file, void *buf, size_t len)
{
switch (file->type) {
case QDL_FILE_TYPE_UNKNOWN:
break;
case QDL_FILE_TYPE_POSIX:
return read(file->fd, buf, len);
case QDL_FILE_TYPE_ZIP:
return zip_fread(file->zip_file, buf, len);
};
return -1;
}
/*
* Loop around qdl_file_read() until @len bytes have been read into
* @buf. Returns the number of bytes read, which equals @len for full
* reads and is less than @len only on EOF; negative on a read error.
*
* Wraps the underlying read() / zip_fread() so callers that don't
* tolerate short reads don't have to roll their own loop. The bytes
* past the returned length are left untouched - callers that need a
* fixed-size buffer must zero them themselves.
*/
ssize_t qdl_file_read_exact(struct qdl_file *file, void *buf, size_t len)
{
uint8_t *p = buf;
size_t got = 0;
ssize_t n;
while (got < len) {
n = qdl_file_read(file, p + got, len - got);
if (n < 0)
return -1;
if (n == 0)
break;
got += (size_t)n;
}
return (ssize_t)got;
}
off_t qdl_file_seek(struct qdl_file *file, off_t offset, int whence)
{
switch (file->type) {
case QDL_FILE_TYPE_UNKNOWN:
return -1;
case QDL_FILE_TYPE_POSIX:
return lseek(file->fd, offset, whence);
case QDL_FILE_TYPE_ZIP:
if (offset == 0 && whence == SEEK_SET)
return 0;
ux_err("seek not implemented for zip files\n");
return -1;
};
return -1;
}
int qdl_zip_open(const char *filename, struct qdl_zip **__qdl_zip)
{
struct qdl_zip *qdl_zip;
zip_t *zip;
zip = zip_open(filename, ZIP_RDONLY, NULL);
if (!zip) {
*__qdl_zip = NULL;
return 0;
}
qdl_zip = calloc(1, sizeof(*qdl_zip));
if (!qdl_zip) {
zip_close(zip);
return -1;
}
qdl_zip->zip = zip;
qdl_zip->refcount = 1;
*__qdl_zip = qdl_zip;
return 0;
}
struct qdl_zip *qdl_zip_get(struct qdl_zip *qdl_zip)
{
if (qdl_zip)
qdl_zip->refcount++;
return qdl_zip;
}
void qdl_zip_put(struct qdl_zip *qdl_zip)
{
if (qdl_zip) {
if (--qdl_zip->refcount == 0) {
zip_close(qdl_zip->zip);
free(qdl_zip);
}
}
}