GitHub has deprecated the macos-14 image: hosted runners only keep the
two newest macOS releases, and macOS 26 pushed 14 out of that window.
Brownouts started on July 6th 2026 and the label is removed on
November 2nd 2026, so the arm64 job must move to a supported image.
Build arm64 on both macos-15 and macos-26. Homebrew bottles and the
default deployment target follow the host release, so a binary built
on macOS 26 does not run on macOS 15. The macos-15 job keeps a release
artifact that works on both supported releases, alongside the Intel
job that already builds on macos-15-intel, while the macos-26 job
tests the newest toolchain.
Two arm64 jobs cannot share an artifact name, so include the macOS
release in it as the Linux jobs already do: qdl-binary-macos-<os>-<arch>.
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
Ubuntu 26.04 LTS is out since April 23, 2026 and GitHub provides hosted
runner images for it on both x64 and arm64. Add it to the Linux build
matrix next to the existing 24.04 and 22.04 entries so every push and pull
request is built and tested against the current LTS toolchain and library
set.
The new jobs reuse the same steps as the other Linux entries and
upload qdl-binary-ubuntu-26-{x64,arm64} artifacts.
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
Include <stdint.h> to provide the definition of uintptr_t and fix
the GCC build failure
Signed-off-by: Ali Erdinc Koroglu <ali.koroglu@oss.qualcomm.com>
Linux checkpatch.pl expects a Closes: trailer followed by a public URL,
while commitlint's default parser only treats GitHub-style "hash-number"
references as issue footers. Commits carrying a kernel-style Closes: tag
therefore trip commitlint's footer parsing.
Add Closes to the noteKeywords list, alongside the other kernel-style
trailers already recognized there, so commitlint treats it as the start
of the footer instead of misparsing the body.
Reported-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
Closes: https://github.com/linux-msm/qdl/issues/318
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
Users can flash an existing installer archive, but the README did not
describe how to create one from a build contents.xml file. This leaves
the create-zip workflow undiscoverable for users preparing packages.
Document create-zip usage, contents selectors, generated archive
contents, and the libzip requirement before the existing dry-run
section.
Closes: https://github.com/linux-msm/qdl/issues/316
Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
UFS provisioning drives the programmer with three <ufs> tags: a common
tag, one or more body tags, and an epilogue. The epilogue is sent twice
- first with commit=0 to validate the requested layout, then with
commit=1 to actually write the UFS configuration descriptor to the
device. All of these tags went through firehose_send_single_tag(),
which waited only 5s for the <response value="ACK"/>.
The commit=1 epilogue is the only expensive step: it makes the
programmer write the configuration descriptor to the device, which can
take considerably longer than 5s. When it does, the device emits its
"Calling handler for ufs" log but has not answered with an ACK yet, so
firehose_read() times out, firehose_send_single_tag() reports the tag
as failed, and provisioning is aborted even though the device is still
working. The program/erase write-back paths already use a 120s timeout
for exactly this reason; the UFS commit needs the same treatment.
Thread a timeout into firehose_send_single_tag() and use a short 5s
wait for the cheap common/body/validation tags while giving the commit
epilogue a 120s window. The commit=0 validation pass keeps the short
timeout.
Observed failure before the fix:
$ qdl --storage ufs prog_firehose_ddr.elf provision_ufs31.xml --debug
...
FIREHOSE WRITE: <?xml version="1.0"?>
<data><ufs LUNtoGrow="0" commit="1"/></data>
FIREHOSE READ: <?xml version="1.0" encoding="UTF-8" ?>
<data>
<log value="INFO: Calling handler for ufs" /></data>
LOG: INFO: Calling handler for ufs
ufs request failed
failed to apply ufs epilogue
UFS provisioning failed
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
Cover detect_type(): the command verbs (read/write/erase/flash/
sha256), an argument that is neither a verb nor an existing file, the
recognized XML roots (patches, data with program/read/ufs children,
contents), a well-formed document with an unrecognized root or child
resolving to QDL_FILE_UNKNOWN, and malformed XML being rejected.
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
How a positional argument is classified - verb, rawprogram, patch,
read, ufs or contents document - decides the whole shape of a flashing
run, yet the logic lived as private helpers of the CLI front-end where
no test could reach it, and misclassification bugs (an erase-only
rawprogram failing as "unknown file type") could only be found by
flashing. Give the classification its own module so it can be tested
directly. The contents.xml sniff used by the sahara-archive subcommand
is the same kind of decision and moves along with it.
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
Extend the util test suite with the two parsers just moved out of
qdl.c: backend-name decoding (auto/usb/qud, NULL defaulting to auto,
and an unknown name) and specifier splitting (plain filename,
file::selector, and the malformed empty / double-"::" / empty-part
forms).
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
Cover the pure helpers in util.c that had no test: the storage
address parser (partition, partition/sector, sector+length, and the
bare-name / partition-name GPT forms, plus the rejected forms), the
storage-type encode/decode round-trip, and the XML attribute
accessors - including that a present-but-empty attribute yields NULL
without raising an error, which several load paths rely on.
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
decode_backend() (a --backend value to enum) and qdl_split_specifier()
(splitting "<file>::<selector>") were static string parsers trapped in
qdl.c, which cannot be unit-tested because qdl.c defines main(). Move
them to util.c alongside the other argument parsers (parse_storage_
address, decode_storage_type) and declare them in qdl.h. Pure code
move, no behaviour change.
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
The archive decoder parses an untrusted blob with hand-rolled bounds
checks, and the audit found real holes there - unvalidated image ids
and entry names among them. Those hardened paths could previously only
be exercised by handing qdl a corrupt archive on the command line.
With the decoder extracted into its own module, craft newc archives in
memory and pin the contract: a valid archive maps its images, and a
truncated header, an out-of-range or zero image id and an unterminated
name are each rejected without touching the image table.
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
qdl.c has grown into the tool's junk drawer, and the Sahara programmer
plumbing is its largest self-contained tenant: the specifier-to-image
mapping, the CPIO archive decoder and, since the sahara-archive
subcommand arrived, the matching writer. None of it depends on the CLI
front-end it lives in, but burying it there couples it to main()'s
compilation unit, keeps the decoder and writer of the same format at
opposite ends of a 1500-line file, and puts the logic out of unit
tests' reach. Move the lot into programmer.c, where the format has one
home and the front-end shrinks to argument handling.
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
The loader's failure handling is exactly what the context refactor
changed, and nothing exercised it before - the module globals made
the loader untestable, so its error paths could only be reached with
a malformed provisioning XML in a real flashing session. Pin the new
contract while it is fresh: a malformed document must leave the
context empty rather than half-populated, cleanup must be safe to
repeat, and under ASAN the error paths must leak nothing.
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
The provisioning loader kept its parsed state in module globals, which
made it impossible to re-run or unit test, and its error paths left
the globals dangling - a caller that continued after a failed load
acted on freed or stale data, and several body strings were leaked
outright. A context owned by the caller removes the shared state, ties
the provisioning data's lifetime to the flash flow that uses it, and
gives every error path a single cleanup to go through.
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
firehose_program() and firehose_program_raw_region() carried identical
sector-streaming loops, serving the regular, sparse and skipblock
program paths. Duplicated wire logic is how the same defects got fixed
twice during the audit: every change to chunking, padding, VIP digest
hooks or write error handling had to be discovered and applied in both
places, and the copies had no mechanism keeping them in sync. Fold the
loop into one helper so the streaming behavior can only be changed -
and reviewed - in one place. No behavior change intended.
Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>