mirror of
https://github.com/linux-msm/qdl.git
synced 2026-10-07 06:29:17 -07:00
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>
242 lines
4.6 KiB
C
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);
|
|
}
|
|
}
|
|
}
|