pax_global_header00006660000000000000000000000064151211370110014502gustar00rootroot0000000000000052 comment=6eeb866b1503a9219258edbdb72fcd478c20b2d7 qdl-2.4/000077500000000000000000000000001512113701100121275ustar00rootroot00000000000000qdl-2.4/.checkpatch.conf000066400000000000000000000020061512113701100151470ustar00rootroot00000000000000--no-tree --strict --max-line-length=120 --ignore FILE_PATH_CHANGES --ignore EMAIL_SUBJECT --ignore SPLIT_STRING # NEW_TYPEDEFS reports "do not add new typedefs" # typedef struct __attribute__((__packed__)) sparse_header { --ignore NEW_TYPEDEFS # PREFER_DEFINED_ATTRIBUTE_MACRO reports this kind of messages: # WARNING: Prefer __packed over __attribute__((__packed__)) --ignore PREFER_DEFINED_ATTRIBUTE_MACRO # PREFER_KERNEL_TYPES reports this kind of messages (when using --strict): # "Prefer kernel type 'u32' over 'uint32_t'" --ignore PREFER_KERNEL_TYPES # BRACES reports this kind of messages: # braces {} are not necessary for any arm of this statement --ignore BRACES # CAMELCASE reports this kind of messages: # Avoid CamelCase: --ignore CAMELCASE # AVOID_EXTERNS reports this kind of messages: # externs should be avoided in .c files # extern const char *__progname; --ignore AVOID_EXTERNS # COMMIT_LOG_LONG_LINE reports line lengths > 75 in commit log # Lets ignore this --ignore COMMIT_LOG_LONG_LINE qdl-2.4/.github/000077500000000000000000000000001512113701100134675ustar00rootroot00000000000000qdl-2.4/.github/workflows/000077500000000000000000000000001512113701100155245ustar00rootroot00000000000000qdl-2.4/.github/workflows/build.yml000066400000000000000000000110061512113701100173440ustar00rootroot00000000000000name: Buildtest on: pull_request: push: jobs: build-linux: strategy: fail-fast: false matrix: include: - { arch: x64, os: 24, runner: ubuntu-24.04 } - { arch: arm64, os: 24, runner: ubuntu-24.04-arm } - { arch: x64, os: 22, runner: ubuntu-22.04 } - { arch: arm64, os: 22, runner: ubuntu-22.04-arm } runs-on: ${{ matrix.runner }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Dependencies run: | sudo apt-get update sudo apt-get install -y libxml2-dev libusb-1.0-0-dev help2man - name: Build run: make - name: Run tests run: make tests - name: Generate man pages run: make manpages - name: Package run: | mkdir dist cp `pkg-config --variable=libdir libusb-1.0`/libusb-1.0.so.0 dist chmod 0644 dist/* cp qdl dist patchelf --set-rpath '$ORIGIN' dist/qdl - name: Upload artifact uses: actions/upload-artifact@v4 with: name: qdl-binary-ubuntu-${{matrix.os}}-${{ matrix.arch }} path: dist/* build-mac: strategy: fail-fast: false matrix: include: - { sys: macos-14, arch: arm64 } - { sys: macos-15-intel, arch: intel } runs-on: ${{ matrix.sys }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Dependencies run: | brew install libxml2 help2man - name: Build run: make - name: Generate man pages run: make manpages - name: Run tests run: make tests - name: Package run: | set -x mkdir dist cp `pkg-config --variable=libdir libusb-1.0`/libusb-1.0.0.dylib dist cp `pkg-config --variable=libdir liblzma`/liblzma.5.dylib dist chmod 0644 dist/* cp qdl dist if uname -a | grep -q arm64; then LIBUSB_DIR=/opt/homebrew/opt/libusb/lib LIBLZMA_DIR=/usr/lib else LIBUSB_DIR=/usr/local/opt/libusb/lib LIBLZMA_DIR=/usr/local/opt/xz/lib fi install_name_tool -add_rpath @executable_path dist/qdl install_name_tool -change $LIBUSB_DIR/libusb-1.0.0.dylib @rpath/libusb-1.0.0.dylib dist/qdl install_name_tool -change $LIBLZMA_DIR/liblzma.5.dylib @rpath/liblzma.5.dylib dist/qdl otool -L dist/qdl dist/qdl || true - name: Upload artifact uses: actions/upload-artifact@v4 with: name: qdl-binary-macos-${{ matrix.arch }} path: dist/* build-windows: strategy: fail-fast: false matrix: include: - { sys: windows-latest, arch: x64 } - { sys: windows-11-arm, arch: arm64 } runs-on: ${{ matrix.sys }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup MSYS2 id: msys2 uses: msys2/setup-msys2@v2 with: msystem: MINGW64 install: > base-devel git help2man mingw-w64-x86_64-gcc mingw-w64-x86_64-make mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libxml2 mingw-w64-x86_64-libusb mingw-w64-x86_64-xz - name: Build run: | git config --global core.autocrlf true make shell: msys2 {0} - name: Generate man pages run: make manpages shell: msys2 {0} - name: Run tests run: make tests shell: msys2 {0} - name: Package shell: pwsh run: | $MSYS2_LOCATION = "${{ steps.msys2.outputs.msys2-location }}" $BIN_DIR = Join-Path $MSYS2_LOCATION "mingw64\bin" $DistDir = "dist" New-Item -ItemType Directory -Path $DistDir | Out-Null Copy-Item (Join-Path $BIN_DIR "zlib1.dll") $DistDir Copy-Item (Join-Path $BIN_DIR "libxml2-16.dll") $DistDir Copy-Item (Join-Path $BIN_DIR "libusb-1.0.dll") $DistDir Copy-Item (Join-Path $BIN_DIR "liblzma-5.dll") $DistDir Copy-Item (Join-Path $BIN_DIR "libiconv-2.dll") $DistDir Copy-Item "qdl.exe" $DistDir - name: Upload artifact uses: actions/upload-artifact@v4 with: name: qdl-binary-windows-${{ matrix.arch }} path: dist/* qdl-2.4/.github/workflows/checkpatch.yml000066400000000000000000000007221512113701100203450ustar00rootroot00000000000000name: Checkpatch Review on: [pull_request] jobs: check-patch: name: checkpatch review runs-on: ubuntu-latest steps: - name: 'Calculate PR commits + 1' run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> $GITHUB_ENV - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - name: Run checkpatch review uses: webispy/checkpatch-action@v9 qdl-2.4/.github/workflows/codeql.yml000066400000000000000000000013551512113701100175220ustar00rootroot00000000000000name: CodeQL on: pull_request: push: jobs: codeql: permissions: # required for all workflows security-events: write # required to fetch internal or private CodeQL packs packages: read runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Dependencies run: | sudo apt-get update sudo apt-get install -y libxml2-dev libusb-1.0-0-dev - name: CodeQL init uses: github/codeql-action/init@v3 with: languages: c-cpp build-mode: autobuild - name: CodeQL build uses: github/codeql-action/autobuild@v3 - name: CodeQL analysis uses: github/codeql-action/analyze@v3 qdl-2.4/.github/workflows/markdown-lint.yml000066400000000000000000000003401512113701100210320ustar00rootroot00000000000000name: Markdown Lint on: [pull_request] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: DavidAnson/markdownlint-cli2-action@v20 with: globs: | README.md qdl-2.4/.gitignore000066400000000000000000000001661512113701100141220ustar00rootroot00000000000000*.1 *.o qdl qdl-ramdump ks *.exe compile_commands.json .cache .version.h version.h scripts .checkpatch-camelcase.git. qdl-2.4/.gitreview000066400000000000000000000001261512113701100141340ustar00rootroot00000000000000[gerrit] host=review.linaro.org port=29418 project=landing-teams/working/qualcomm/qdl qdl-2.4/LICENSE000066400000000000000000000031341512113701100131350ustar00rootroot00000000000000/* * Copyright (c) 2016-2017, Linaro Ltd. * Copyright (c) 2016, Bjorn Andersson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ qdl-2.4/Makefile000066400000000000000000000055301512113701100135720ustar00rootroot00000000000000QDL := qdl RAMDUMP := qdl-ramdump VERSION := $(or $(VERSION), $(shell git describe --dirty --always --tags 2>/dev/null), "unknown-version") CFLAGS += -O2 -Wall -g `pkg-config --cflags libxml-2.0 libusb-1.0` LDFLAGS += `pkg-config --libs libxml-2.0 libusb-1.0` ifeq ($(OS),Windows_NT) 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 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 RAMDUMP_OBJS := $(RAMDUMP_SRCS:.c=.o) KS_OUT := ks KS_SRCS := ks.c sahara.c util.c ux.c oscompat.c KS_OBJS := $(KS_SRCS:.c=.o) CHECKPATCH_SOURCES := $(shell find . -type f \( -name "*.c" -o -name "*.h" -o -name "*.sh" \) ! -name "sha2.c" ! -name "sha2.h" ! -name "*version.h" ! -name "list.h") CHECKPATCH_ROOT := https://raw.githubusercontent.com/torvalds/linux/v6.15/scripts CHECKPATCH_URL := $(CHECKPATCH_ROOT)/checkpatch.pl CHECKPATCH_SP_URL := $(CHECKPATCH_ROOT)/spelling.txt CHECKPATCH := ./.scripts/checkpatch.pl CHECKPATCH_SP := ./.scripts/spelling.txt MANPAGES := ks.1 qdl-ramdump.1 qdl.1 default: $(QDL) $(RAMDUMP) $(KS_OUT) $(QDL): $(QDL_OBJS) $(CC) -o $@ $^ $(LDFLAGS) $(RAMDUMP): $(RAMDUMP_OBJS) $(CC) -o $@ $^ $(LDFLAGS) $(KS_OUT): $(KS_OBJS) $(CC) -o $@ $^ $(LDFLAGS) compile_commands.json: $(QDL_SRCS) $(KS_SRCS) @echo -n $^ | jq -snR "[inputs|split(\" \")[]|{directory:\"$(PWD)\", command: \"$(CC) $(CFLAGS) -c \(.)\", file:.}]" > $@ manpages: $(KS_OUT) $(RAMDUMP) $(QDL) help2man -N -n "KS" -o ks.1 ./ks help2man -N -n "Qualcomm Download" -o qdl.1 ./qdl help2man -N -n "Qualcomm Download Ramdump" -o qdl-ramdump.1 ./qdl-ramdump version.h:: @echo "#define VERSION \"$(VERSION)\"" > .version.h @cmp -s .version.h version.h || cp .version.h version.h util.o: version.h clean: rm -f $(QDL) $(QDL_OBJS) rm -f $(RAMDUMP) $(RAMDUMP_OBJS) rm -f $(KS_OUT) $(KS_OBJS) rm -f $(MANPAGES) rm -f compile_commands.json rm -f version.h .version.h rm -f $(CHECKPATCH) rm -f $(CHECKPATCH_SP) if [ -d .scripts ]; then rmdir .scripts; fi install: $(QDL) $(RAMDUMP) $(KS_OUT) install -d $(DESTDIR)$(prefix)/bin install -m 755 $^ $(DESTDIR)$(prefix)/bin tests: default tests: @./tests/run_tests.sh # Target to download checkpatch.pl if not present $(CHECKPATCH): @echo "Downloading checkpatch.pl..." @mkdir -p $(dir $(CHECKPATCH)) @curl -sSfL $(CHECKPATCH_URL) -o $(CHECKPATCH) @curl -sSfL $(CHECKPATCH_SP_URL) -o $(CHECKPATCH_SP) @chmod +x $(CHECKPATCH) check: $(CHECKPATCH) @echo "Running checkpatch on source files (excluding sha2.c and sha2.h)..." @for file in $(CHECKPATCH_SOURCES); do \ perl $(CHECKPATCH) --no-tree -f $$file || exit 1; \ done check-cached: $(CHECKPATCH) @echo "Running checkpatch on staged changes..." @git diff --cached -- . | perl $(CHECKPATCH) --no-tree - qdl-2.4/README.md000066400000000000000000000210771512113701100134150ustar00rootroot00000000000000# Qualcomm Download [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![Build on push](https://github.com/linux-msm/qdl/actions/workflows/build.yml/badge.svg)](https://github.com/linux-msm/qdl/actions/workflows/build.yml/badge.svg) This tool communicates with USB devices of id `05c6:9008` to upload a flash loader and use this to flash images. ## Build ### Linux ```bash sudo apt install libxml2 libusb-1.0-0-dev help2man make ``` ### MacOS For Homebrew users, ```bash brew install libxml2 pkg-config libusb help2man make ``` For MacPorts users ```bash sudo port install libxml2 pkgconfig libusb help2man make ``` ### Windows First, install the [MSYS2 environment](https://www.msys2.org/). Then, run the MSYS2 MinGW64 terminal (located at `\mingw64.exe`) and install additional packages needed for QDL compilation using the `pacman` tool: ```bash pacman -S base-devel --needed pacman -S git pacman -S help2man pacman -S mingw-w64-x86_64-gcc pacman -S mingw-w64-x86_64-make pacman -S mingw-w64-x86_64-pkg-config pacman -S mingw-w64-x86_64-libusb pacman -S mingw-w64-x86_64-libxml2 ``` Then use the `make` tool to build QDL: ```bash make ``` ## Use QDL ### EDL mode The device intended for flashing must be booted into **Emergency Download (EDL)** mode. EDL is a special boot mode available on Qualcomm-based devices that provides low-level access for firmware flashing and recovery. It bypasses the standard boot process, allowing operations such as flashing firmware even on unresponsive devices or those with locked bootloaders. Please consult your device’s documentation for instructions on how to enter EDL mode. ### Flash device Run QDL with the `--help` option to view detailed usage information. Below is an example of how to invoke QDL to flash a FLAT build: ```bash qdl --dry-run prog_firehose_ddr.elf rawprogram*.xml patch*.xml ``` If you have multiple boards connected the host, provide the serial number of the board to flash through `--serial` param: ```bash qdl --serial=0AA94EFD prog_firehose_ddr.elf rawprogram*.xml patch*.xml ``` ### Reading and writing raw binaries In addition to flashing builds using their XML-based descriptions, QDL supports reading and writing binaries directly. ```bash qdl prog_firehose_ddr.elf [read | write] [address specifier] ... ``` Multiple read and write commands can be specified at once. The ***address specifier*** can take the forms: - N - single number, specifies the physical partition number N to write the ***binary** into, starting at sector 0 (currently reading a whole physical partition is not supported). - N/S - two numbers, specifies the physical partition number N, and the start sector S, to write the ***binary*** into (reading with an offset is not supported) - N/S+L - three numbers, specified the physical partition number N, the start sector S and the number of sectors L, that ***binary*** should be written to, or which should be read into ***binary***. - partition name - a string, will match against partition names across the GPT partition tables on all physical partitions. - N/partition_name - single number, followed by string - will match against partition names of the GPT partition table in the specified physical partition N. ### Validated Image Programming (VIP) QDL now supports **Validated Image Programming (VIP)** mode , which is activated when Secure Boot is enabled on the target. VIP controls which packets are allowed to be issued to the target. Controlling the packets that can be sent to the target is done through hashing. The target applies a hashing function to all received data, comparing the resulting hash digest against an existing digest table in memory. If the calculated hash digest matches the next entry in the table, the packet (data or command) is accepted; otherwise, the packet is rejected,and the target halts. To use VIP programming, a digest table must be generated prior to flashing the device. To generate table of digests run QDL with `--create-digests` param, providing a path to store VIP tables. For example: ```bash mkdir vip qdl --create-digests=./vip prog_firehose_ddr.elf rawprogram*.xml patch*.xml ``` As a result 3 types of files are generated: - `DIGEST_TABLE.bin` - contains the SHA256 table of digests for all firehose packets to be sent to the target. It is an intermediary table and is used only for the subsequent generation of `DigestsToSign.bin` and `ChainedTableOfDigests\.bin` files. It is not used by QDL for VIP programming. - `DigestsToSign.bin` - first 53 digests + digest of `ChainedTableOfDigests.bin`. This file has to be converted to MBN format and then signed with sectools: ```bash sectools mbn-tool generate --data DigestsToSign.bin --mbn-version 6 --outfile DigestsToSign.bin.mbn sectools secure-image --sign DigestsToSign.bin.mbn --image-id=VIP ``` Please check the security profile for your SoC to determine which version of the MBN format should be used. - `ChainedTableOfDigests\.bin` - contains left digests, split on multiple files with 255 digests + appended hash of next table. To flash board using VIP mode provide a path where previously generated and signed table of digests are stored using `--vip-table-path` param: ```bash qdl --vip-table-path=./vip prog_firehose_ddr.elf rawprogram*.xml patch*.xml ``` ### Multi-programmer targets On some targets multiple files need to be loaded in order to reach the Firehose programmer, these targets will request multiple images over Sahara. Three mechanisms for providing these images are provided: #### Command line argument The *programmer* argument, allows specifying a comma-separated list of colon-separated "id" and "filename" pairs. Each filename should refer to the Sahara image of the specified Sahara image id. ```bash qdl 13:prog_firehose_ddr.elf,42:the-answer rawprogram.xml ``` #### Sahara configuration XML file Flattened METAs does include the various images that need to be loaded to enter Firehose mode, as well as a sahara_config XML file, which defines the Sahara image id for each of these images. If the specified device programmer is determined to be a Sahara configuration XML file, it will be parsed and the referred to files will be loaded and serviced to the device upon request. ```bash qdl sahara_programmer.xml rawprogram.xml ``` #### Programmer archive Directly providing a list of ids and filenames is cumbersome and error prone, QDL therefore accepts a "*programmer archive*". This allows the user to use the tool in the same fashion as was done for single-programmer targets. The *programmer archive* is a CPIO archive containing the Sahara images to be loaded, identified by the filename **id[:filename]** (*filename* is optional, but useful for debugging). Each included file will be used to serve requests for the given Sahara *id*. Such an archive can be created by putting the target's programmer images in an empty directory, then in that directory execute the command: ```bash ls | cpio -o -H newc > ../programmer.cpio ``` *programmer.cpio* can now be passed to QDL and the included images will be served, in order to reach Firehose mode. ## Run tests To run the integration test suite for QDL, use the `make tests` target: ```bash make tests ``` ## Generate man pages Manpages can be generated using `make manpages` target: ```bash make manpages ``` ## Contributing Please submit any patches to the qdl (`master` branch) by using the GitHub pull request feature. Fork the repo, create a branch, do the work, rebase with upstream, and submit the pull request. The preferred coding style for this tool is [Linux kernel coding style](https://www.kernel.org/doc/html/v6.15/process/coding-style.html). Before creating a commit, please ensure that your changes adhere to the coding style by using the `make check-cached` target, for example: ```bash $ git status On branch improvements Changes to be committed: (use "git restore --staged ..." to unstage) modified: qdl.c modified: qdl.h $ make check-cached Running checkpatch on staged changes... ERROR: trailing whitespace #28: FILE: qdl.h:32: +^IQDL_DEVICE_USB, $ total: 1 errors, 0 warnings, 0 checks, 27 lines checked NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. NOTE: Whitespace errors detected. You may wish to use scripts/cleanpatch or scripts/cleanfile Your patch has style problems, please review. ``` ## License This tool is licensed under the BSD 3-Clause licensed. Check out [LICENSE](LICENSE) for more detais. qdl-2.4/firehose.c000066400000000000000000000646271512113701100141160ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2016-2017, Linaro Ltd. * Copyright (c) 2018, The Linux Foundation. All rights reserved. * All rights reserved. */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qdl.h" #include "ufs.h" #include "oscompat.h" #include "vip.h" #include "sparse.h" enum { FIREHOSE_ACK = 0, FIREHOSE_NAK, }; static void xml_setpropf(xmlNode *node, const char *attr, const char *fmt, ...) { xmlChar buf[128]; va_list ap; va_start(ap, fmt); vsnprintf((char *)buf, sizeof(buf), fmt, ap); xmlSetProp(node, (xmlChar *)attr, buf); va_end(ap); } static xmlNode *firehose_response_parse(const void *buf, size_t len, int *error) { xmlNode *node; xmlNode *root; xmlDoc *doc; doc = xmlReadMemory(buf, len, NULL, NULL, 0); if (!doc) { ux_err("failed to parse firehose response\n"); *error = -EINVAL; return NULL; } root = xmlDocGetRootElement(doc); for (node = root; node; node = node->next) { if (node->type != XML_ELEMENT_NODE) continue; if (xmlStrcmp(node->name, (xmlChar *)"data") == 0) break; } if (!node) { ux_err("firehose response without data tag\n"); *error = -EINVAL; xmlFreeDoc(doc); return NULL; } for (node = node->children; node && node->type != XML_ELEMENT_NODE; node = node->next) ; if (!node) { ux_err("empty firehose response\n"); *error = -EINVAL; } return node; } static int firehose_generic_parser(xmlNode *node, void *data __unused, bool *rawmode) { xmlChar *value; int ret = -EINVAL; value = xmlGetProp(node, (xmlChar *)"value"); if (!value) return -EINVAL; if (xmlStrcmp(node->name, (xmlChar *)"log") == 0) { ux_log("LOG: %s\n", value); ret = -EAGAIN; } else if (xmlStrcmp(value, (xmlChar *)"ACK") == 0) { ret = FIREHOSE_ACK; } else if (xmlStrcmp(value, (xmlChar *)"NAK") == 0) { ret = FIREHOSE_NAK; } xmlFree(value); value = xmlGetProp(node, (xmlChar *)"rawmode"); if (value) { if (xmlStrcmp(value, (xmlChar *)"true") == 0) *rawmode = true; xmlFree(value); } return ret; } static int firehose_read(struct qdl_device *qdl, int timeout_ms, int (*response_parser)(xmlNode *node, void *data, bool *rawmode), void *data) { char buf[4096]; xmlNode *node; int error; int resp = -EIO; int ret = -EAGAIN; int n; bool rawmode = false; struct timeval timeout; struct timeval now; struct timeval delta = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 }; gettimeofday(&now, NULL); timeradd(&now, &delta, &timeout); /* In simulation mode we don't expent to read and parse any responses */ if (qdl->dev_type == QDL_DEVICE_SIM) return 0; /* * The goal of firehose_read() is to find a response to a request among * one or more incoming messages AND to consume all incoming messages * (otherwise subsequent writes will time out). * The messages can be one of: * - * - * - * * Generally messages are coming prior to the , but * on MSM8916 (at least) it's been observed that messages can * arrive after the . * * We therefor need to consume messages until there are no more * (timeout) and we have been able to parse out a response (using * @response_parser). * * In the special case that the contain an attribute * "rawmode=true", the device signals that it has entered a mode where * it will not send/receive XML-formatted commands. So, (at least for * reads) we need to shortcircuit the logic and directly terminate the * consumption of incoming data. */ for (;;) { n = qdl_read(qdl, buf, sizeof(buf), 100); /* Timeout after seeing a response, we're done waiting for logs */ if (n == -ETIMEDOUT && resp >= 0) break; /* We want to return resp on error, to not loose the reset resposne */ else if (n == -EIO) break; if (n == -ETIMEDOUT || n == 0) { gettimeofday(&now, NULL); if (timercmp(&now, &timeout, <)) continue; return -ETIMEDOUT; } buf[n] = '\0'; ux_debug("FIREHOSE READ: %s\n", buf); node = firehose_response_parse(buf, n, &error); if (!node) return error; ret = response_parser(node, data, &rawmode); xmlFreeDoc(node->doc); if (ret >= 0) resp = ret; if (rawmode) break; } return resp; } static int firehose_write(struct qdl_device *qdl, xmlDoc *doc) { int saved_errno; xmlChar *s; int len; int ret; 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 (;;) { ux_debug("FIREHOSE WRITE: %s\n", s); vip_gen_chunk_update(qdl, s, len); ret = qdl_write(qdl, s, len, 1000); saved_errno = errno; /* * db410c sometimes sense a followed by * entries and won't accept write commands until these are * drained, so attempt to read any pending data and then retry * the write. */ if (ret < 0 && errno == ETIMEDOUT) { firehose_read(qdl, 100, firehose_generic_parser, NULL); } else { break; } } xmlFree(s); vip_gen_chunk_store(qdl); return ret < 0 ? -saved_errno : 0; } /** * firehose_configure_response_parser() - parse a configure response * @node: response xmlNode * * Return: max size supported by the remote, or negative errno on failure */ static int firehose_configure_response_parser(xmlNode *node, void *data, bool *rawmode __unused) { xmlChar *payload; xmlChar *value; size_t max_size; value = xmlGetProp(node, (xmlChar *)"value"); if (!value) return -EINVAL; if (xmlStrcmp(node->name, (xmlChar *)"log") == 0) { ux_log("LOG: %s\n", value); xmlFree(value); return -EAGAIN; } payload = xmlGetProp(node, (xmlChar *)"MaxPayloadSizeToTargetInBytes"); if (!payload) { xmlFree(value); return -EINVAL; } max_size = strtoul((char *)payload, NULL, 10); xmlFree(payload); /* * When receiving an ACK the remote may indicate that we should attempt * a larger payload size */ if (!xmlStrcmp(value, (xmlChar *)"ACK")) { payload = xmlGetProp(node, (xmlChar *)"MaxPayloadSizeToTargetInBytesSupported"); if (!payload) return -EINVAL; max_size = strtoul((char *)payload, NULL, 10); xmlFree(payload); } *(size_t *)data = max_size; xmlFree(value); return FIREHOSE_ACK; } static int firehose_send_configure(struct qdl_device *qdl, size_t payload_size, bool skip_storage_init, enum qdl_storage_type storage, size_t *max_payload_size) { static const char * const memory_names[] = { [QDL_STORAGE_EMMC] = "emmc", [QDL_STORAGE_NAND] = "nand", [QDL_STORAGE_UFS] = "ufs", [QDL_STORAGE_NVME] = "nvme", [QDL_STORAGE_SPINOR] = "spinor", }; xmlNode *root; xmlNode *node; xmlDoc *doc; doc = xmlNewDoc((xmlChar *)"1.0"); root = xmlNewNode(NULL, (xmlChar *)"data"); xmlDocSetRootElement(doc, root); node = xmlNewChild(root, NULL, (xmlChar *)"configure", NULL); xml_setpropf(node, "MemoryName", memory_names[storage]); xml_setpropf(node, "MaxPayloadSizeToTargetInBytes", "%lu", payload_size); xml_setpropf(node, "verbose", "%d", 0); xml_setpropf(node, "ZLPAwareHost", "%d", 1); xml_setpropf(node, "SkipStorageInit", "%d", skip_storage_init); firehose_write(qdl, doc); xmlFreeDoc(doc); return firehose_read(qdl, 100, firehose_configure_response_parser, max_payload_size); } static int firehose_try_configure(struct qdl_device *qdl, bool skip_storage_init, enum qdl_storage_type storage) { size_t max_sector_size; size_t sector_sizes[] = { 512, 4096 }; struct read_op op; size_t size = 0; void *buf; int ret; unsigned int i; ret = firehose_send_configure(qdl, qdl->max_payload_size, skip_storage_init, storage, &size); if (ret < 0) return ret; /* * In simulateion mode "remote" target can't propose different size, so * for QDL_DEVICE_SIM we just don't re-send configure packet */ if (qdl->dev_type == QDL_DEVICE_SIM) return 0; /* Retry if remote proposed different 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; } qdl->max_payload_size = size; } ux_debug("accepted max payload size: %zu\n", qdl->max_payload_size); if (storage != QDL_STORAGE_NAND) { max_sector_size = sector_sizes[ARRAY_SIZE(sector_sizes) - 1]; buf = malloc(max_sector_size); memset(&op, 0, sizeof(op)); op.partition = 0; op.start_sector = "1"; op.num_sectors = 1; /* * Testing has shown that the loader will fail gracefully if a * read is issued with the wrong sector size, use this to attempt * to discover the storage device's sector size. */ for (i = 0; i < ARRAY_SIZE(sector_sizes); i++) { op.sector_size = sector_sizes[i]; ret = firehose_read_buf(qdl, &op, buf, max_sector_size); if (ret == 0) { qdl->sector_size = sector_sizes[i]; break; } } } if (qdl->sector_size) ux_debug("detected sector size of: %zd\n", qdl->sector_size); return 0; } static int firehose_erase(struct qdl_device *qdl, struct program *program) { unsigned int sector_size; xmlNode *root; xmlNode *node; xmlDoc *doc; int ret; sector_size = program->sector_size ? : qdl->sector_size; doc = xmlNewDoc((xmlChar *)"1.0"); root = xmlNewNode(NULL, (xmlChar *)"data"); xmlDocSetRootElement(doc, root); node = xmlNewChild(root, NULL, (xmlChar *)"erase", NULL); xml_setpropf(node, "SECTOR_SIZE_IN_BYTES", "%d", sector_size); xml_setpropf(node, "num_partition_sectors", "%d", program->num_sectors); xml_setpropf(node, "physical_partition_number", "%d", program->partition); xml_setpropf(node, "start_sector", "%s", program->start_sector); if (qdl->slot != UINT_MAX) { xml_setpropf(node, "slot", "%u", qdl->slot); } if (program->is_nand) { xml_setpropf(node, "PAGES_PER_BLOCK", "%d", program->pages_per_block); } ret = firehose_write(qdl, doc); if (ret < 0) { ux_err("failed to send program request\n"); goto out; } ret = firehose_read(qdl, 30000, firehose_generic_parser, NULL); if (ret) ux_err("failed to erase %s+0x%x\n", program->start_sector, program->num_sectors); else ux_info("successfully erased %s+0x%x\n", program->start_sector, program->num_sectors); out: xmlFreeDoc(doc); return ret == FIREHOSE_ACK ? 0 : -1; } static int firehose_program(struct qdl_device *qdl, struct program *program, int fd) { unsigned int num_sectors; unsigned int sector_size; unsigned int zlp_timeout = 10000; struct stat sb; size_t chunk_size; xmlNode *root; xmlNode *node; xmlDoc *doc; void *buf; time_t t0; time_t t; size_t left; int ret; int n; size_t i; uint32_t fill_value; /* * ZLP has been measured to take up to 15 seconds on SPINOR devices, * let's double it to be on the safe side... */ if (qdl->storage_type == QDL_STORAGE_SPINOR) zlp_timeout = 60000; num_sectors = program->num_sectors; sector_size = program->sector_size ? : qdl->sector_size; ret = fstat(fd, &sb); if (ret < 0) err(1, "failed to stat \"%s\"\n", program->filename); if (!program->sparse) { num_sectors = (sb.st_size + sector_size - 1) / sector_size; if (program->num_sectors && num_sectors > program->num_sectors) { ux_err("%s to big for %s truncated to %d\n", program->filename, program->label, program->num_sectors * sector_size); num_sectors = program->num_sectors; } } buf = malloc(qdl->max_payload_size); if (!buf) err(1, "failed to allocate sector buffer"); doc = xmlNewDoc((xmlChar *)"1.0"); root = xmlNewNode(NULL, (xmlChar *)"data"); xmlDocSetRootElement(doc, root); node = xmlNewChild(root, NULL, (xmlChar *)"program", NULL); xml_setpropf(node, "SECTOR_SIZE_IN_BYTES", "%d", sector_size); xml_setpropf(node, "num_partition_sectors", "%d", num_sectors); xml_setpropf(node, "physical_partition_number", "%d", program->partition); xml_setpropf(node, "start_sector", "%s", program->start_sector); if (qdl->slot != UINT_MAX) { xml_setpropf(node, "slot", "%u", qdl->slot); } if (program->filename) xml_setpropf(node, "filename", "%s", program->filename); if (program->is_nand) { xml_setpropf(node, "PAGES_PER_BLOCK", "%d", program->pages_per_block); xml_setpropf(node, "last_sector", "%d", program->last_sector); } ret = firehose_write(qdl, doc); if (ret < 0) { ux_err("failed to send program request\n"); goto out; } ret = firehose_read(qdl, 10000, firehose_generic_parser, NULL); if (ret) { ux_err("failed to setup programming\n"); goto out; } t0 = time(NULL); if (!program->sparse) { lseek(fd, (off_t)program->file_offset * sector_size, SEEK_SET); } else { switch (program->sparse_chunk_type) { case CHUNK_TYPE_RAW: lseek(fd, program->sparse_offset, SEEK_SET); break; case CHUNK_TYPE_FILL: fill_value = program->sparse_fill_value; for (i = 0; i < qdl->max_payload_size; i += sizeof(fill_value)) memcpy(buf + i, &fill_value, sizeof(fill_value)); break; default: ux_err("[SPARSE] invalid chunk type\n"); goto out; } } left = num_sectors; ux_debug("FIREHOSE RAW BINARY WRITE: %s, %d bytes\n", program->filename, sector_size * num_sectors); while (left > 0) { /* * We should calculate hash for every raw packet sent, * not for the whole binary. */ vip_gen_chunk_init(qdl); chunk_size = MIN(qdl->max_payload_size / sector_size, left); if (!program->sparse || program->sparse_chunk_type != CHUNK_TYPE_FILL) { n = read(fd, buf, chunk_size * sector_size); if (n < 0) { ux_err("failed to read %s\n", program->filename); goto out; } if ((size_t)n < qdl->max_payload_size) memset(buf + n, 0, qdl->max_payload_size - n); } vip_gen_chunk_update(qdl, buf, chunk_size * 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 * sector_size, zlp_timeout); if (n < 0) { ux_err("USB write failed for data chunk\n"); ret = firehose_read(qdl, 30000, firehose_generic_parser, NULL); if (ret) ux_err("flashing of chunk failed\n"); goto out; } if ((size_t)n != chunk_size * sector_size) { ux_err("USB write truncated\n"); ret = -1; goto out; } left -= chunk_size; vip_gen_chunk_store(qdl); ux_progress("%s", num_sectors - left, num_sectors, program->label); } t = time(NULL) - t0; ret = firehose_read(qdl, 30000, firehose_generic_parser, NULL); if (ret) { ux_err("flashing of %s failed\n", program->label); } else if (t) { ux_info("flashed \"%s\" successfully at %lukB/s\n", program->label, (unsigned long)sector_size * num_sectors / t / 1024); } else { ux_info("flashed \"%s\" successfully\n", program->label); } out: xmlFreeDoc(doc); free(buf); return ret == FIREHOSE_ACK ? 0 : -1; } static int firehose_issue_read(struct qdl_device *qdl, struct read_op *read_op, int fd, void *out_buf, size_t out_len, bool quiet) { unsigned int sector_size; size_t chunk_size; size_t out_offset = 0; xmlNode *root; xmlNode *node; xmlDoc *doc; void *buf; time_t t0; time_t t; size_t left; int ret; int n; buf = malloc(qdl->max_payload_size); if (!buf) err(1, "failed to allocate sector buffer"); doc = xmlNewDoc((xmlChar *)"1.0"); root = xmlNewNode(NULL, (xmlChar *)"data"); xmlDocSetRootElement(doc, root); sector_size = read_op->sector_size ? : qdl->sector_size; node = xmlNewChild(root, NULL, (xmlChar *)"read", NULL); xml_setpropf(node, "SECTOR_SIZE_IN_BYTES", "%d", sector_size); xml_setpropf(node, "num_partition_sectors", "%d", read_op->num_sectors); xml_setpropf(node, "physical_partition_number", "%d", read_op->partition); xml_setpropf(node, "start_sector", "%s", read_op->start_sector); if (qdl->slot != UINT_MAX) { xml_setpropf(node, "slot", "%u", qdl->slot); } if (read_op->filename) xml_setpropf(node, "filename", "%s", read_op->filename); ret = firehose_write(qdl, doc); if (ret < 0) { ux_err("failed to send read command\n"); goto out; } ret = firehose_read(qdl, 10000, firehose_generic_parser, NULL); if (ret) { if (!quiet) ux_err("failed to setup reading operation\n"); goto out; } t0 = time(NULL); left = read_op->num_sectors; while (left > 0) { chunk_size = MIN(qdl->max_payload_size / sector_size, left); n = qdl_read(qdl, buf, chunk_size * sector_size, 30000); if (n < 0) { err(1, "failed to read"); } if ((size_t)n != chunk_size * sector_size) { err(1, "failed to read full sector"); } if (out_buf) { if ((size_t)n > out_len - out_offset) n = out_len - out_offset; memcpy(out_buf + out_offset, buf, n); out_offset += n; } else { n = write(fd, buf, n); if (n < 0 || (size_t)n != chunk_size * sector_size) { err(1, "failed to write"); } } left -= chunk_size; if (!quiet) ux_progress("%s", read_op->num_sectors - left, read_op->num_sectors, read_op->filename); } ret = firehose_read(qdl, 10000, firehose_generic_parser, NULL); if (ret) { ux_err("read operation failed\n"); goto out; } t = time(NULL) - t0; if (!quiet) { if (t) { ux_info("read \"%s\" successfully at %ldkB/s\n", read_op->filename, (unsigned long)sector_size * read_op->num_sectors / t / 1024); } else { ux_info("read \"%s\" successfully\n", read_op->filename); } } out: xmlFreeDoc(doc); free(buf); return ret; } 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); } static int firehose_read_op(struct qdl_device *qdl, struct read_op *read_op, int fd) { return firehose_issue_read(qdl, read_op, fd, NULL, 0, false); } static int firehose_apply_patch(struct qdl_device *qdl, struct patch *patch) { xmlNode *root; xmlNode *node; xmlDoc *doc; int ret; ux_debug("applying patch \"%s\"\n", patch->what); doc = xmlNewDoc((xmlChar *)"1.0"); root = xmlNewNode(NULL, (xmlChar *)"data"); xmlDocSetRootElement(doc, root); node = xmlNewChild(root, NULL, (xmlChar *)"patch", NULL); xml_setpropf(node, "SECTOR_SIZE_IN_BYTES", "%d", patch->sector_size); xml_setpropf(node, "byte_offset", "%d", patch->byte_offset); xml_setpropf(node, "filename", "%s", patch->filename); xml_setpropf(node, "physical_partition_number", "%d", patch->partition); xml_setpropf(node, "size_in_bytes", "%d", patch->size_in_bytes); xml_setpropf(node, "start_sector", "%s", patch->start_sector); xml_setpropf(node, "value", "%s", patch->value); if (qdl->slot != UINT_MAX) { xml_setpropf(node, "slot", "%u", qdl->slot); } ret = firehose_write(qdl, doc); if (ret < 0) goto out; ret = firehose_read(qdl, 5000, firehose_generic_parser, NULL); if (ret) ux_err("patch application failed\n"); out: xmlFreeDoc(doc); return ret == FIREHOSE_ACK ? 0 : -1; } static int firehose_send_single_tag(struct qdl_device *qdl, xmlNode *node) { xmlNode *root; xmlDoc *doc; int ret; doc = xmlNewDoc((xmlChar *)"1.0"); root = xmlNewNode(NULL, (xmlChar *)"data"); xmlDocSetRootElement(doc, root); xmlAddChild(root, node); ret = firehose_write(qdl, doc); if (ret < 0) goto out; ret = firehose_read(qdl, 5000, firehose_generic_parser, NULL); if (ret) { ux_err("ufs request failed\n"); ret = -EINVAL; } out: xmlFreeDoc(doc); return ret; } int firehose_apply_ufs_common(struct qdl_device *qdl, struct ufs_common *ufs) { xmlNode *node_to_send; int ret; node_to_send = xmlNewNode(NULL, (xmlChar *)"ufs"); xml_setpropf(node_to_send, "bNumberLU", "%d", ufs->bNumberLU); xml_setpropf(node_to_send, "bBootEnable", "%d", ufs->bBootEnable); xml_setpropf(node_to_send, "bDescrAccessEn", "%d", ufs->bDescrAccessEn); xml_setpropf(node_to_send, "bInitPowerMode", "%d", ufs->bInitPowerMode); xml_setpropf(node_to_send, "bHighPriorityLUN", "%d", ufs->bHighPriorityLUN); xml_setpropf(node_to_send, "bSecureRemovalType", "%d", ufs->bSecureRemovalType); xml_setpropf(node_to_send, "bInitActiveICCLevel", "%d", ufs->bInitActiveICCLevel); xml_setpropf(node_to_send, "wPeriodicRTCUpdate", "%d", ufs->wPeriodicRTCUpdate); xml_setpropf(node_to_send, "bConfigDescrLock", "%d", ufs->bConfigDescrLock); if (qdl->slot != UINT_MAX) { xml_setpropf(node_to_send, "slot", "%u", qdl->slot); } if (ufs->wb) { xml_setpropf(node_to_send, "bWriteBoosterBufferPreserveUserSpaceEn", "%d", ufs->bWriteBoosterBufferPreserveUserSpaceEn); xml_setpropf(node_to_send, "bWriteBoosterBufferType", "%d", ufs->bWriteBoosterBufferType); xml_setpropf(node_to_send, "shared_wb_buffer_size_in_kb", "%d", ufs->shared_wb_buffer_size_in_kb); } ret = firehose_send_single_tag(qdl, node_to_send); if (ret) ux_err("failed to send ufs common tag\n"); return ret == FIREHOSE_ACK ? 0 : -1; } int firehose_apply_ufs_body(struct qdl_device *qdl, struct ufs_body *ufs) { xmlNode *node_to_send; int ret; node_to_send = xmlNewNode(NULL, (xmlChar *)"ufs"); xml_setpropf(node_to_send, "LUNum", "%d", ufs->LUNum); xml_setpropf(node_to_send, "bLUEnable", "%d", ufs->bLUEnable); xml_setpropf(node_to_send, "bBootLunID", "%d", ufs->bBootLunID); xml_setpropf(node_to_send, "size_in_kb", "%d", ufs->size_in_kb); xml_setpropf(node_to_send, "bDataReliability", "%d", ufs->bDataReliability); xml_setpropf(node_to_send, "bLUWriteProtect", "%d", ufs->bLUWriteProtect); xml_setpropf(node_to_send, "bMemoryType", "%d", ufs->bMemoryType); xml_setpropf(node_to_send, "bLogicalBlockSize", "%d", ufs->bLogicalBlockSize); xml_setpropf(node_to_send, "bProvisioningType", "%d", ufs->bProvisioningType); xml_setpropf(node_to_send, "wContextCapabilities", "%d", ufs->wContextCapabilities); if (qdl->slot != UINT_MAX) { xml_setpropf(node_to_send, "slot", "%u", qdl->slot); } if (ufs->desc) xml_setpropf(node_to_send, "desc", "%s", ufs->desc); ret = firehose_send_single_tag(qdl, node_to_send); if (ret) ux_err("failed to apply ufs body tag\n"); return ret == FIREHOSE_ACK ? 0 : -1; } int firehose_apply_ufs_epilogue(struct qdl_device *qdl, struct ufs_epilogue *ufs, bool commit) { xmlNode *node_to_send; int ret; node_to_send = xmlNewNode(NULL, (xmlChar *)"ufs"); xml_setpropf(node_to_send, "LUNtoGrow", "%d", ufs->LUNtoGrow); xml_setpropf(node_to_send, "commit", "%d", commit); if (qdl->slot != UINT_MAX) { xml_setpropf(node_to_send, "slot", "%u", qdl->slot); } ret = firehose_send_single_tag(qdl, node_to_send); if (ret) ux_err("failed to apply ufs epilogue\n"); return ret == FIREHOSE_ACK ? 0 : -1; } static int firehose_set_bootable(struct qdl_device *qdl, int part) { xmlNode *root; xmlNode *node; xmlDoc *doc; int ret; doc = xmlNewDoc((xmlChar *)"1.0"); root = xmlNewNode(NULL, (xmlChar *)"data"); xmlDocSetRootElement(doc, root); node = xmlNewChild(root, NULL, (xmlChar *)"setbootablestoragedrive", NULL); xml_setpropf(node, "value", "%d", part); ret = firehose_write(qdl, doc); xmlFreeDoc(doc); if (ret < 0) return -1; ret = firehose_read(qdl, 5000, firehose_generic_parser, NULL); if (ret) { ux_err("failed to mark partition %d as bootable\n", part); return -1; } ux_info("partition %d is now bootable\n", part); return 0; } static int firehose_reset(struct qdl_device *qdl) { xmlNode *root; xmlNode *node; xmlDoc *doc; int ret; doc = xmlNewDoc((xmlChar *)"1.0"); root = xmlNewNode(NULL, (xmlChar *)"data"); xmlDocSetRootElement(doc, root); node = xmlNewChild(root, NULL, (xmlChar *)"power", NULL); xml_setpropf(node, "value", "reset"); ret = firehose_write(qdl, doc); xmlFreeDoc(doc); if (ret < 0) return -1; ret = firehose_read(qdl, 5000, firehose_generic_parser, NULL); if (ret < 0) ux_err("failed to request device reset\n"); /* drain any remaining log messages for reset */ else firehose_read(qdl, 1000, firehose_generic_parser, NULL); return ret == FIREHOSE_ACK ? 0 : -1; } static int firehose_detect_and_configure(struct qdl_device *qdl, bool skip_storage_init __unused, enum qdl_storage_type storage, unsigned int timeout_s) { struct timeval timeout = { .tv_sec = timeout_s }; struct timeval now; int ret; gettimeofday(&now, NULL); timeradd(&now, &timeout, &timeout); for (;;) { ret = firehose_try_configure(qdl, false, storage); if (ret == FIREHOSE_ACK) { break; } else if (ret != -ETIMEDOUT) { ux_err("configure request failed\n"); return -1; } gettimeofday(&now, NULL); if (timercmp(&now, &timeout, >)) { ux_err("failed to detect firehose programmer\n"); return -1; } } return 0; } int firehose_provision(struct qdl_device *qdl) { int ret; ret = firehose_detect_and_configure(qdl, true, QDL_STORAGE_UFS, 5); if (ret) return ret; ret = ufs_provisioning_execute(qdl, firehose_apply_ufs_common, firehose_apply_ufs_body, firehose_apply_ufs_epilogue); if (!ret) ux_info("UFS provisioning succeeded\n"); else ux_info("UFS provisioning failed\n"); firehose_reset(qdl); return ret; } int firehose_run(struct qdl_device *qdl) { bool multiple; int bootable; int ret; ux_info("waiting for programmer...\n"); ret = firehose_detect_and_configure(qdl, true, qdl->storage_type, 5); 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; ret = program_execute(qdl, firehose_program); if (ret) return ret; ret = patch_execute(qdl, firehose_apply_patch); if (ret) return ret; ret = read_op_execute(qdl, firehose_read_op); if (ret) return ret; bootable = program_find_bootable_partition(&multiple); if (bootable < 0) { ux_debug("no boot partition found\n"); } else { if (multiple) { ux_info("Multiple candidates for primary bootloader found, using partition %d\n", bootable); } firehose_set_bootable(qdl, bootable); } firehose_reset(qdl); return 0; } qdl-2.4/gpt.c000066400000000000000000000150721512113701100130720ustar00rootroot00000000000000// 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; unsigned 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; /* if first_lba == last_lba there is 1 sector worth of data (IE: add 1 below) */ partition->num_sectors = entry->last_lba - entry->first_lba + 1; ux_debug(" %3d: %s start sector %u, num sectors %u\n", i, partition->name, 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 != (unsigned int)(*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; } qdl-2.4/gpt.h000066400000000000000000000003731512113701100130750ustar00rootroot00000000000000/* 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 qdl-2.4/io.c000066400000000000000000000030261512113701100127030ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include "qdl.h" struct qdl_device *qdl_init(enum QDL_DEVICE_TYPE type) { if (type == QDL_DEVICE_USB) return usb_init(); if (type == QDL_DEVICE_SIM) return sim_init(); return NULL; } void qdl_deinit(struct qdl_device *qdl) { if (qdl) free(qdl); } void qdl_set_out_chunk_size(struct qdl_device *qdl, long size) { qdl->set_out_chunk_size(qdl, size); } int qdl_open(struct qdl_device *qdl, const char *serial) { return qdl->open(qdl, serial); } void qdl_close(struct qdl_device *qdl) { qdl->close(qdl); } /** * qdl_read() - Read a message from the device * @qdl: device handle * @buf: buffer to write the data into * @len: maximum length of data to be read * @timeout: timeout for the read, in milliseconds * * Returns: number of bytes read, might be zero for a ZLP * negative errno on failure (notably -ETIMEDOUT) */ int qdl_read(struct qdl_device *qdl, void *buf, size_t len, unsigned int timeout) { return qdl->read(qdl, buf, len, timeout); } /** * qdl_write() - Write a message from the device * @qdl: device handle * @buf: buffer with data to be written * @len: length of data to be written * @timeout: timeout for write, in milliseconds * * Returns: number of bytes read * negative errno on failure (notably -ETIMEDOUT) */ int qdl_write(struct qdl_device *qdl, const void *buf, size_t len, unsigned int timeout) { return qdl->write(qdl, buf, len, timeout); } qdl-2.4/ks.c000066400000000000000000000060311512113701100127100ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qdl.h" #include "oscompat.h" #ifdef _WIN32 const char *__progname = "ks"; #endif static struct qdl_device qdl; bool qdl_debug; int qdl_read(struct qdl_device *qdl, void *buf, size_t len, unsigned int timeout __unused) { return read(qdl->fd, buf, len); } int qdl_write(struct qdl_device *qdl, const void *buf, size_t len, unsigned int timeout __unused) { return write(qdl->fd, buf, len); } static void print_usage(FILE *out) { extern const char *__progname; fprintf(out, "%s -p -s ...\n", __progname); fprintf(out, " -h --help Print this usage info\n" " -p --port Sahara device node to use\n" " -s --sahara Sahara protocol file mapping\n" "\n" "One -p instance is required. One or more -s instances are required.\n" "\n" "Example:\n" "ks -p /dev/mhi0_QAIC_SAHARA -s 1:/opt/qti-aic/firmware/fw1.bin -s 2:/opt/qti-aic/firmware/fw2.bin\n"); } int main(int argc, char **argv) { struct sahara_image mappings[MAPPING_SZ] = {}; const char *filename; bool found_mapping = false; char *dev_node = NULL; long file_id; char *colon; int opt; int ret; static struct option options[] = { {"debug", no_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'}, {"port", required_argument, 0, 'p'}, {"sahara", required_argument, 0, 's'}, {0, 0, 0, 0} }; while ((opt = getopt_long(argc, argv, "dvp:s:h", options, NULL)) != -1) { switch (opt) { case 'd': qdl_debug = true; break; case 'v': print_version(); return 0; case 'p': dev_node = optarg; printf("Using port - %s\n", dev_node); break; case 's': found_mapping = true; file_id = strtol(optarg, NULL, 10); if (file_id < 0) { print_usage(stderr); return 1; } if (file_id >= MAPPING_SZ) { fprintf(stderr, "ID:%ld exceeds the max value of %d\n", file_id, MAPPING_SZ - 1); return 1; } colon = strchr(optarg, ':'); if (!colon) { print_usage(stderr); return 1; } filename = &optarg[colon - optarg + 1]; ret = load_sahara_image(filename, &mappings[file_id]); if (ret < 0) exit(1); printf("Created mapping ID:%ld File:%s\n", file_id, filename); break; case 'h': print_usage(stdout); return 0; default: print_usage(stderr); return 1; } } // -p and -s is required if (!dev_node || !found_mapping) { print_usage(stderr); return 1; } if (qdl_debug) print_version(); qdl.fd = open(dev_node, O_RDWR); if (qdl.fd < 0) { fprintf(stderr, "Unable to open %s\n", dev_node); return 1; } ret = sahara_run(&qdl, mappings, false, NULL, NULL); if (ret < 0) return 1; return 0; } qdl-2.4/list.h000066400000000000000000000033401512113701100132530ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2016, Linaro Ltd. */ #ifndef __LIST_H__ #define __LIST_H__ #include #include struct list_head { struct list_head *prev; struct list_head *next; }; #define LIST_INIT(list) { &(list), &(list) } static inline void list_init(struct list_head *list) { list->prev = list; list->next = list; } static inline bool list_empty(struct list_head *list) { return list->next == list; } static inline void list_add(struct list_head *list, struct list_head *item) { struct list_head *prev = list->prev; item->next = list; item->prev = prev; prev->next = item; list->prev = item; } static inline void list_del(struct list_head *item) { item->prev->next = item->next; item->next->prev = item->prev; } #define list_for_each(item, list) \ for (item = (list)->next; item != list; item = item->next) #define list_for_each_safe(item, tmp, list) \ for (item = (list)->next, tmp = item->next; item != list; item = tmp, tmp = item->next) #define list_entry(item, type, member) \ container_of(item, type, member) #define list_entry_first(list, type, member) \ container_of((list)->next, type, member) #define list_entry_next(item, member) \ container_of((item)->member.next, typeof(*(item)), member) #define list_for_each_entry(item, list, member) \ for (item = list_entry_first(list, typeof(*(item)), member); \ &item->member != list; \ item = list_entry_next(item, member)) #define list_for_each_entry_safe(item, next, list, member) \ for (item = list_entry_first(list, typeof(*(item)), member), \ next = list_entry_next(item, member); \ &item->member != list; \ item = next, \ next = list_entry_next(item, member)) \ #endif qdl-2.4/oscompat.c000066400000000000000000000025201512113701100141170ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause #ifdef _WIN32 #include #include #include #include #include "oscompat.h" extern const char *__progname; void timeradd(const struct timeval *a, const struct timeval *b, struct timeval *result) { result->tv_sec = a->tv_sec + b->tv_sec; result->tv_usec = a->tv_usec + b->tv_usec; if (result->tv_usec >= 1000000) { result->tv_sec += 1; result->tv_usec -= 1000000; } } void err(int eval, const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "%s: ", __progname); if (fmt) { vfprintf(stderr, fmt, ap); fprintf(stderr, ": "); } fprintf(stderr, "%s\n", strerror(errno)); va_end(ap); exit(eval); } void errx(int eval, const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "%s: ", __progname); if (fmt) vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); exit(eval); } void warn(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "%s: ", __progname); if (fmt) { vfprintf(stderr, fmt, ap); fprintf(stderr, ": "); } fprintf(stderr, "%s\n", strerror(errno)); va_end(ap); } void warnx(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "%s: ", __progname); if (fmt) vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } #endif // _WIN32 qdl-2.4/oscompat.h000066400000000000000000000007201512113701100141240ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ #ifndef __OSCOMPAT_H__ #define __OSCOMPAT_H__ #ifndef _WIN32 #include #define O_BINARY 0 #else // _WIN32 #include #include void timeradd(const struct timeval *a, const struct timeval *b, struct timeval *result); void err(int eval, const char *fmt, ...); void errx(int eval, const char *fmt, ...); void warn(const char *fmt, ...); void warnx(const char *fmt, ...); #endif #endif qdl-2.4/patch.c000066400000000000000000000052321512113701100133740ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2016-2017, Linaro Ltd. * All rights reserved. */ #include #include #include #include #include #include #include "patch.h" #include "qdl.h" static struct list_head patches = LIST_INIT(patches); static bool patches_loaded; int patch_load(const char *patch_file) { struct patch *patch; xmlNode *node; xmlNode *root; xmlDoc *doc; int errors; doc = xmlReadFile(patch_file, NULL, 0); if (!doc) { ux_err("failed to parse patch-type file \"%s\"\n", patch_file); return -EINVAL; } root = xmlDocGetRootElement(doc); for (node = root->children; node ; node = node->next) { if (node->type != XML_ELEMENT_NODE) continue; if (xmlStrcmp(node->name, (xmlChar *)"patch")) { ux_err("unrecognized tag \"%s\" in patch-type file, ignoring\n", node->name); continue; } errors = 0; patch = calloc(1, sizeof(struct patch)); patch->sector_size = attr_as_unsigned(node, "SECTOR_SIZE_IN_BYTES", &errors); patch->byte_offset = attr_as_unsigned(node, "byte_offset", &errors); patch->filename = attr_as_string(node, "filename", &errors); patch->partition = attr_as_unsigned(node, "physical_partition_number", &errors); patch->size_in_bytes = attr_as_unsigned(node, "size_in_bytes", &errors); patch->start_sector = attr_as_string(node, "start_sector", &errors); patch->value = attr_as_string(node, "value", &errors); patch->what = attr_as_string(node, "what", &errors); if (errors) { ux_err("errors while parsing patch-type file \"%s\"\n", patch_file); free(patch); continue; } list_add(&patches, &patch->node); } xmlFreeDoc(doc); patches_loaded = true; return 0; } int patch_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, struct patch *patch)) { struct patch *patch; unsigned int count = 0; unsigned int idx = 0; int ret; if (!patches_loaded) return 0; list_for_each_entry(patch, &patches, node) { if (!patch->filename) continue; if (!strcmp(patch->filename, "DISK")) count++; } list_for_each_entry(patch, &patches, node) { if (!patch->filename) continue; if (strcmp(patch->filename, "DISK")) continue; ret = apply(qdl, patch); if (ret) return ret; ux_progress("Applying patches", idx++, count); } ux_info("%d patches applied\n", idx); return 0; } void free_patches(void) { struct patch *patch; struct patch *next; list_for_each_entry_safe(patch, next, &patches, node) { free((void *)patch->filename); free((void *)patch->start_sector); free((void *)patch->value); free((void *)patch->what); free(patch); } list_init(&patches); } qdl-2.4/patch.h000066400000000000000000000010341512113701100133750ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ #ifndef __PATCH_H__ #define __PATCH_H__ #include "list.h" struct qdl_device; struct patch { unsigned int sector_size; unsigned int byte_offset; const char *filename; unsigned int partition; unsigned int size_in_bytes; const char *start_sector; const char *value; const char *what; struct list_head node; }; int patch_load(const char *patch_file); int patch_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, struct patch *patch)); void free_patches(void); #endif qdl-2.4/program.c000066400000000000000000000263221512113701100137470ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2016-2017, Linaro Ltd. * All rights reserved. */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include "program.h" #include "qdl.h" #include "oscompat.h" #include "sparse.h" #include "gpt.h" static struct list_head programs = LIST_INIT(programs); static int load_erase_tag(xmlNode *node, bool is_nand) { struct program *program; int errors = 0; program = calloc(1, sizeof(struct program)); program->is_nand = is_nand; program->is_erase = true; program->sector_size = attr_as_unsigned(node, "SECTOR_SIZE_IN_BYTES", &errors); program->num_sectors = attr_as_unsigned(node, "num_partition_sectors", &errors); program->partition = attr_as_unsigned(node, "physical_partition_number", &errors); program->start_sector = attr_as_string(node, "start_sector", &errors); if (is_nand) { program->pages_per_block = attr_as_unsigned(node, "PAGES_PER_BLOCK", &errors); } if (errors) { ux_err("errors while parsing erase tag\n"); free(program); return -EINVAL; } list_add(&programs, &program->node); return 0; } static int program_load_sparse(struct program *program, int fd) { struct program *program_sparse = NULL; char tmp[PATH_MAX]; sparse_header_t sparse_header; unsigned int start_sector; uint32_t sparse_fill_value; uint64_t chunk_size; off_t sparse_offset; int chunk_type; if (sparse_header_parse(fd, &sparse_header)) { /* * If the XML tag "program" contains the attribute 'sparse="true"' * for a partition node but lacks a sparse header, * it will be validated against the defined partition size. * If the sizes match, it is likely that the 'sparse="true"' attribute * was set by mistake, fix the sparse flag and add the * program entry to the list. */ if ((off_t)program->sector_size * program->num_sectors == lseek(fd, 0, SEEK_END)) { program->sparse = false; list_add(&programs, &program->node); return 0; } ux_err("[PROGRAM] Unable to parse sparse header at %s...failed\n", program->filename); return -1; } for (uint32_t i = 0; i < sparse_header.total_chunks; ++i) { chunk_type = sparse_chunk_header_parse(fd, &sparse_header, &chunk_size, &sparse_fill_value, &sparse_offset); if (chunk_type < 0) { ux_err("[PROGRAM] Unable to parse sparse chunk %i at %s...failed\n", i, program->filename); return -1; } if (chunk_size == 0) continue; if (chunk_size % program->sector_size != 0) { ux_err("[SPARSE] File chunk #%u size %" PRIu64 " is not a sector-multiple\n", i, chunk_size); return -1; } if (chunk_size / program->sector_size >= UINT_MAX) { /* * Perhaps the programmer can handle larger "num_sectors"? * Let's cap it for now, it's big enough for now... */ ux_err("[SPARSE] File chunk #%u size %" PRIu64 " is too large\n", i, chunk_size); return -1; } if (chunk_type == CHUNK_TYPE_RAW || chunk_type == CHUNK_TYPE_FILL) { program_sparse = calloc(1, sizeof(struct program)); program_sparse->pages_per_block = program->pages_per_block; program_sparse->sector_size = program->sector_size; program_sparse->file_offset = program->file_offset; program_sparse->filename = strdup(program->filename); program_sparse->label = strdup(program->label); program_sparse->partition = program->partition; program_sparse->sparse = program->sparse; program_sparse->start_sector = strdup(program->start_sector); program_sparse->last_sector = program->last_sector; program_sparse->is_nand = program->is_nand; program_sparse->is_erase = false; program_sparse->sparse_chunk_type = chunk_type; program_sparse->num_sectors = chunk_size / program->sector_size; if (chunk_type == CHUNK_TYPE_RAW) program_sparse->sparse_offset = sparse_offset; else program_sparse->sparse_fill_value = sparse_fill_value; list_add(&programs, &program_sparse->node); } start_sector = (unsigned int)strtoul(program->start_sector, NULL, 0); start_sector += chunk_size / program->sector_size; sprintf(tmp, "%u", start_sector); program->start_sector = strdup(tmp); } return 0; } static int load_program_tag(xmlNode *node, bool is_nand, bool allow_missing, const char *incdir) { struct program *program; char tmp[PATH_MAX]; int errors = 0; int ret; int fd = -1; program = calloc(1, sizeof(struct program)); program->is_nand = is_nand; program->sector_size = attr_as_unsigned(node, "SECTOR_SIZE_IN_BYTES", &errors); program->filename = attr_as_string(node, "filename", &errors); program->label = attr_as_string(node, "label", &errors); program->num_sectors = attr_as_unsigned(node, "num_partition_sectors", &errors); program->partition = attr_as_unsigned(node, "physical_partition_number", &errors); program->sparse = attr_as_bool(node, "sparse", &errors); program->start_sector = attr_as_string(node, "start_sector", &errors); if (is_nand) { program->pages_per_block = attr_as_unsigned(node, "PAGES_PER_BLOCK", &errors); if (xmlGetProp(node, (xmlChar *)"last_sector")) { program->last_sector = attr_as_unsigned(node, "last_sector", &errors); } } else { program->file_offset = attr_as_unsigned(node, "file_sector_offset", &errors); } if (errors) { ux_err("errors while parsing program tag\n"); free(program); return -EINVAL; } if (program->filename) { if (incdir) { snprintf(tmp, PATH_MAX, "%s/%s", incdir, program->filename); if (access(tmp, F_OK) != -1) { free((void *)program->filename); program->filename = strdup(tmp); } } fd = open(program->filename, O_RDONLY | O_BINARY); if (fd < 0) { ux_info("unable to open %s", program->filename); if (!allow_missing) { ux_info("...failing\n"); return -1; } ux_info("...ignoring\n"); free((void *)program->filename); program->filename = NULL; } } if (fd >= 0 && program->sparse) { ret = program_load_sparse(program, fd); if (ret < 0) return -1; /* * Chunks were added to the program list, drop the filename of * the parent, to prevent this from being written to the device */ free((void *)program->filename); program->filename = NULL; } list_add(&programs, &program->node); if (fd >= 0) close(fd); return 0; } int program_load(const char *program_file, bool is_nand, bool allow_missing, const char *incdir) { xmlNode *node; xmlNode *root; xmlDoc *doc; int errors = 0; doc = xmlReadFile(program_file, NULL, 0); if (!doc) { ux_err("failed to parse program-type file \"%s\"\n", program_file); return -EINVAL; } root = xmlDocGetRootElement(doc); for (node = root->children; node ; node = node->next) { if (node->type != XML_ELEMENT_NODE) continue; if (!xmlStrcmp(node->name, (xmlChar *)"erase")) errors = load_erase_tag(node, is_nand); else if (!xmlStrcmp(node->name, (xmlChar *)"program")) errors = load_program_tag(node, is_nand, allow_missing, incdir); else { ux_err("unrecognized tag \"%s\" in program-type file \"%s\"\n", node->name, program_file); errors = -EINVAL; } if (errors) goto out; } out: xmlFreeDoc(doc); return errors; } int program_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, struct program *program, int fd)) { struct program *program; int ret; int fd; list_for_each_entry(program, &programs, node) { if (program->is_erase || !program->filename) continue; fd = open(program->filename, O_RDONLY | O_BINARY); if (fd < 0) { ux_err("unable to open %s\n", program->filename); return -1; } ret = apply(qdl, program, fd); close(fd); if (ret) return ret; } return 0; } int erase_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, struct program *program)) { struct program *program; int ret; list_for_each_entry(program, &programs, node) { if (!program->is_erase) continue; ret = apply(qdl, program); if (ret) return ret; } return 0; } static struct program *program_find_partition(const char *partition) { struct program *program; const char *label; list_for_each_entry(program, &programs, node) { label = program->label; if (!label) continue; if (!strcmp(label, partition)) return program; } return NULL; } /** * program_find_bootable_partition() - find one bootable partition * * Returns partition number, or negative errno on failure. * * Scan program tags for a partition with the label "sbl1", "xbl" or "xbl_a" * and return the partition number for this. If more than one line matches * we're informing the caller so that they can warn the user about the * uncertainty of this logic. */ int program_find_bootable_partition(bool *multiple_found) { struct program *program; int part = -ENOENT; *multiple_found = false; program = program_find_partition("xbl"); if (program) part = program->partition; program = program_find_partition("xbl_a"); if (program) { if (part != -ENOENT) *multiple_found = true; else part = program->partition; } program = program_find_partition("sbl1"); if (program) { if (part != -ENOENT) *multiple_found = true; else part = program->partition; } return part; } /** * program_is_sec_partition_flashed() - find if secdata partition is flashed * * Returns true if filename for secdata is set in program*.xml, * or false otherwise. */ int program_is_sec_partition_flashed(void) { struct program *program; program = program_find_partition("secdata"); if (!program) return false; if (program->filename) return true; return false; } void free_programs(void) { struct program *program; struct program *next; list_for_each_entry_safe(program, next, &programs, node) { free((void *)program->filename); free((void *)program->label); free((void *)program->start_sector); free((void *)program->gpt_partition); free(program); } list_init(&programs); } 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, &gpt_partition); if (ret < 0) return ret; program = calloc(1, sizeof(struct program)); program->sector_size = 0; program->file_offset = 0; program->filename = filename ? strdup(filename) : NULL; program->label = filename ? strdup(filename) : NULL; program->num_sectors = num_sectors; program->partition = partition; program->sparse = false; sprintf(buf, "%u", start_sector); program->start_sector = strdup(buf); program->last_sector = 0; program->is_nand = false; program->is_erase = false; program->gpt_partition = gpt_partition; list_add(&programs, &program->node); return 0; } int program_resolve_gpt_deferrals(struct qdl_device *qdl) { struct program *program; unsigned int start_sector; char buf[20]; int ret; list_for_each_entry(program, &programs, node) { 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; } qdl-2.4/program.h000066400000000000000000000022641512113701100137530ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ #ifndef __PROGRAM_H__ #define __PROGRAM_H__ #include #include #include #include "list.h" struct program { unsigned int pages_per_block; unsigned int sector_size; unsigned int file_offset; const char *filename; const char *label; unsigned int num_sectors; int partition; bool sparse; const char *start_sector; unsigned int last_sector; bool is_nand; bool is_erase; unsigned int sparse_chunk_type; uint32_t sparse_fill_value; off_t sparse_offset; const char *gpt_partition; struct list_head node; }; struct qdl_device; int program_load(const char *program_file, bool is_nand, bool allow_missing, const char *incdir); int program_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, struct program *program, int fd)); int erase_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, struct program *program)); 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); #endif qdl-2.4/qdl.c000066400000000000000000000415461512113701100130650ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2016-2017, Linaro Ltd. * Copyright (c) 2018, The Linux Foundation. All rights reserved. * All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include "qdl.h" #include "patch.h" #include "program.h" #include "ufs.h" #include "oscompat.h" #include "vip.h" #ifdef _WIN32 const char *__progname = "qdl"; #endif #define MAX_USBFS_BULK_SIZE (16 * 1024) enum { QDL_FILE_UNKNOWN, QDL_FILE_PATCH, QDL_FILE_PROGRAM, QDL_FILE_READ, QDL_FILE_UFS, QDL_FILE_CONTENTS, QDL_CMD_READ, QDL_CMD_WRITE, }; bool qdl_debug; static int detect_type(const char *verb) { xmlNode *root; xmlDoc *doc; xmlNode *node; int type = QDL_FILE_UNKNOWN; if (!strcmp(verb, "read")) return QDL_CMD_READ; if (!strcmp(verb, "write")) return QDL_CMD_WRITE; if (access(verb, F_OK)) { ux_err("%s is not a verb and not a XML file\n", verb); return -EINVAL; } doc = xmlReadFile(verb, NULL, 0); if (!doc) { ux_err("failed to parse XML file \"%s\"\n", verb); return -EINVAL; } root = xmlDocGetRootElement(doc); if (!xmlStrcmp(root->name, (xmlChar *)"patches")) { type = QDL_FILE_PATCH; } else if (!xmlStrcmp(root->name, (xmlChar *)"data")) { for (node = root->children; node ; node = node->next) { if (node->type != XML_ELEMENT_NODE) continue; if (!xmlStrcmp(node->name, (xmlChar *)"program")) { type = QDL_FILE_PROGRAM; break; } if (!xmlStrcmp(node->name, (xmlChar *)"read")) { type = QDL_FILE_READ; break; } if (!xmlStrcmp(node->name, (xmlChar *)"ufs")) { type = QDL_FILE_UFS; break; } } } else if (!xmlStrcmp(root->name, (xmlChar *)"contents")) { type = QDL_FILE_CONTENTS; } xmlFreeDoc(doc); return type; } static enum qdl_storage_type decode_storage(const char *storage) { if (!strcmp(storage, "emmc")) return QDL_STORAGE_EMMC; if (!strcmp(storage, "nand")) return QDL_STORAGE_NAND; if (!strcmp(storage, "nvme")) return QDL_STORAGE_NVME; if (!strcmp(storage, "spinor")) return QDL_STORAGE_SPINOR; if (!strcmp(storage, "ufs")) return QDL_STORAGE_UFS; fprintf(stderr, "Unknown storage type \"%s\"\n", storage); exit(1); } #define CPIO_MAGIC "070701" struct cpio_newc_header { char c_magic[6]; /* "070701" */ char c_ino[8]; char c_mode[8]; char c_uid[8]; char c_gid[8]; char c_nlink[8]; char c_mtime[8]; char c_filesize[8]; char c_devmajor[8]; char c_devminor[8]; char c_rdevmajor[8]; char c_rdevminor[8]; char c_namesize[8]; char c_check[8]; }; static uint32_t parse_ascii_hex32(const char *s) { uint32_t x = 0; for (int i = 0; i < 8; i++) { if (!isxdigit(s[i])) err(1, "non-hex-digit found in archive header"); if (s[i] <= '9') x = (x << 4) | (s[i] - '0'); else x = (x << 4) | (10 + (s[i] | 32) - 'a'); } return x; } /** * decode_programmer_archive() - Attempt to decode a programmer CPIO archive * @images: List of Sahara images, with @images[0] populated * * The single blob provided in @images[0] might be a CPIO archive containing * Sahara images, in files with names in the format ":". Load * each such Sahara image into the relevant spot in the @images array. * * The original blob (in @images[0]) is freed once it has been consumed. * * Returns: 0 if no archive was found, 1 if archive was decoded, -1 on error */ static int decode_programmer_archive(struct sahara_image *images) { struct sahara_image *blob = &images[0]; struct cpio_newc_header *hdr; size_t filesize; size_t namesize; char name[128]; char *save; char *tok; void *ptr = blob->ptr; void *end = blob->ptr + blob->len; long id; if (blob->len < sizeof(*hdr) || memcmp(ptr, CPIO_MAGIC, 6)) return 0; for (;;) { if (ptr + sizeof(*hdr) > end) { ux_err("programmer archive is truncated\n"); return -1; } hdr = ptr; if (memcmp(hdr->c_magic, "070701", 6)) { ux_err("expected cpio header in programmer archive\n"); return -1; } filesize = parse_ascii_hex32(hdr->c_filesize); namesize = parse_ascii_hex32(hdr->c_namesize); ptr += sizeof(*hdr); if (ptr + namesize > end || ptr + filesize + namesize > end) { ux_err("programmer archive is truncated\n"); return -1; } if (namesize > sizeof(name)) { ux_err("unexpected filename length in progammer archive\n"); return -1; } memcpy(name, ptr, namesize); if (!memcmp(name, "TRAILER!!!", 11)) break; tok = strtok_r(name, ":", &save); id = strtoul(tok, NULL, 0); if (id == 0 || id >= MAPPING_SZ) { ux_err("invalid image id \"%s\" in programmer archive\n", tok); return -1; } ptr += namesize; ptr = ALIGN_UP(ptr, 4); tok = strtok_r(NULL, ":", &save); if (tok) images[id].name = strdup(tok); images[id].len = filesize; images[id].ptr = malloc(filesize); memcpy(images[id].ptr, ptr, filesize); ptr += filesize; ptr = ALIGN_UP(ptr, 4); } free(blob->ptr); blob->ptr = NULL; blob->len = 0; return 1; } /** * decode_sahara_config() - Attempt to decode a Sahara config XML document * @images: List of Sahara images, with @images[0] populated * * The single blob provided in @images[0] might be a XML blob containing * a sahara_config document with definitions of the various Sahara images that * will be loaded. Attempt to parse this and if possible load each referenced * Sahara image into the @images array. * * The original blob (in @images[0]) is freed once it has been consumed. * * Returns: 0 if no archive was found, 1 if archive was decoded, -1 on error */ static int decode_sahara_config(struct sahara_image *images) { struct sahara_image *blob = &images[0]; char image_path_full[PATH_MAX]; const char *image_path; unsigned int image_id; size_t image_path_len; xmlNode *images_node; xmlNode *image_node; char *blob_name_buf; size_t base_path_len; char *base_path; xmlNode *root; xmlDoc *doc; int errors = 0; int ret; if (blob->len < 5 || memcmp(blob->ptr, "ptr, blob->len, blob->name, NULL, 0); if (!doc) { ux_err("failed to parse sahara_config in \"%s\"\n", blob->name); return -1; } blob_name_buf = strdup(blob->name); base_path = dirname(blob_name_buf); base_path_len = strlen(base_path); root = xmlDocGetRootElement(doc); if (xmlStrcmp(root->name, (xmlChar *)"sahara_config")) { ux_err("specified sahara_config \"%s\" is not a Sahara config\n", blob->name); goto err_free_doc; } for (images_node = root->children; images_node; images_node = images_node->next) { if (images_node->type == XML_ELEMENT_NODE && !xmlStrcmp(images_node->name, (xmlChar *)"images")) break; } if (!images_node) { ux_err("no images definitions found in sahara_config \"%s\"\n", blob->name); goto err_free_doc; } for (image_node = images_node->children; image_node; image_node = image_node->next) { if (image_node->type != XML_ELEMENT_NODE || xmlStrcmp(image_node->name, (xmlChar *)"image")) continue; image_id = attr_as_unsigned(image_node, "image_id", &errors); image_path = attr_as_string(image_node, "image_path", &errors); if (image_id == 0 || image_id >= MAPPING_SZ || errors) { ux_err("invalid sahara_config image in \"%s\"\n", blob->name); free((void *)image_path); goto err_free_doc; } image_path_len = strlen(image_path); if (base_path_len + 1 + image_path_len + 1 > PATH_MAX) { free((void *)image_path); goto err_free_doc; } memcpy(image_path_full, base_path, base_path_len); image_path_full[base_path_len] = '/'; memcpy(image_path_full + base_path_len + 1, image_path, image_path_len); image_path_full[base_path_len + 1 + image_path_len] = '\0'; free((void *)image_path); ret = load_sahara_image(image_path_full, &images[image_id]); if (ret < 0) goto err_free_doc; } xmlFreeDoc(doc); free(blob_name_buf); free(blob->ptr); blob->ptr = NULL; blob->len = 0; return 1; err_free_doc: xmlFreeDoc(doc); free(blob_name_buf); return -1; } /** * decode_programmer() - decodes the programmer specifier * @s: programmer specifier, from the user * @images: array of images to populate * @single: legacy single image specifier, for which image id should be ignored * * This parses the progammer specifier @s, which can either be a single * filename, or a comma-separated series of : entries. * * In the first case @images[0] is assigned the provided filename and @single is * set to true. In the second case, each comma-separated entry will be split on * ':' and the given will be assigned to the @image entry indicated * by the given . * * Memory is not allocated for the various strings, instead @s will be modified * by the tokenizer and pointers to the individual parts will be stored in the * @images array. * * Returns: 0 on success, -1 otherwise. */ static int decode_programmer(char *s, struct sahara_image *images, bool *single) { char *filename; char *save1; char *save2; char *pair; char *id_str; long id; int ret; if (!strchr(s, ':')) { ret = load_sahara_image(s, &images[0]); if (ret < 0) return -1; ret = decode_programmer_archive(images); if (ret < 0) return -1; if (!ret) { ret = decode_sahara_config(images); if (ret < 0) return -1; } *single = (ret == 0); return 0; } for (pair = strtok_r(s, ",", &save1); pair; pair = strtok_r(NULL, ",", &save1)) { id_str = strtok_r(pair, ":", &save2); filename = strtok_r(NULL, ":", &save2); if (!id_str || !filename) { ux_err("failed to parse programmer specifier\n"); return -1; } id = strtoul(id_str, NULL, 0); if (id == 0 || id >= MAPPING_SZ) { ux_err("invalid image id \"%s\"\n", id_str); return -1; } ret = load_sahara_image(filename, &images[id]); if (ret < 0) return -1; } *single = false; return 0; } static void print_usage(FILE *out) { extern const char *__progname; fprintf(out, "Usage: %s [options] ( | | )...\n", __progname); fprintf(out, " %s [options] ((read | write)
)...\n", __progname); fprintf(out, " -d, --debug\t\t\tPrint detailed debug info\n"); fprintf(out, " -v, --version\t\t\tPrint the current version and exit\n"); fprintf(out, " -n, --dry-run\t\t\tDry run execution, no device reading or flashing\n"); fprintf(out, " -f, --allow-missing\t\tAllow skipping of missing files during flashing\n"); fprintf(out, " -s, --storage=T\t\tSet target storage type T: \n"); fprintf(out, " -l, --finalize-provisioning\tProvision the target storage\n"); fprintf(out, " -i, --include=T\t\tSet an optional folder T to search for files\n"); fprintf(out, " -S, --serial=T\t\t\tSelect target by serial number T (e.g. <0AA94EFD>)\n"); fprintf(out, " -u, --out-chunk-size=T\t\tOverride chunk size for transaction with T\n"); fprintf(out, " -t, --create-digests=T\t\tGenerate table of digests in the T folder\n"); fprintf(out, " -T, --slot=T\t\t\tSet slot number T for multiple storage devices\n"); fprintf(out, " -D, --vip-table-path=T\t\tUse digest tables in the T folder for VIP\n"); fprintf(out, " -h, --help\t\t\tPrint this usage info\n"); fprintf(out, " \txml file containing or directives\n"); fprintf(out, " \txml file containing directives\n"); fprintf(out, " \txml file containing directives\n"); fprintf(out, "
\tdisk address specifier, can be one of

,

,

, , or\n"); fprintf(out, " \t

, to specify a physical partition number P, a starting sector\n"); fprintf(out, " \tnumber S, the number of sectors to follow L, or partition by \"name\"\n"); fprintf(out, "\n"); fprintf(out, "Example: %s prog_firehose_ddr.elf rawprogram*.xml patch*.xml\n", __progname); } int main(int argc, char **argv) { enum qdl_storage_type storage_type = QDL_STORAGE_UFS; struct sahara_image sahara_images[MAPPING_SZ] = {}; bool single_image = true; char *incdir = NULL; char *serial = NULL; const char *vip_generate_dir = NULL; const char *vip_table_path = NULL; int type; int ret; int opt; bool qdl_finalize_provisioning = false; bool allow_fusing = false; bool allow_missing = false; long out_chunk_size = 0; unsigned int slot = UINT_MAX; struct qdl_device *qdl = NULL; enum QDL_DEVICE_TYPE qdl_dev_type = QDL_DEVICE_USB; static struct option options[] = { {"debug", no_argument, 0, 'd'}, {"version", no_argument, 0, 'v'}, {"include", required_argument, 0, 'i'}, {"finalize-provisioning", no_argument, 0, 'l'}, {"out-chunk-size", required_argument, 0, 'u' }, {"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'}, {"dry-run", no_argument, 0, 'n'}, {"create-digests", required_argument, 0, 't'}, {"slot", required_argument, 0, 'T'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; while ((opt = getopt_long(argc, argv, "dvi:lu:S:D:s:fcnt:T:h", options, NULL)) != -1) { switch (opt) { case 'd': qdl_debug = true; break; case 'n': qdl_dev_type = QDL_DEVICE_SIM; break; case 't': vip_generate_dir = optarg; /* we also enforce dry-run mode */ qdl_dev_type = QDL_DEVICE_SIM; break; case 'v': print_version(); return 0; case 'f': allow_missing = true; break; case 'i': incdir = optarg; break; case 'l': qdl_finalize_provisioning = true; break; case 'c': allow_fusing = true; break; case 'u': out_chunk_size = strtol(optarg, NULL, 10); break; case 's': storage_type = decode_storage(optarg); break; case 'S': serial = optarg; break; case 'D': vip_table_path = optarg; break; case 'T': slot = (unsigned int)strtoul(optarg, NULL, 10); break; case 'h': print_usage(stdout); return 0; default: print_usage(stderr); return 1; } } /* at least 2 non optional args required */ if ((optind + 2) > argc) { print_usage(stderr); return 1; } qdl = qdl_init(qdl_dev_type); if (!qdl) { ret = -1; goto out_cleanup; } qdl->slot = slot; 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); if (vip_generate_dir) { ret = vip_gen_init(qdl, vip_generate_dir); if (ret) goto out_cleanup; } ux_init(); if (qdl_debug) print_version(); ret = decode_programmer(argv[optind++], sahara_images, &single_image); if (ret < 0) exit(1); do { type = detect_type(argv[optind]); if (type < 0 || type == QDL_FILE_UNKNOWN) errx(1, "failed to detect file type of %s\n", argv[optind]); switch (type) { case QDL_FILE_PATCH: ret = patch_load(argv[optind]); if (ret < 0) errx(1, "patch_load %s failed", argv[optind]); break; case QDL_FILE_PROGRAM: ret = program_load(argv[optind], storage_type == QDL_STORAGE_NAND, allow_missing, incdir); if (ret < 0) errx(1, "program_load %s failed", argv[optind]); if (!allow_fusing && program_is_sec_partition_flashed()) errx(1, "secdata partition to be programmed, which can lead to irreversible" " changes. Allow explicitly with --allow-fusing parameter"); break; case QDL_FILE_READ: ret = read_op_load(argv[optind], incdir); if (ret < 0) errx(1, "read_op_load %s failed", argv[optind]); break; case QDL_FILE_UFS: if (storage_type != QDL_STORAGE_UFS) errx(1, "attempting to load provisioning config when storage isn't \"ufs\""); ret = ufs_load(argv[optind], qdl_finalize_provisioning); if (ret < 0) errx(1, "ufs_load %s failed", argv[optind]); break; case QDL_CMD_READ: if (optind + 2 >= argc) errx(1, "read command missing arguments"); ret = read_cmd_add(argv[optind + 1], argv[optind + 2]); if (ret < 0) errx(1, "failed to add read command"); optind += 2; break; case QDL_CMD_WRITE: if (optind + 2 >= argc) errx(1, "write command missing arguments"); ret = program_cmd_add(argv[optind + 1], argv[optind + 2]); if (ret < 0) errx(1, "failed to add write command"); optind += 2; break; default: errx(1, "%s type not yet supported", argv[optind]); break; } } while (++optind < argc); ret = qdl_open(qdl, serial); if (ret) goto out_cleanup; qdl->storage_type = storage_type; ret = sahara_run(qdl, sahara_images, single_image, NULL, NULL); if (ret < 0) goto out_cleanup; if (ufs_need_provisioning()) ret = firehose_provision(qdl); else ret = firehose_run(qdl); if (ret < 0) goto out_cleanup; out_cleanup: if (vip_generate_dir) vip_gen_finalize(qdl); qdl_close(qdl); free_programs(); free_patches(); if (qdl->vip_data.state != VIP_DISABLED) vip_transfer_deinit(qdl); qdl_deinit(qdl); return !!ret; } qdl-2.4/qdl.h000066400000000000000000000070061512113701100130630ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ #ifndef __QDL_H__ #define __QDL_H__ #include #include "patch.h" #include "program.h" #include "read.h" #include #include "vip.h" #define container_of(ptr, typecast, member) ({ \ void *_ptr = (void *)(ptr); \ ((typeof(typecast) *)(_ptr - offsetof(typecast, member))); }) #define MIN(x, y) ({ \ __typeof__(x) _x = (x); \ __typeof__(y) _y = (y); \ _x < _y ? _x : _y; \ }) #define ROUND_UP(x, a) ({ \ __typeof__(x) _x = (x); \ __typeof__(a) _a = (a); \ (_x + _a - 1) & ~(_a - 1); \ }) #define __unused __attribute__((__unused__)) #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define ALIGN_UP(p, size) ({ \ __typeof__(size) _mask = (size) - 1; \ (__typeof__(p))(((uintptr_t)(p) + _mask) & ~_mask); \ }) #define MAPPING_SZ 64 enum QDL_DEVICE_TYPE { QDL_DEVICE_USB, QDL_DEVICE_SIM, }; enum qdl_storage_type { QDL_STORAGE_UNKNOWN, QDL_STORAGE_EMMC, QDL_STORAGE_NAND, QDL_STORAGE_UFS, QDL_STORAGE_NVME, QDL_STORAGE_SPINOR, }; struct qdl_device { enum QDL_DEVICE_TYPE dev_type; int fd; size_t max_payload_size; size_t sector_size; enum qdl_storage_type storage_type; unsigned int slot; 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, unsigned int timeout); 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); struct vip_transfer_data vip_data; }; struct sahara_image { const char *name; void *ptr; size_t len; }; struct libusb_device_handle; struct qdl_device *qdl_init(enum QDL_DEVICE_TYPE type); void qdl_deinit(struct qdl_device *qdl); int qdl_open(struct qdl_device *qdl, const char *serial); 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, unsigned int timeout); 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); int firehose_run(struct qdl_device *qdl); int firehose_provision(struct qdl_device *qdl); 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, const struct sahara_image *images, bool single_image, const char *ramdump_path, const char *ramdump_filter); int load_sahara_image(const char *filename, struct sahara_image *image); void print_hex_dump(const char *prefix, const void *buf, size_t len); unsigned int attr_as_unsigned(xmlNode *node, const char *attr, int *errors); const char *attr_as_string(xmlNode *node, const char *attr, int *errors); bool attr_as_bool(xmlNode *node, const char *attr, int *errors); void ux_init(void); void ux_err(const char *fmt, ...); void ux_info(const char *fmt, ...); void ux_log(const char *fmt, ...); void ux_debug(const char *fmt, ...); void ux_progress(const char *fmt, unsigned int value, unsigned int size, ...); void print_version(void); 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; #endif qdl-2.4/ramdump.c000066400000000000000000000031271512113701100137430ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause #include #include #include #include #include "qdl.h" #ifdef _WIN32 const char *__progname = "ramdump"; #endif bool qdl_debug; static void print_usage(FILE *out) { extern const char *__progname; fprintf(out, "%s [--debug] [-o ] [segment-filter,...]\n", __progname); } int main(int argc, char **argv) { struct qdl_device *qdl; qdl = qdl_init(QDL_DEVICE_USB); if (!qdl) return 1; char *ramdump_path = "."; char *filter = NULL; char *serial = NULL; int ret = 0; int opt; static struct option options[] = { {"debug", no_argument, 0, 'd'}, {"version", no_argument, 0, 'v'}, {"output", required_argument, 0, 'o'}, {"serial", required_argument, 0, 'S'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; while ((opt = getopt_long(argc, argv, "dvo:S:h", options, NULL)) != -1) { switch (opt) { case 'd': qdl_debug = true; break; case 'v': print_version(); ret = 0; goto out_cleanup; case 'o': ramdump_path = optarg; break; case 'S': serial = optarg; break; case 'h': print_usage(stdout); return 0; default: print_usage(stderr); return 1; } } if (optind < argc) filter = argv[optind++]; if (optind != argc) { print_usage(stderr); return 1; } if (qdl_debug) print_version(); ret = qdl_open(qdl, serial); if (ret) { ret = 1; goto out_cleanup; } ret = sahara_run(qdl, NULL, true, ramdump_path, filter); if (ret < 0) { ret = 1; goto out_cleanup; } out_cleanup: qdl_close(qdl); qdl_deinit(qdl); return ret; } qdl-2.4/read.c000066400000000000000000000070471512113701100132160ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2016-2017, Linaro Ltd. * All rights reserved. */ #include #include #include #include #include #include #include #include "list.h" #include "read.h" #include "qdl.h" #include "oscompat.h" #include "gpt.h" static struct list_head read_ops = LIST_INIT(read_ops); int read_op_load(const char *read_op_file, const char *incdir) { struct read_op *read_op; xmlNode *node; xmlNode *root; xmlDoc *doc; int errors; char tmp[PATH_MAX]; doc = xmlReadFile(read_op_file, NULL, 0); if (!doc) { ux_err("failed to parse read-type file \"%s\"\n", read_op_file); return -EINVAL; } root = xmlDocGetRootElement(doc); for (node = root->children; node ; node = node->next) { if (node->type != XML_ELEMENT_NODE) continue; if (xmlStrcmp(node->name, (xmlChar *)"read")) { ux_err("unrecognized tag \"%s\" in read-type file \"%s\", ignoring\n", node->name, read_op_file); continue; } errors = 0; read_op = calloc(1, sizeof(struct read_op)); read_op->sector_size = attr_as_unsigned(node, "SECTOR_SIZE_IN_BYTES", &errors); read_op->filename = attr_as_string(node, "filename", &errors); read_op->partition = attr_as_unsigned(node, "physical_partition_number", &errors); read_op->num_sectors = attr_as_unsigned(node, "num_partition_sectors", &errors); read_op->start_sector = attr_as_string(node, "start_sector", &errors); if (errors) { ux_err("errors while parsing read-type file \"%s\"\n", read_op_file); free(read_op); continue; } if (incdir) { snprintf(tmp, PATH_MAX, "%s/%s", incdir, read_op->filename); if (access(tmp, F_OK) != -1) read_op->filename = strdup(tmp); } list_add(&read_ops, &read_op->node); } xmlFreeDoc(doc); return 0; } int read_op_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, struct read_op *read_op, int fd)) { struct read_op *read_op; int ret; int fd; list_for_each_entry(read_op, &read_ops, node) { fd = open(read_op->filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); if (fd < 0) { ux_info("unable to open %s...\n", read_op->filename); return ret; } ret = apply(qdl, read_op, fd); close(fd); if (ret) return ret; } return 0; } 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, &gpt_partition); if (ret < 0) return ret; if (num_sectors == 0 && !gpt_partition) { ux_err("read command without length specifier not supported\n"); return -1; } read_op = calloc(1, sizeof(struct read_op)); read_op->sector_size = 0; read_op->filename = strdup(filename); read_op->partition = partition; read_op->num_sectors = num_sectors; sprintf(buf, "%u", start_sector); read_op->start_sector = strdup(buf); read_op->gpt_partition = gpt_partition; list_add(&read_ops, &read_op->node); 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; list_for_each_entry(read_op, &read_ops, node) { 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; } qdl-2.4/read.h000066400000000000000000000012101512113701100132050ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ #ifndef __READ_H__ #define __READ_H__ #include #include "list.h" struct qdl_device; struct read_op { unsigned int sector_size; const char *filename; int partition; unsigned int num_sectors; const char *start_sector; const char *gpt_partition; struct list_head node; }; int read_op_load(const char *read_op_file, const char *incdir); int read_op_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, struct read_op *read_op, int fd)); int read_cmd_add(const char *source, const char *filename); int read_resolve_gpt_deferrals(struct qdl_device *qdl); #endif qdl-2.4/sahara.c000066400000000000000000000305301512113701100135330ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2016-2017, Linaro Ltd. * All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qdl.h" #include "oscompat.h" #define SAHARA_HELLO_CMD 0x1 /* Min protocol version 1.0 */ #define SAHARA_HELLO_RESP_CMD 0x2 /* Min protocol version 1.0 */ #define SAHARA_READ_DATA_CMD 0x3 /* Min protocol version 1.0 */ #define SAHARA_END_OF_IMAGE_CMD 0x4 /* Min protocol version 1.0 */ #define SAHARA_DONE_CMD 0x5 /* Min protocol version 1.0 */ #define SAHARA_DONE_RESP_CMD 0x6 /* Min protocol version 1.0 */ #define SAHARA_RESET_CMD 0x7 /* Min protocol version 1.0 */ #define SAHARA_RESET_RESP_CMD 0x8 /* Min protocol version 1.0 */ #define SAHARA_MEM_DEBUG_CMD 0x9 /* Min protocol version 2.0 */ #define SAHARA_MEM_READ_CMD 0xa /* Min protocol version 2.0 */ #define SAHARA_CMD_READY_CMD 0xb /* Min protocol version 2.1 */ #define SAHARA_SWITCH_MODE_CMD 0xc /* Min protocol version 2.1 */ #define SAHARA_EXECUTE_CMD 0xd /* Min protocol version 2.1 */ #define SAHARA_EXECUTE_RESP_CMD 0xe /* Min protocol version 2.1 */ #define SAHARA_EXECUTE_DATA_CMD 0xf /* Min protocol version 2.1 */ #define SAHARA_MEM_DEBUG64_CMD 0x10 /* Min protocol version 2.5 */ #define SAHARA_MEM_READ64_CMD 0x11 /* Min protocol version 2.5 */ #define SAHARA_READ_DATA64_CMD 0x12 /* Min protocol version 2.8 */ #define SAHARA_RESET_STATE_CMD 0x13 /* Min protocol version 2.9 */ #define SAHARA_WRITE_DATA_CMD 0x14 /* Min protocol version 3.0 */ #define SAHARA_VERSION 2 #define SAHARA_SUCCESS 0 #define SAHARA_MODE_IMAGE_TX_PENDING 0x0 #define SAHARA_MODE_IMAGE_TX_COMPLETE 0x1 #define SAHARA_MODE_MEMORY_DEBUG 0x2 #define SAHARA_MODE_COMMAND 0x3 #define SAHARA_HELLO_LENGTH 0x30 #define SAHARA_READ_DATA_LENGTH 0x14 #define SAHARA_READ_DATA64_LENGTH 0x20 #define SAHARA_END_OF_IMAGE_LENGTH 0x10 #define SAHARA_MEM_READ64_LENGTH 0x18 #define SAHARA_MEM_DEBUG64_LENGTH 0x18 #define SAHARA_DONE_LENGTH 0x8 #define SAHARA_DONE_RESP_LENGTH 0xc #define SAHARA_RESET_LENGTH 0x8 #define DEBUG_BLOCK_SIZE (512u * 1024u) #define SAHARA_CMD_TIMEOUT_MS 1000 struct sahara_pkt { uint32_t cmd; uint32_t length; union { struct { uint32_t version; uint32_t compatible; uint32_t max_len; uint32_t mode; uint32_t reserved[6]; } hello_req; struct { uint32_t version; uint32_t compatible; uint32_t status; uint32_t mode; uint32_t reserved[6]; } hello_resp; struct { uint32_t image; uint32_t offset; uint32_t length; } read_req; struct { uint32_t image; uint32_t status; } eoi; struct { } done_req; struct { uint32_t status; } done_resp; struct { uint64_t addr; uint64_t length; } debug64_req; struct { uint64_t image; uint64_t offset; uint64_t length; } read64_req; }; }; struct sahara_debug_region64 { uint64_t type; uint64_t addr; uint64_t length; char region[20]; char filename[20]; }; static void sahara_send_reset(struct qdl_device *qdl) { struct sahara_pkt resp; resp.cmd = SAHARA_RESET_CMD; resp.length = SAHARA_RESET_LENGTH; qdl_write(qdl, &resp, resp.length, SAHARA_CMD_TIMEOUT_MS); } static void sahara_hello(struct qdl_device *qdl, struct sahara_pkt *pkt) { struct sahara_pkt resp = {}; assert(pkt->length == SAHARA_HELLO_LENGTH); ux_debug("HELLO version: 0x%x compatible: 0x%x max_len: %d mode: %d\n", pkt->hello_req.version, pkt->hello_req.compatible, pkt->hello_req.max_len, pkt->hello_req.mode); resp.cmd = SAHARA_HELLO_RESP_CMD; resp.length = SAHARA_HELLO_LENGTH; resp.hello_resp.version = SAHARA_VERSION; resp.hello_resp.compatible = 1; resp.hello_resp.status = SAHARA_SUCCESS; resp.hello_resp.mode = pkt->hello_req.mode; qdl_write(qdl, &resp, resp.length, SAHARA_CMD_TIMEOUT_MS); } static void sahara_read(struct qdl_device *qdl, struct sahara_pkt *pkt, const struct sahara_image *images, bool single_image) { const struct sahara_image *image; unsigned int image_idx; size_t offset; size_t len; int ret; assert(pkt->length == SAHARA_READ_DATA_LENGTH); ux_debug("READ image: %d offset: 0x%x length: 0x%x\n", pkt->read_req.image, pkt->read_req.offset, pkt->read_req.length); if (single_image) image_idx = 0; else image_idx = pkt->read_req.image; if (image_idx >= MAPPING_SZ || !images[image_idx].ptr) { ux_err("device requested invalid image: %u\n", image_idx); sahara_send_reset(qdl); return; } offset = pkt->read_req.offset; len = pkt->read_req.length; image = &images[image_idx]; if (offset > image->len || offset + len > image->len) { ux_err("device requested invalid range of image %d\n", image_idx); return; } ret = qdl_write(qdl, image->ptr + offset, len, SAHARA_CMD_TIMEOUT_MS); if (ret < 0 || ((size_t)ret != len)) err(1, "failed to write %zu bytes to sahara", len); } static void sahara_read64(struct qdl_device *qdl, struct sahara_pkt *pkt, const struct sahara_image *images, bool single_image) { const struct sahara_image *image; unsigned int image_idx; size_t offset; size_t len; int ret; assert(pkt->length == SAHARA_READ_DATA64_LENGTH); ux_debug("READ64 image: %" PRId64 " offset: 0x%" PRIx64 " length: 0x%" PRIx64 "\n", pkt->read64_req.image, pkt->read64_req.offset, pkt->read64_req.length); if (single_image) image_idx = 0; else image_idx = pkt->read64_req.image; if (image_idx >= MAPPING_SZ || !images[image_idx].ptr) { ux_err("device requested invalid image: %u\n", image_idx); sahara_send_reset(qdl); return; } offset = pkt->read64_req.offset; len = pkt->read64_req.length; image = &images[image_idx]; if (offset > image->len || offset + len > image->len) { ux_err("device requested invalid range of image %d\n", image_idx); return; } ret = qdl_write(qdl, image->ptr + offset, len, SAHARA_CMD_TIMEOUT_MS); if (ret < 0 || ((size_t)ret != len)) err(1, "failed to write %zu bytes to sahara", len); } static void sahara_eoi(struct qdl_device *qdl, struct sahara_pkt *pkt) { struct sahara_pkt done; assert(pkt->length == SAHARA_END_OF_IMAGE_LENGTH); ux_debug("END OF IMAGE image: %d status: %d\n", pkt->eoi.image, pkt->eoi.status); if (pkt->eoi.status != 0) { ux_err("received non-successful end-of-image result\n"); return; } done.cmd = SAHARA_DONE_CMD; done.length = SAHARA_DONE_LENGTH; qdl_write(qdl, &done, done.length, SAHARA_CMD_TIMEOUT_MS); } static int sahara_done(struct qdl_device *qdl __unused, struct sahara_pkt *pkt) { assert(pkt->length == SAHARA_DONE_RESP_LENGTH); ux_debug("DONE status: %d\n", pkt->done_resp.status); // 0 == PENDING, 1 == COMPLETE. Device expects more images if // PENDING is set in status. return pkt->done_resp.status; } static ssize_t sahara_debug64_one(struct qdl_device *qdl, struct sahara_debug_region64 region, const char *ramdump_path) { struct sahara_pkt read_req; uint64_t remain; size_t offset, buf_offset; size_t chunk; size_t written; ssize_t n; void *buf; int fd; buf = malloc(DEBUG_BLOCK_SIZE); if (!buf) return -1; char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", ramdump_path, region.filename); fd = open(path, O_WRONLY | O_CREAT | O_BINARY, 0644); if (fd < 0) { warn("failed to open \"%s\"", region.filename); free(buf); return -1; } chunk = 0; while (chunk < region.length) { remain = MIN((uint64_t)(region.length - chunk), DEBUG_BLOCK_SIZE); read_req.cmd = SAHARA_MEM_READ64_CMD; read_req.length = SAHARA_MEM_READ64_LENGTH; read_req.debug64_req.addr = region.addr + chunk; read_req.debug64_req.length = remain; n = qdl_write(qdl, &read_req, read_req.length, SAHARA_CMD_TIMEOUT_MS); if (n < 0) break; offset = 0; while (offset < remain) { buf_offset = 0; n = qdl_read(qdl, buf, DEBUG_BLOCK_SIZE, 30000); if (n < 0) { warn("failed to read ramdump chunk"); goto out; } while (buf_offset < (size_t)n) { written = write(fd, buf + buf_offset, n - buf_offset); if (written <= 0) { warn("failed to write ramdump chunk to \"%s\"", region.filename); goto out; } buf_offset += written; } offset += buf_offset; } qdl_read(qdl, buf, DEBUG_BLOCK_SIZE, 10); chunk += DEBUG_BLOCK_SIZE; } out: close(fd); free(buf); return 0; } // simple pattern matching function supporting * and ? bool pattern_match(const char *pattern, const char *string) { if (*pattern == '\0' && *string == '\0') return true; if (*pattern == '*') return pattern_match(pattern + 1, string) || (*string != '\0' && pattern_match(pattern, string + 1)); if (*pattern == '?') return (*string != '\0') && pattern_match(pattern + 1, string + 1); if (*pattern == *string) return pattern_match(pattern + 1, string + 1); return false; } static bool sahara_debug64_filter(const char *filename, const char *filter) { bool anymatch = false; char *ptr; char *tmp; char *s; if (!filter) return false; tmp = strdup(filter); for (s = strtok_r(tmp, ",", &ptr); s; s = strtok_r(NULL, ",", &ptr)) { if (pattern_match(s, filename)) { anymatch = true; break; } } free(tmp); return !anymatch; } static void sahara_debug64(struct qdl_device *qdl, struct sahara_pkt *pkt, const char *ramdump_path, const char *filter) { struct sahara_debug_region64 *table; struct sahara_pkt read_req; ssize_t n; size_t i; assert(pkt->length == SAHARA_MEM_DEBUG64_LENGTH); ux_debug("DEBUG64 address: 0x%" PRIx64 " length: 0x%" PRIx64 "\n", pkt->debug64_req.addr, pkt->debug64_req.length); read_req.cmd = SAHARA_MEM_READ64_CMD; read_req.length = SAHARA_MEM_READ64_LENGTH; read_req.debug64_req.addr = pkt->debug64_req.addr; read_req.debug64_req.length = pkt->debug64_req.length; n = qdl_write(qdl, &read_req, read_req.length, SAHARA_CMD_TIMEOUT_MS); if (n < 0) return; table = malloc(read_req.debug64_req.length); n = qdl_read(qdl, table, pkt->debug64_req.length, SAHARA_CMD_TIMEOUT_MS); if (n < 0) return; for (i = 0; i < pkt->debug64_req.length / sizeof(table[0]); i++) { if (sahara_debug64_filter(table[i].filename, filter)) continue; ux_debug("%-2d: type 0x%" PRIx64 " address: 0x%" PRIx64 " length: 0x%" PRIx64 " region: %s filename: %s\n", i, table[i].type, table[i].addr, table[i].length, table[i].region, table[i].filename); n = sahara_debug64_one(qdl, table[i], ramdump_path); if (n < 0) break; } free(table); sahara_send_reset(qdl); } static void sahara_debug_list_images(const struct sahara_image *images, bool single_image) { int i; if (single_image) ux_debug("Sahara image id in read requests will be ignored\n"); ux_debug("Sahara images:\n"); for (i = 0; i < MAPPING_SZ; i++) { if (images[i].ptr) ux_debug(" %2d: %s\n", i, images[i].name ? : "(unknown)"); } } int sahara_run(struct qdl_device *qdl, const struct sahara_image *images, bool single_image, const char *ramdump_path, const char *ramdump_filter) { struct sahara_pkt *pkt; char buf[4096]; char tmp[32]; bool done = false; int n; sahara_debug_list_images(images, single_image); /* * Don't need to do anything in simulation mode with Sahara, * we care only about Firehose protocol */ if (qdl->dev_type == QDL_DEVICE_SIM) return 0; while (!done) { n = qdl_read(qdl, buf, sizeof(buf), SAHARA_CMD_TIMEOUT_MS); if (n < 0) { ux_err("failed to read sahara request from device\n"); break; } pkt = (struct sahara_pkt *)buf; if ((uint32_t)n != pkt->length) { ux_err("request length not matching received request\n"); return -EINVAL; } switch (pkt->cmd) { case SAHARA_HELLO_CMD: sahara_hello(qdl, pkt); break; case SAHARA_READ_DATA_CMD: sahara_read(qdl, pkt, images, single_image); break; case SAHARA_END_OF_IMAGE_CMD: sahara_eoi(qdl, pkt); break; case SAHARA_DONE_RESP_CMD: done = sahara_done(qdl, pkt); /* E.g MSM8916 EDL reports done = 0 here */ if (single_image) done = true; break; case SAHARA_MEM_DEBUG64_CMD: sahara_debug64(qdl, pkt, ramdump_path, ramdump_filter); break; case SAHARA_READ_DATA64_CMD: sahara_read64(qdl, pkt, images, single_image); break; case SAHARA_RESET_RESP_CMD: assert(pkt->length == SAHARA_RESET_LENGTH); if (ramdump_path) done = true; break; default: sprintf(tmp, "CMD%x", pkt->cmd); print_hex_dump(tmp, buf, n); break; } } return done ? 0 : -1; } qdl-2.4/sha2.c000066400000000000000000000304421512113701100131330ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* $OpenBSD: sha2.c,v 1.28 2019/07/23 12:35:22 dtucker Exp $ */ /* * FILE: sha2.c * AUTHOR: Aaron D. Gifford * * Copyright (c) 2000-2001, Aaron D. Gifford * All rights reserved. * * $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $ */ #include #include #include "sha2.h" /* * UNROLLED TRANSFORM LOOP NOTE: * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform * loop version for the hash transform rounds (defined using macros * later in this file). Either define on the command line, for example: * * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c * * or define below: * * #define SHA2_UNROLL_TRANSFORM * */ #ifndef SHA2_SMALL #if defined(__amd64__) || defined(__i386__) #define SHA2_UNROLL_TRANSFORM #endif #endif /*** SHA-224/256/384/512 Machine Architecture Definitions *****************/ /* * BYTE_ORDER NOTE: * * Please make sure that your system defines BYTE_ORDER. If your * architecture is little-endian, make sure it also defines * LITTLE_ENDIAN and that the two (BYTE_ORDER and LITTLE_ENDIAN) are * equivalent. * * If your system does not define the above, then you can do so by * hand like this: * * #define LITTLE_ENDIAN 1234 * #define BIG_ENDIAN 4321 * * And for little-endian machines, add: * * #define BYTE_ORDER LITTLE_ENDIAN * * Or for big-endian machines: * * #define BYTE_ORDER BIG_ENDIAN * * The FreeBSD machine this was written on defines BYTE_ORDER * appropriately by including (which in turn includes * where the appropriate definitions are actually * made). */ #if defined(__MINGW32__) || defined(__MINGW64__) #include #endif #if !defined(BYTE_ORDER) || (BYTE_ORDER != LITTLE_ENDIAN && BYTE_ORDER != BIG_ENDIAN) #error Define BYTE_ORDER to be equal to either LITTLE_ENDIAN or BIG_ENDIAN #endif /*** SHA-224/256/384/512 Various Length Definitions ***********************/ /* NOTE: Most of these are in sha2.h */ #define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8) /*** ENDIAN SPECIFIC COPY MACROS **************************************/ #define BE_8_TO_32(dst, cp) do { \ (dst) = (uint32_t)(cp)[3] | ((uint32_t)(cp)[2] << 8) | \ ((uint32_t)(cp)[1] << 16) | ((uint32_t)(cp)[0] << 24); \ } while(0) #define BE_8_TO_64(dst, cp) do { \ (dst) = (uint64_t)(cp)[7] | ((uint64_t)(cp)[6] << 8) | \ ((uint64_t)(cp)[5] << 16) | ((uint64_t)(cp)[4] << 24) | \ ((uint64_t)(cp)[3] << 32) | ((uint64_t)(cp)[2] << 40) | \ ((uint64_t)(cp)[1] << 48) | ((uint64_t)(cp)[0] << 56); \ } while (0) #define BE_64_TO_8(cp, src) do { \ (cp)[0] = (src) >> 56; \ (cp)[1] = (src) >> 48; \ (cp)[2] = (src) >> 40; \ (cp)[3] = (src) >> 32; \ (cp)[4] = (src) >> 24; \ (cp)[5] = (src) >> 16; \ (cp)[6] = (src) >> 8; \ (cp)[7] = (src); \ } while (0) #define BE_32_TO_8(cp, src) do { \ (cp)[0] = (src) >> 24; \ (cp)[1] = (src) >> 16; \ (cp)[2] = (src) >> 8; \ (cp)[3] = (src); \ } while (0) /* * Macro for incrementally adding the unsigned 64-bit integer n to the * unsigned 128-bit integer (represented using a two-element array of * 64-bit words): */ #define ADDINC128(w,n) do { \ (w)[0] += (uint64_t)(n); \ if ((w)[0] < (n)) { \ (w)[1]++; \ } \ } while (0) /*** THE SIX LOGICAL FUNCTIONS ****************************************/ /* * Bit shifting and rotation (used by the six SHA-XYZ logical functions: * * NOTE: The naming of R and S appears backwards here (R is a SHIFT and * S is a ROTATION) because the SHA-224/256/384/512 description document * (see http://csrc.nist.gov/cryptval/shs/sha256-384-512.pdf) uses this * same "backwards" definition. */ /* Shift-right (used in SHA-224, SHA-256, SHA-384, and SHA-512): */ #define R(b,x) ((x) >> (b)) /* 32-bit Rotate-right (used in SHA-224 and SHA-256): */ #define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b)))) /* Two of six logical functions used in SHA-224, SHA-256, SHA-384, and SHA-512: */ #define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) #define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) /* Four of six logical functions used in SHA-224 and SHA-256: */ #define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x))) #define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x))) #define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x))) #define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x))) /*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/ /* Hash constant words K for SHA-224 and SHA-256: */ static const uint32_t K256[64] = { 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL }; /* Initial hash value H for SHA-256: */ static const uint32_t sha256_initial_hash_value[8] = { 0x6a09e667UL, 0xbb67ae85UL, 0x3c6ef372UL, 0xa54ff53aUL, 0x510e527fUL, 0x9b05688cUL, 0x1f83d9abUL, 0x5be0cd19UL }; /*** SHA-256: *********************************************************/ void SHA256Init(SHA2_CTX *context) { memcpy(context->state.st32, sha256_initial_hash_value, sizeof(sha256_initial_hash_value)); memset(context->buffer, 0, sizeof(context->buffer)); context->bitcount[0] = 0; } #ifdef SHA2_UNROLL_TRANSFORM /* Unrolled SHA-256 round macros: */ #define ROUND256_0_TO_15(a,b,c,d,e,f,g,h) do { \ BE_8_TO_32(W256[j], data); \ data += 4; \ T1 = (h) + Sigma1_256((e)) + Ch((e), (f), (g)) + K256[j] + W256[j]; \ (d) += T1; \ (h) = T1 + Sigma0_256((a)) + Maj((a), (b), (c)); \ j++; \ } while(0) #define ROUND256(a,b,c,d,e,f,g,h) do { \ s0 = W256[(j+1)&0x0f]; \ s0 = sigma0_256(s0); \ s1 = W256[(j+14)&0x0f]; \ s1 = sigma1_256(s1); \ T1 = (h) + Sigma1_256((e)) + Ch((e), (f), (g)) + K256[j] + \ (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); \ (d) += T1; \ (h) = T1 + Sigma0_256((a)) + Maj((a), (b), (c)); \ j++; \ } while(0) void SHA256Transform(uint32_t state[8], const uint8_t data[SHA256_BLOCK_LENGTH]) { uint32_t a, b, c, d, e, f, g, h, s0, s1; uint32_t T1, W256[16]; int j; /* Initialize registers with the prev. intermediate value */ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; f = state[5]; g = state[6]; h = state[7]; j = 0; do { /* Rounds 0 to 15 (unrolled): */ ROUND256_0_TO_15(a,b,c,d,e,f,g,h); ROUND256_0_TO_15(h,a,b,c,d,e,f,g); ROUND256_0_TO_15(g,h,a,b,c,d,e,f); ROUND256_0_TO_15(f,g,h,a,b,c,d,e); ROUND256_0_TO_15(e,f,g,h,a,b,c,d); ROUND256_0_TO_15(d,e,f,g,h,a,b,c); ROUND256_0_TO_15(c,d,e,f,g,h,a,b); ROUND256_0_TO_15(b,c,d,e,f,g,h,a); } while (j < 16); /* Now for the remaining rounds up to 63: */ do { ROUND256(a,b,c,d,e,f,g,h); ROUND256(h,a,b,c,d,e,f,g); ROUND256(g,h,a,b,c,d,e,f); ROUND256(f,g,h,a,b,c,d,e); ROUND256(e,f,g,h,a,b,c,d); ROUND256(d,e,f,g,h,a,b,c); ROUND256(c,d,e,f,g,h,a,b); ROUND256(b,c,d,e,f,g,h,a); } while (j < 64); /* Compute the current intermediate hash value */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; state[5] += f; state[6] += g; state[7] += h; /* Clean up */ a = b = c = d = e = f = g = h = T1 = 0; } #else /* SHA2_UNROLL_TRANSFORM */ void SHA256Transform(uint32_t state[8], const uint8_t data[SHA256_BLOCK_LENGTH]) { uint32_t a, b, c, d, e, f, g, h, s0, s1; uint32_t T1, T2, W256[16]; int j; /* Initialize registers with the prev. intermediate value */ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; f = state[5]; g = state[6]; h = state[7]; j = 0; do { BE_8_TO_32(W256[j], data); data += 4; /* Apply the SHA-256 compression function to update a..h */ T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + W256[j]; T2 = Sigma0_256(a) + Maj(a, b, c); h = g; g = f; f = e; e = d + T1; d = c; c = b; b = a; a = T1 + T2; j++; } while (j < 16); do { /* Part of the message block expansion: */ s0 = W256[(j+1)&0x0f]; s0 = sigma0_256(s0); s1 = W256[(j+14)&0x0f]; s1 = sigma1_256(s1); /* Apply the SHA-256 compression function to update a..h */ T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); T2 = Sigma0_256(a) + Maj(a, b, c); h = g; g = f; f = e; e = d + T1; d = c; c = b; b = a; a = T1 + T2; j++; } while (j < 64); /* Compute the current intermediate hash value */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; state[5] += f; state[6] += g; state[7] += h; /* Clean up */ a = b = c = d = e = f = g = h = T1 = T2 = 0; } #endif /* SHA2_UNROLL_TRANSFORM */ void SHA256Update(SHA2_CTX *context, const uint8_t *data, size_t len) { uint64_t freespace, usedspace; /* Calling with no data is valid (we do nothing) */ if (len == 0) return; usedspace = (context->bitcount[0] >> 3) % SHA256_BLOCK_LENGTH; if (usedspace > 0) { /* Calculate how much free space is available in the buffer */ freespace = SHA256_BLOCK_LENGTH - usedspace; if (len >= freespace) { /* Fill the buffer completely and process it */ memcpy(&context->buffer[usedspace], data, freespace); context->bitcount[0] += freespace << 3; len -= freespace; data += freespace; SHA256Transform(context->state.st32, context->buffer); } else { /* The buffer is not yet full */ memcpy(&context->buffer[usedspace], data, len); context->bitcount[0] += (uint64_t)len << 3; /* Clean up: */ usedspace = freespace = 0; return; } } while (len >= SHA256_BLOCK_LENGTH) { /* Process as many complete blocks as we can */ SHA256Transform(context->state.st32, data); context->bitcount[0] += SHA256_BLOCK_LENGTH << 3; len -= SHA256_BLOCK_LENGTH; data += SHA256_BLOCK_LENGTH; } if (len > 0) { /* There's left-overs, so save 'em */ memcpy(context->buffer, data, len); context->bitcount[0] += len << 3; } /* Clean up: */ usedspace = freespace = 0; } void SHA256Pad(SHA2_CTX *context) { unsigned int usedspace; usedspace = (context->bitcount[0] >> 3) % SHA256_BLOCK_LENGTH; if (usedspace > 0) { /* Begin padding with a 1 bit: */ context->buffer[usedspace++] = 0x80; if (usedspace <= SHA256_SHORT_BLOCK_LENGTH) { /* Set-up for the last transform: */ memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace); } else { if (usedspace < SHA256_BLOCK_LENGTH) { memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace); } /* Do second-to-last transform: */ SHA256Transform(context->state.st32, context->buffer); /* Prepare for last transform: */ memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH); } } else { /* Set-up for the last transform: */ memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH); /* Begin padding with a 1 bit: */ *context->buffer = 0x80; } /* Store the length of input data (in bits) in big endian format: */ BE_64_TO_8(&context->buffer[SHA256_SHORT_BLOCK_LENGTH], context->bitcount[0]); /* Final transform: */ SHA256Transform(context->state.st32, context->buffer); /* Clean up: */ usedspace = 0; } void SHA256Final(uint8_t digest[SHA256_DIGEST_LENGTH], SHA2_CTX *context) { SHA2_CTX *volatile const contextv = context; SHA256Pad(context); #if BYTE_ORDER == LITTLE_ENDIAN int i; /* Convert TO host byte order */ for (i = 0; i < 8; i++) BE_32_TO_8(digest + i * 4, context->state.st32[i]); #else memcpy(digest, context->state.st32, SHA256_DIGEST_LENGTH); #endif memset(contextv, 0, sizeof(*contextv)); }qdl-2.4/sha2.h000066400000000000000000000021511512113701100131340ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* $OpenBSD: sha2.h,v 1.10 2016/09/03 17:00:29 tedu Exp $ */ /* * FILE: sha2.h * AUTHOR: Aaron D. Gifford * * Copyright (c) 2000-2001, Aaron D. Gifford * All rights reserved. * * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $ */ #ifndef __SHA2_H__ #define __SHA2_H__ #include "stdint.h" /*** SHA-256/384/512 Various Length Definitions ***********************/ #define SHA256_BLOCK_LENGTH 64 #define SHA256_DIGEST_LENGTH 32 #define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1) /*** SHA-224/256/384/512 Context Structure *******************************/ typedef struct _SHA2_CTX { union { uint32_t st32[8]; uint64_t st64[8]; } state; uint64_t bitcount[2]; uint8_t buffer[SHA256_BLOCK_LENGTH]; } SHA2_CTX; void SHA256Init(SHA2_CTX *); void SHA256Transform(uint32_t state[8], const uint8_t [SHA256_BLOCK_LENGTH]); void SHA256Update(SHA2_CTX *, const uint8_t *, size_t); void SHA256Pad(SHA2_CTX *); void SHA256Final(uint8_t [SHA256_DIGEST_LENGTH], SHA2_CTX *); char *SHA256End(SHA2_CTX *, char *); #endif /* __SHA2_H__ */qdl-2.4/sim.c000066400000000000000000000037401512113701100130670ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include "sim.h" struct qdl_device_sim { struct qdl_device base; struct vip_table_generator *vip_gen; bool create_digests; }; static int sim_open(struct qdl_device *qdl __unused, const char *serial __unused) { ux_info("This is a dry-run execution of QDL. No actual flashing has been performed\n"); return 0; } static void sim_close(struct qdl_device *qdl __unused) {} static int sim_read(struct qdl_device *qdl __unused, void *buf __unused, size_t len, unsigned int timeout __unused) { return len; } static int sim_write(struct qdl_device *qdl __unused, const void *buf __unused, size_t len, unsigned int timeout __unused) { return len; } static void sim_set_out_chunk_size(struct qdl_device *qdl __unused, long size __unused) {} struct qdl_device *sim_init(void) { struct qdl_device *qdl = malloc(sizeof(struct qdl_device_sim)); if (!qdl) return NULL; memset(qdl, 0, sizeof(struct qdl_device_sim)); qdl->dev_type = QDL_DEVICE_SIM; qdl->open = sim_open; qdl->read = sim_read; 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; } struct vip_table_generator *sim_get_vip_generator(struct qdl_device *qdl) { struct qdl_device_sim *qdl_sim; if (qdl->dev_type != QDL_DEVICE_SIM) return NULL; qdl_sim = container_of(qdl, struct qdl_device_sim, base); if (!qdl_sim->create_digests) return NULL; return qdl_sim->vip_gen; } bool sim_set_digest_generation(bool create_digests, struct qdl_device *qdl, struct vip_table_generator *vip_gen) { struct qdl_device_sim *qdl_sim; if (qdl->dev_type != QDL_DEVICE_SIM) return false; qdl_sim = container_of(qdl, struct qdl_device_sim, base); qdl_sim->create_digests = create_digests; qdl_sim->vip_gen = vip_gen; return true; } qdl-2.4/sim.h000066400000000000000000000006471512113701100130770ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. */ #ifndef __SIM_H__ #define __SIM_H__ #include "qdl.h" #include "vip.h" struct vip_table_generator *sim_get_vip_generator(struct qdl_device *qdl); bool sim_set_digest_generation(bool create_digests, struct qdl_device *qdl, struct vip_table_generator *vip_gen); #endif /* __SIM_H__ */ qdl-2.4/sparse.c000066400000000000000000000057611512113701100136010ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2025, Maksim Paimushkin * All rights reserved. */ #define _FILE_OFFSET_BITS 64 #ifdef _WIN32 #include #else #include #endif #include #include #include #include #include #include #include #include #include "sparse.h" #include "qdl.h" int sparse_header_parse(int fd, sparse_header_t *sparse_header) { lseek(fd, 0, SEEK_SET); if (read(fd, sparse_header, sizeof(sparse_header_t)) != sizeof(sparse_header_t)) { ux_err("[SPARSE] Unable to read sparse header\n"); return -EINVAL; } if (sparse_header->magic != SPARSE_HEADER_MAGIC) { ux_err("[SPARSE] Invalid magic in sparse header\n"); return -EINVAL; } if (sparse_header->major_version != SPARSE_HEADER_MAJOR_VER) { ux_err("[SPARSE] Invalid major version in sparse header\n"); return -EINVAL; } if (sparse_header->minor_version != SPARSE_HEADER_MINOR_VER) { ux_err("[SPARSE] Invalid minor version in sparse header\n"); return -EINVAL; } if (sparse_header->file_hdr_sz > sizeof(sparse_header_t)) lseek(fd, sparse_header->file_hdr_sz - sizeof(sparse_header_t), SEEK_CUR); return 0; } int sparse_chunk_header_parse(int fd, sparse_header_t *sparse_header, uint64_t *chunk_size, uint32_t *value, off_t *offset) { chunk_header_t chunk_header; uint32_t fill_value = 0; unsigned int type; *chunk_size = 0; *value = 0; if (read(fd, &chunk_header, sizeof(chunk_header_t)) != sizeof(chunk_header_t)) { ux_err("[SPARSE] Unable to read sparse chunk header\n"); return -EINVAL; } if (sparse_header->chunk_hdr_sz > sizeof(chunk_header_t)) lseek(fd, sparse_header->chunk_hdr_sz - sizeof(chunk_header_t), SEEK_CUR); type = chunk_header.chunk_type; *chunk_size = (uint64_t)chunk_header.chunk_sz * sparse_header->blk_sz; switch (type) { case CHUNK_TYPE_RAW: if (chunk_header.total_sz != (sparse_header->chunk_hdr_sz + *chunk_size)) { ux_err("[SPARSE] Bogus chunk size, type Raw\n"); return -EINVAL; } /* Save the current file offset in the 'value' variable */ *offset = lseek(fd, 0, SEEK_CUR); /* Move the file cursor forward by the size of the chunk */ lseek(fd, *chunk_size, SEEK_CUR); break; case CHUNK_TYPE_DONT_CARE: if (chunk_header.total_sz != sparse_header->chunk_hdr_sz) { ux_err("[SPARSE] Bogus chunk size, type Don't Care\n"); return -EINVAL; } break; case CHUNK_TYPE_FILL: if (chunk_header.total_sz != (sparse_header->chunk_hdr_sz + sizeof(fill_value))) { ux_err("[SPARSE] Bogus chunk size, type Fill\n"); return -EINVAL; } if (read(fd, &fill_value, sizeof(fill_value)) != sizeof(fill_value)) { ux_err("[SPARSE] Unable to read fill value\n"); return -EINVAL; } /* Save the current fill value in the 'value' variable */ *value = fill_value; break; default: ux_err("[SPARSE] Unknown chunk type: %#x\n", type); return -EINVAL; } return type; } qdl-2.4/sparse.h000066400000000000000000000040751512113701100136030ustar00rootroot00000000000000/* SPDX-License-Identifier: Apache-2.0 */ /* * Copyright (C) 2010 The Android Open Source Project */ #ifndef __SPARSE_H__ #define __SPARSE_H__ #include #include typedef struct __attribute__((__packed__)) sparse_header { /* 0xed26ff3a */ uint32_t magic; /* (0x1) - reject images with higher major versions */ uint16_t major_version; /* (0x0) - allow images with higer minor versions */ uint16_t minor_version; /* 28 bytes for first revision of the file format */ uint16_t file_hdr_sz; /* 12 bytes for first revision of the file format */ uint16_t chunk_hdr_sz; /* block size in bytes, must be a multiple of 4 (4096) */ uint32_t blk_sz; /* total blocks in the non-sparse output image */ uint32_t total_blks; /* total chunks in the sparse input image */ uint32_t total_chunks; /* * CRC32 checksum of the original data, counting "don't care" * as 0. Standard 802.3 polynomial, use a Public Domain * table implementation */ uint32_t image_checksum; } sparse_header_t; #define SPARSE_HEADER_MAGIC 0xed26ff3a #define SPARSE_HEADER_MAJOR_VER 0x0001 #define SPARSE_HEADER_MINOR_VER 0x0000 typedef struct __attribute__((__packed__)) chunk_header { uint16_t chunk_type; /* 0xCAC1 -> raw; 0xCAC2 -> fill; 0xCAC3 -> don't care */ uint16_t reserved1; uint32_t chunk_sz; /* in blocks in output image */ uint32_t total_sz; /* in bytes of chunk input file including chunk header and data */ } chunk_header_t; #define CHUNK_TYPE_RAW 0xCAC1 #define CHUNK_TYPE_FILL 0xCAC2 #define CHUNK_TYPE_DONT_CARE 0xCAC3 /* * Parses the sparse image header from the file descriptor. * Returns 0 on success, or an error code otherwise. */ int sparse_header_parse(int fd, sparse_header_t *sparse_header); /* * Parses the sparse image chunk header from the file descriptor. * Sets the chunk size, and value or offset based on the parsed data. * Returns the chunk type on success, or an error code otherwise. */ int sparse_chunk_header_parse(int fd, sparse_header_t *sparse_header, uint64_t *chunk_size, uint32_t *value, off_t *offset); #endif qdl-2.4/tests/000077500000000000000000000000001512113701100132715ustar00rootroot00000000000000qdl-2.4/tests/data/000077500000000000000000000000001512113701100142025ustar00rootroot00000000000000qdl-2.4/tests/data/generate_flat_build.sh000077500000000000000000000011641512113701100205220ustar00rootroot00000000000000#!/bin/sh # SPDX-License-Identifier: BSD-3-Clause SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" create_file_with_size() { filename="$1" size_kbytes="$2" dd if=/dev/zero of="$SCRIPT_PATH/$filename" bs=1024 count="$size_kbytes" status=none } create_file_with_size prog_firehose_ddr.elf 20 create_file_with_size efi.bin 524288 create_file_with_size gpt_backup0.bin 20 create_file_with_size gpt_backup1.bin 20 create_file_with_size gpt_main0.bin 24 create_file_with_size gpt_main1.bin 24 create_file_with_size rootfs.img 512000 create_file_with_size xbl_config.elf 320 create_file_with_size xbl.elf 800 qdl-2.4/tests/data/patch0.xml000066400000000000000000000141471512113701100161120ustar00rootroot00000000000000 qdl-2.4/tests/data/patch1.xml000066400000000000000000000141671512113701100161150ustar00rootroot00000000000000 qdl-2.4/tests/data/rawprogram0.xml000066400000000000000000000024361512113701100171720ustar00rootroot00000000000000 qdl-2.4/tests/data/rawprogram1.xml000066400000000000000000000041541512113701100171720ustar00rootroot00000000000000 qdl-2.4/tests/run_tests.sh000077500000000000000000000012421512113701100156550ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: BSD-3-Clause set -e SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" FLAT_BUILD_PATH=$SCRIPT_PATH/data echo "####### Generate a FLAT build" $FLAT_BUILD_PATH/generate_flat_build.sh echo "####### Run QDL tests" cd $SCRIPT_PATH for t in test_*.sh; do echo "###### Run $t" bash $t if [ $? -eq 0 ]; then echo "####### Test $t: OK" else echo "####### Test $t: FAIL" failed=1 fi done echo "####### Housekeeping" rm -f ${FLAT_BUILD_PATH}/*.bin ${FLAT_BUILD_PATH}/*.img rm -f ${FLAT_BUILD_PATH}/*.elf if [ "$failed" == "1" ]; then echo "####### Some test failed" exit 1 fi echo "####### All tests passed" qdl-2.4/tests/test_vip_generation.sh000077500000000000000000000022231512113701100176770ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: BSD-3-Clause set -e SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" FLAT_BUILD=${SCRIPT_PATH}/data REP_ROOT=${SCRIPT_PATH}/.. VIP_PATH=${FLAT_BUILD}/vip EXPECTED_DIGEST="a05e1124edbe34dc504a327544fb66572591353dc3fa25e6e7eafbe4803e63e0" VIP_TABLE_FILE=${VIP_PATH}/DigestsToSign.bin mkdir -p $VIP_PATH cd $FLAT_BUILD ${REP_ROOT}/qdl --dry-run --create-digests=${VIP_PATH} \ prog_firehose_ddr.elf rawprogram*.xml patch*.xml if command -v sha256sum >/dev/null 2>&1; then shacmd="sha256sum" elif command -v shasum >/dev/null 2>&1; then shacmd="shasum -a 256" else echo "No SHA-256 checksum tool found (need 'sha256sum' or 'shasum')" exit 1 fi actual_digest=`${shacmd} "${VIP_TABLE_FILE}" | cut -d ' ' -f1` if [ "$actual_digest" != "${EXPECTED_DIGEST}" ]; then echo "Expected SHA256 digest of ${VIP_TABLE_FILE} file is ${EXPECTED_DIGEST}" echo "Calculated SHA256 digest of ${VIP_TABLE_FILE} file is $actual_digest" echo "VIP table folder contents:" ls -la ${VIP_PATH} exit 1 fi echo "VIP tables are generated successfully and validated" rm -r ${VIP_PATH}/*.bin rmdir ${VIP_PATH} qdl-2.4/ufs.c000066400000000000000000000200001512113701100130600ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2018, The Linux Foundation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include "ufs.h" #include "qdl.h" #include "list.h" #include "patch.h" struct ufs_common *ufs_common_p; struct ufs_epilogue *ufs_epilogue_p; static struct list_head ufs_bodies = LIST_INIT(ufs_bodies); static const char notice_bconfigdescrlock[] = "\n" "Please pay attention that UFS provisioning is irreversible (OTP) operation unless parameter bConfigDescrLock = 0.\n" "In order to prevent unintentional device locking the tool has the following safety:\n\n" " if you REALLY intend to perform OTP, please ensure that your XML includes property\n" " bConfigDescrLock = 1 AND provide command line parameter --finalize-provisioning.\n\n" " Unless you intend to lock your device, please set bConfigDescrLock = 0 in your XML\n" " and don't use command line parameter --finalize-provisioning.\n\n" "In case of mismatch between CL and XML provisioning is not performed.\n\n"; bool ufs_need_provisioning(void) { return !!ufs_epilogue_p; } struct ufs_common *ufs_parse_common_params(xmlNode *node, bool finalize_provisioning __unused) { struct ufs_common *result; int errors; result = calloc(1, sizeof(struct ufs_common)); errors = 0; result->bNumberLU = attr_as_unsigned(node, "bNumberLU", &errors); result->bBootEnable = !!attr_as_unsigned(node, "bBootEnable", &errors); result->bDescrAccessEn = !!attr_as_unsigned(node, "bDescrAccessEn", &errors); result->bInitPowerMode = attr_as_unsigned(node, "bInitPowerMode", &errors); result->bHighPriorityLUN = attr_as_unsigned(node, "bHighPriorityLUN", &errors); result->bSecureRemovalType = attr_as_unsigned(node, "bSecureRemovalType", &errors); result->bInitActiveICCLevel = attr_as_unsigned(node, "bInitActiveICCLevel", &errors); result->wPeriodicRTCUpdate = attr_as_unsigned(node, "wPeriodicRTCUpdate", &errors); result->bConfigDescrLock = !!attr_as_unsigned(node, "bConfigDescrLock", &errors); if (errors) { ux_err("errors while parsing UFS common tag\n"); free(result); return NULL; } /* These parameters are optional */ errors = 0; result->bWriteBoosterBufferPreserveUserSpaceEn = !!attr_as_unsigned(node, "bWriteBoosterBufferPreserveUserSpaceEn", &errors); result->bWriteBoosterBufferType = !!attr_as_unsigned(node, "bWriteBoosterBufferType", &errors); result->shared_wb_buffer_size_in_kb = attr_as_unsigned(node, "shared_wb_buffer_size_in_kb", &errors); result->wb = !errors; return result; } struct ufs_body *ufs_parse_body(xmlNode *node) { struct ufs_body *result; int errors; result = calloc(1, sizeof(struct ufs_body)); errors = 0; result->LUNum = attr_as_unsigned(node, "LUNum", &errors); result->bLUEnable = !!attr_as_unsigned(node, "bLUEnable", &errors); result->bBootLunID = attr_as_unsigned(node, "bBootLunID", &errors); result->size_in_kb = attr_as_unsigned(node, "size_in_kb", &errors); result->bDataReliability = attr_as_unsigned(node, "bDataReliability", &errors); result->bLUWriteProtect = attr_as_unsigned(node, "bLUWriteProtect", &errors); result->bMemoryType = attr_as_unsigned(node, "bMemoryType", &errors); result->bLogicalBlockSize = attr_as_unsigned(node, "bLogicalBlockSize", &errors); result->bProvisioningType = attr_as_unsigned(node, "bProvisioningType", &errors); result->wContextCapabilities = attr_as_unsigned(node, "wContextCapabilities", &errors); result->desc = attr_as_string(node, "desc", &errors); if (errors) { ux_err("errors while parsing UFS body tag\n"); free(result); return NULL; } return result; } struct ufs_epilogue *ufs_parse_epilogue(xmlNode *node) { struct ufs_epilogue *result; int errors = 0; result = calloc(1, sizeof(struct ufs_epilogue)); result->LUNtoGrow = attr_as_unsigned(node, "LUNtoGrow", &errors); if (errors) { ux_err("errors while parsing UFS epilogue tag\n"); free(result); return NULL; } return result; } int ufs_load(const char *ufs_file, bool finalize_provisioning) { xmlNode *node; xmlNode *root; xmlDoc *doc; int retval = 0; struct ufs_body *ufs_body_tmp; struct ufs_body *ufs_body; if (ufs_common_p) { ux_err("Only one UFS provisioning XML allowed, \"%s\" ignored\n", ufs_file); return -EEXIST; } doc = xmlReadFile(ufs_file, NULL, 0); if (!doc) { ux_err("failed to parse ufs-type file \"%s\"\n", ufs_file); return -EINVAL; } root = xmlDocGetRootElement(doc); for (node = root->children; node ; node = node->next) { if (node->type != XML_ELEMENT_NODE) continue; if (xmlStrcmp(node->name, (xmlChar *)"ufs")) { ux_err("unrecognized tag \"%s\" in ufs-type file \"%s\", ignoring\n", ufs_file, node->name); continue; } if (xmlGetProp(node, (xmlChar *)"bNumberLU")) { if (!ufs_common_p) { ufs_common_p = ufs_parse_common_params(node, finalize_provisioning); } else { ux_err("multiple UFS common tags found in \"%s\"\n", ufs_file); retval = -EINVAL; break; } if (!ufs_common_p) { ux_err("invalid UFS common tag found in \"%s\"\n", ufs_file); retval = -EINVAL; break; } } else if (xmlGetProp(node, (xmlChar *)"LUNum")) { ufs_body_tmp = ufs_parse_body(node); if (ufs_body_tmp) { list_add(&ufs_bodies, &ufs_body_tmp->node); } else { ux_err("invalid UFS body tag found in \"%s\"\n", ufs_file); retval = -EINVAL; break; } } else if (xmlGetProp(node, (xmlChar *)"commit")) { if (!ufs_epilogue_p) { ufs_epilogue_p = ufs_parse_epilogue(node); if (ufs_epilogue_p) continue; } else { ux_err("multiple UFS finalizing tags found in \"%s\"\n", ufs_file); retval = -EINVAL; break; } if (!ufs_epilogue_p) { ux_err("invalid UFS finalizing tag found in \"%s\"\n", ufs_file); retval = -EINVAL; break; } } else { ux_err("unknown tag found in ufs-type file \"%s\"\n", ufs_file); retval = -EINVAL; break; } } xmlFreeDoc(doc); if (!retval && (!ufs_common_p || list_empty(&ufs_bodies) || !ufs_epilogue_p)) { ux_err("incomplete UFS provisioning information in \"%s\"\n", ufs_file); retval = -EINVAL; } if (retval) { if (ufs_common_p) { free(ufs_common_p); } list_for_each_entry_safe(ufs_body, ufs_body_tmp, &ufs_bodies, node) { free(ufs_body); } if (ufs_epilogue_p) { free(ufs_epilogue_p); } return retval; } if (!finalize_provisioning != !ufs_common_p->bConfigDescrLock) { ux_err("UFS provisioning value bConfigDescrLock %d in file \"%s\" don't match command line parameter --finalize-provisioning %d\n", ufs_common_p->bConfigDescrLock, ufs_file, finalize_provisioning); ux_err(notice_bconfigdescrlock); return -EINVAL; } return 0; } int ufs_provisioning_execute(struct qdl_device *qdl, int (*apply_ufs_common)(struct qdl_device *, struct ufs_common*), int (*apply_ufs_body)(struct qdl_device *, struct ufs_body*), int (*apply_ufs_epilogue)(struct qdl_device *, struct ufs_epilogue*, bool)) { int ret; struct ufs_body *body; if (ufs_common_p->bConfigDescrLock) { int i; ux_info("WARNING: irreversible provisioning will start in 5s"); for (i = 5; i > 0; i--) { ux_info(".\a"); fflush(stdout); sleep(1); } ux_info("\n"); } // Just ask a target to check the XML w/o real provisioning ret = apply_ufs_common(qdl, ufs_common_p); if (ret) return ret; list_for_each_entry(body, &ufs_bodies, node) { ret = apply_ufs_body(qdl, body); if (ret) return ret; } ret = apply_ufs_epilogue(qdl, ufs_epilogue_p, false); if (ret) { ux_err("UFS provisioning impossible, provisioning XML may be corrupted\n"); return ret; } // Real provisioning -- target didn't refuse a given XML ret = apply_ufs_common(qdl, ufs_common_p); if (ret) return ret; list_for_each_entry(body, &ufs_bodies, node) { ret = apply_ufs_body(qdl, body); if (ret) return ret; } return apply_ufs_epilogue(qdl, ufs_epilogue_p, true); } qdl-2.4/ufs.h000066400000000000000000000027221512113701100131000ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2018, The Linux Foundation. All rights reserved. */ #ifndef __UFS_H__ #define __UFS_H__ #include #include "list.h" struct qdl_device; struct ufs_common { unsigned int bNumberLU; bool bBootEnable; bool bDescrAccessEn; unsigned int bInitPowerMode; unsigned int bHighPriorityLUN; unsigned int bSecureRemovalType; unsigned int bInitActiveICCLevel; unsigned int wPeriodicRTCUpdate; bool bConfigDescrLock; bool wb; bool bWriteBoosterBufferPreserveUserSpaceEn; bool bWriteBoosterBufferType; unsigned int shared_wb_buffer_size_in_kb; }; struct ufs_body { unsigned int LUNum; bool bLUEnable; unsigned int bBootLunID; unsigned int size_in_kb; unsigned int bDataReliability; unsigned int bLUWriteProtect; unsigned int bMemoryType; unsigned int bLogicalBlockSize; unsigned int bProvisioningType; unsigned int wContextCapabilities; const char *desc; struct list_head node; }; struct ufs_epilogue { unsigned int LUNtoGrow; bool commit; }; int ufs_load(const char *ufs_file, bool finalize_provisioning); int ufs_provisioning_execute(struct qdl_device *qdl, int (*apply_ufs_common)(struct qdl_device *qdl, struct ufs_common *ufs), int (*apply_ufs_body)(struct qdl_device *qdl, struct ufs_body *ufs), int (*apply_ufs_epilogue)(struct qdl_device *qdl, struct ufs_epilogue *ufs, bool commit)); bool ufs_need_provisioning(void); #endif qdl-2.4/usb.c000066400000000000000000000170041512113701100130660ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause #include #include #include #include #include #include #include #include #include #include "oscompat.h" #include "qdl.h" #define DEFAULT_OUT_CHUNK_SIZE (1024 * 1024) struct qdl_device_usb { struct qdl_device base; struct libusb_device_handle *usb_handle; int in_ep; int out_ep; size_t in_maxpktsize; size_t out_maxpktsize; size_t out_chunk_size; }; /* * libusb commit f0cce43f882d ("core: Fix definition and use of enum * libusb_transfer_type") split transfer type and endpoint transfer types. * Provide an alias in order to make the code compile with the old (non-split) * definition. */ #ifndef LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK #define LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK LIBUSB_TRANSFER_TYPE_BULK #endif static bool usb_match_usb_serial(struct libusb_device_handle *handle, const char *serial, const struct libusb_device_descriptor *desc) { char buf[128]; char *p; int ret; /* If no serial is requested, consider everything a match */ if (!serial) return true; ret = libusb_get_string_descriptor_ascii(handle, desc->iProduct, (unsigned char *)buf, sizeof(buf)); if (ret < 0) { warnx("failed to read iProduct descriptor: %s", libusb_strerror(ret)); return false; } p = strstr(buf, "_SN:"); if (!p) return false; p += strlen("_SN:"); p[strcspn(p, " _")] = '\0'; return strcmp(p, serial) == 0; } static int usb_try_open(libusb_device *dev, struct qdl_device_usb *qdl, const char *serial) { const struct libusb_endpoint_descriptor *endpoint; const struct libusb_interface_descriptor *ifc; struct libusb_config_descriptor *config; struct libusb_device_descriptor desc; struct libusb_device_handle *handle; size_t out_size; size_t in_size; uint8_t type; int ret; int out; int in; int k; int l; ret = libusb_get_device_descriptor(dev, &desc); if (ret < 0) { warnx("failed to get USB device descriptor"); return -1; } /* Consider only devices with vid 0x0506 and known product id */ if (desc.idVendor != 0x05c6) return 0; if (desc.idProduct != 0x9008 && desc.idProduct != 0x900e && desc.idProduct != 0x901d) return 0; ret = libusb_get_active_config_descriptor(dev, &config); if (ret < 0) { warnx("failed to acquire USB device's active config descriptor"); return -1; } for (k = 0; k < config->bNumInterfaces; k++) { ifc = config->interface[k].altsetting; in = -1; out = -1; in_size = 0; out_size = 0; for (l = 0; l < ifc->bNumEndpoints; l++) { endpoint = &ifc->endpoint[l]; type = endpoint->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK; if (type != LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK) continue; if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) { in = endpoint->bEndpointAddress; in_size = endpoint->wMaxPacketSize; } else { out = endpoint->bEndpointAddress; out_size = endpoint->wMaxPacketSize; } } if (ifc->bInterfaceClass != 0xff) continue; if (ifc->bInterfaceSubClass != 0xff) continue; /* bInterfaceProtocol of 0xff, 0x10 and 0x11 has been seen */ if (ifc->bInterfaceProtocol != 0xff && ifc->bInterfaceProtocol != 16 && ifc->bInterfaceProtocol != 17) continue; ret = libusb_open(dev, &handle); if (ret < 0) { warnx("unable to open USB device"); continue; } if (!usb_match_usb_serial(handle, serial, &desc)) { libusb_close(handle); continue; } libusb_detach_kernel_driver(handle, ifc->bInterfaceNumber); ret = libusb_claim_interface(handle, ifc->bInterfaceNumber); if (ret < 0) { warnx("failed to claim USB interface"); libusb_close(handle); continue; } qdl->usb_handle = handle; qdl->in_ep = in; qdl->out_ep = out; qdl->in_maxpktsize = in_size; qdl->out_maxpktsize = out_size; if (qdl->out_chunk_size && qdl->out_chunk_size % out_size) { ux_err("WARNING: requested out-chunk-size must be multiple of the device's wMaxPacketSize %ld, using %ld\n", out_size, out_size); qdl->out_chunk_size = out_size; } else if (!qdl->out_chunk_size) { qdl->out_chunk_size = DEFAULT_OUT_CHUNK_SIZE; } ux_debug("USB: using out-chunk-size of %ld\n", qdl->out_chunk_size); break; } libusb_free_config_descriptor(config); return !!qdl->usb_handle; } static int usb_open(struct qdl_device *qdl, const char *serial) { struct libusb_device **devs; struct libusb_device *dev; struct qdl_device_usb *qdl_usb = container_of(qdl, struct qdl_device_usb, base); bool wait_printed = false; bool found = false; ssize_t n; int ret; int i; ret = libusb_init(NULL); if (ret < 0) err(1, "failed to initialize libusb"); for (;;) { n = libusb_get_device_list(NULL, &devs); if (n < 0) err(1, "failed to list USB devices"); for (i = 0; devs[i]; i++) { dev = devs[i]; ret = usb_try_open(dev, qdl_usb, serial); if (ret == 1) { found = true; break; } } libusb_free_device_list(devs, 1); if (found) return 0; if (!wait_printed) { ux_info("Waiting for EDL device\n"); wait_printed = true; } usleep(250000); } return -1; } static void usb_close(struct qdl_device *qdl) { struct qdl_device_usb *qdl_usb = container_of(qdl, struct qdl_device_usb, base); libusb_close(qdl_usb->usb_handle); libusb_exit(NULL); } static int usb_read(struct qdl_device *qdl, void *buf, size_t len, unsigned int timeout) { struct qdl_device_usb *qdl_usb = container_of(qdl, struct qdl_device_usb, base); int actual; int ret; ret = libusb_bulk_transfer(qdl_usb->usb_handle, qdl_usb->in_ep, buf, len, &actual, timeout); if (ret != 0 && ret != LIBUSB_ERROR_TIMEOUT) return -EIO; if (ret == LIBUSB_ERROR_TIMEOUT && actual == 0) return -ETIMEDOUT; /* If what we read equals the endpoint's Max Packet Size, consume the ZLP explicitly */ if (len == actual && !(actual % qdl_usb->in_maxpktsize)) { ret = libusb_bulk_transfer(qdl_usb->usb_handle, qdl_usb->in_ep, NULL, 0, NULL, timeout); if (ret) warnx("Unable to read ZLP: %s", libusb_strerror(ret)); } return actual; } static int usb_write(struct qdl_device *qdl, const void *buf, size_t len, unsigned int timeout) { unsigned char *data = (unsigned char *)buf; struct qdl_device_usb *qdl_usb = container_of(qdl, struct qdl_device_usb, base); unsigned int count = 0; size_t len_orig = len; int actual; int xfer; int ret; while (len > 0) { xfer = (len > qdl_usb->out_chunk_size) ? qdl_usb->out_chunk_size : len; ret = libusb_bulk_transfer(qdl_usb->usb_handle, qdl_usb->out_ep, data, xfer, &actual, timeout); if (ret != 0 && ret != LIBUSB_ERROR_TIMEOUT) { warnx("bulk write failed: %s", libusb_strerror(ret)); return -EIO; } if (ret == LIBUSB_ERROR_TIMEOUT && actual == 0) return -ETIMEDOUT; count += actual; len -= actual; data += actual; } if (len_orig % qdl_usb->out_maxpktsize == 0) { ret = libusb_bulk_transfer(qdl_usb->usb_handle, qdl_usb->out_ep, NULL, 0, &actual, timeout); if (ret < 0) return -EIO; } return count; } static void usb_set_out_chunk_size(struct qdl_device *qdl, long size) { struct qdl_device_usb *qdl_usb = container_of(qdl, struct qdl_device_usb, base); qdl_usb->out_chunk_size = size; } struct qdl_device *usb_init(void) { struct qdl_device *qdl = malloc(sizeof(struct qdl_device_usb)); 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; } qdl-2.4/util.c000066400000000000000000000123111512113701100132460ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2016, Bjorn Andersson * All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include "oscompat.h" #include "qdl.h" #include "version.h" static uint8_t to_hex(uint8_t ch) { ch &= 0xf; return ch <= 9 ? '0' + ch : 'a' + ch - 10; } void print_version(void) { extern const char *__progname; fprintf(stdout, "%s version %s\n", __progname, VERSION); } void print_hex_dump(const char *prefix, const void *buf, size_t len) { const uint8_t *ptr = buf; size_t linelen; uint8_t ch; char line[16 * 3 + 16 + 1]; int li; unsigned int i; unsigned int j; for (i = 0; i < len; i += 16) { linelen = MIN(16u, (size_t)(len - i)); li = 0; for (j = 0; j < linelen; j++) { ch = ptr[i + j]; line[li++] = to_hex(ch >> 4); line[li++] = to_hex(ch); line[li++] = ' '; } for (; j < 16; j++) { line[li++] = ' '; line[li++] = ' '; line[li++] = ' '; } for (j = 0; j < linelen; j++) { ch = ptr[i + j]; line[li++] = isprint(ch) ? ch : '.'; } line[li] = '\0'; printf("%s %04x: %s\n", prefix, i, line); } } unsigned int attr_as_unsigned(xmlNode *node, const char *attr, int *errors) { unsigned int ret; xmlChar *value; value = xmlGetProp(node, (xmlChar *)attr); if (!value) { (*errors)++; return 0; } ret = (unsigned int)strtoul((char *)value, NULL, 0); xmlFree(value); return ret; } const char *attr_as_string(xmlNode *node, const char *attr, int *errors) { xmlChar *value; char *ret = NULL; value = xmlGetProp(node, (xmlChar *)attr); if (!value) { (*errors)++; return NULL; } if (value[0] != '\0') ret = strdup((char *)value); xmlFree(value); return ret; } bool attr_as_bool(xmlNode *node, const char *attr, int *errors) { xmlChar *value; bool ret = false; if (!xmlHasProp(node, (xmlChar *)attr)) return false; value = xmlGetProp(node, (xmlChar *)attr); if (!value) { (*errors)++; return false; } ret = (xmlStrcmp(value, (xmlChar *)"true") == 0); xmlFree(value); return ret; } /*** * parse_storage_address() - parse a storage address specifier * @address: specifier to be parsed * @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: * * 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, 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) { partition = -1; gpt = strdup(ptr); goto done; } if ((errno == ERANGE && partition == LONG_MAX) || partition < 0) return -1; if (end[0] == '\0') goto done; if (end[0] != '/') return -1; ptr = end + 1; errno = 0; sector = strtoul(ptr, &end, 10); if (end == ptr) { gpt = strdup(ptr); goto done; } if (errno == ERANGE && sector == ULONG_MAX) return -1; if (end[0] == '\0') goto done; if (end[0] != '+') return -1; ptr = end + 1; errno = 0; length = strtoul(ptr, &end, 10); if (end == ptr) return -1; if (errno == ERANGE && length == ULONG_MAX) return -1; if (length == 0) return -1; if (end[0] != '\0') return -1; done: *physical_partition = partition; *start_sector = sector; *num_sectors = length; *gpt_partition = gpt; return 0; } /** * load_sahara_image() - Load the content of the given file into the image * @filename: file to be loaded * @image: Sahara image object to be populated * * Read the content of the given @filename into the given @image, update the * @image->len, and then populate the @image->name for debugging purposes. * * Returns: 0 on success, -1 on error */ int load_sahara_image(const char *filename, struct sahara_image *image) { ssize_t n; off_t len; void *ptr; int fd; fd = open(filename, O_RDONLY | O_BINARY); if (fd < 0) { ux_err("failed to read \"%s\"\n", filename); return -1; } len = lseek(fd, 0, SEEK_END); if (len < 0) { ux_err("failed to find end of \"%s\"\n", filename); goto err_close; } lseek(fd, 0, SEEK_SET); ptr = malloc(len); n = read(fd, ptr, len); if (n != len) { ux_err("failed to read content of \"%s\"\n", filename); free(ptr); goto err_close; } close(fd); image->name = strdup(filename); image->ptr = ptr; image->len = len; return 0; err_close: close(fd); return -1; } qdl-2.4/ux.c000066400000000000000000000064471512113701100127420ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause #include #ifdef _WIN32 #include #else #include #endif #include #include #include "qdl.h" #define UX_PROGRESS_REFRESH_RATE 10 #define UX_PROGRESS_SIZE_MAX 80 #define HASHES "################################################################################" #define DASHES "--------------------------------------------------------------------------------" static const char * const progress_hashes = HASHES; static const char * const progress_dashes = DASHES; static unsigned int ux_width; static unsigned int ux_cur_line_length; /* * Levels of output: * * error: used to signal errors to the user * info: used to inform the user about progress * logs: log prints from the device * debug: protocol logs */ /* Clear ux_cur_line_length characters of the progress bar from the screen */ static void ux_clear_line(void) { if (!ux_cur_line_length) return; printf("%*s\r", ux_cur_line_length, ""); fflush(stdout); ux_cur_line_length = 0; } #ifdef _WIN32 void ux_init(void) { CONSOLE_SCREEN_BUFFER_INFO csbi; int columns; HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); if (GetConsoleScreenBufferInfo(stdoutHandle, &csbi)) { columns = csbi.srWindow.Right - csbi.srWindow.Left + 1; ux_width = MIN(columns, UX_PROGRESS_SIZE_MAX); } } #else void ux_init(void) { struct winsize w; int ret; ret = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); if (!ret) ux_width = MIN(w.ws_col, UX_PROGRESS_SIZE_MAX); } #endif void ux_err(const char *fmt, ...) { va_list ap; ux_clear_line(); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fflush(stderr); } void ux_info(const char *fmt, ...) { va_list ap; ux_clear_line(); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); fflush(stdout); } void ux_log(const char *fmt, ...) { va_list ap; if (!qdl_debug) return; ux_clear_line(); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); fflush(stdout); } void ux_debug(const char *fmt, ...) { va_list ap; if (!qdl_debug) return; ux_clear_line(); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); fflush(stdout); } void ux_progress(const char *fmt, unsigned int value, unsigned int max, ...) { static struct timeval last_progress_update; unsigned long elapsed_us; unsigned int bar_length; unsigned int bars; unsigned int dashes; struct timeval now; char task_name[32]; float percent; va_list ap; /* Don't print progress is window is too narrow, or if stdout is redirected */ if (ux_width < 30) return; /* Avoid updating the console more than UX_PROGRESS_REFRESH_RATE per second */ if (last_progress_update.tv_sec) { gettimeofday(&now, NULL); elapsed_us = (now.tv_sec - last_progress_update.tv_sec) * 1000000 + (now.tv_usec - last_progress_update.tv_usec); if (elapsed_us < (1000000 / UX_PROGRESS_REFRESH_RATE)) return; } va_start(ap, max); vsnprintf(task_name, sizeof(task_name), fmt, ap); va_end(ap); bar_length = ux_width - (20 + 4 + 6); percent = (float)value / max; bars = percent * bar_length; dashes = bar_length - bars; printf("%-20.20s [%.*s%.*s] %1.2f%%%n\r", task_name, bars, progress_hashes, dashes, progress_dashes, percent * 100, &ux_cur_line_length); fflush(stdout); gettimeofday(&last_progress_update, NULL); } qdl-2.4/vip.c000066400000000000000000000273611512113701100131020ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include "sim.h" #define DIGEST_FULL_TABLE_FILE "DIGEST_TABLE.bin" #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 #define MAX_DIGESTS_PER_CHAINED_TABLE (MAX_DIGESTS_PER_CHAINED_FILE - 1) #define MAX_DIGESTS_PER_BUF 16 #ifndef O_BINARY #define O_BINARY 0 #define O_TEXT 0 #endif struct vip_table_generator { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA2_CTX ctx; FILE *digest_table_fd; size_t digest_num_written; const char *path; }; void vip_transfer_deinit(struct qdl_device *qdl); static void print_digest(unsigned char *buf) { char hex_str[SHA256_DIGEST_STRING_LENGTH]; for (size_t i = 0; i < SHA256_DIGEST_LENGTH; ++i) sprintf(hex_str + i * 2, "%02x", buf[i]); hex_str[SHA256_DIGEST_STRING_LENGTH - 1] = '\0'; ux_debug("FIREHOSE PACKET SHA256: %s\n", hex_str); } int vip_gen_init(struct qdl_device *qdl, const char *path) { struct vip_table_generator *vip_gen; struct stat st; char filepath[PATH_MAX]; if (qdl->dev_type != QDL_DEVICE_SIM) { ux_err("Should be executed in simulation dry-run mode\n"); return -1; } if (stat(path, &st) || !S_ISDIR(st.st_mode)) { ux_err("Directory '%s' to store VIP tables doesn't exist\n", path); return -1; } vip_gen = malloc(sizeof(struct vip_table_generator)); if (!vip_gen) { ux_err("Can't allocate memory for vip_table_generator\n"); return -1; } if (!sim_set_digest_generation(true, qdl, vip_gen)) { ux_err("Can't enable digest table generation\n"); goto out_cleanup; } vip_gen->digest_num_written = 0; vip_gen->path = path; snprintf(filepath, sizeof(filepath), "%s/%s", path, DIGEST_FULL_TABLE_FILE); vip_gen->digest_table_fd = fopen(filepath, "wb"); if (!vip_gen->digest_table_fd) { ux_err("Can't create %s file\n", filepath); goto out_cleanup; } return 0; out_cleanup: free(vip_gen); sim_set_digest_generation(false, qdl, NULL); return -1; } void vip_gen_chunk_init(struct qdl_device *qdl) { struct vip_table_generator *vip_gen; vip_gen = sim_get_vip_generator(qdl); if (!vip_gen) return; SHA256Init(&vip_gen->ctx); } void vip_gen_chunk_update(struct qdl_device *qdl, const void *buf, size_t len) { struct vip_table_generator *vip_gen; vip_gen = sim_get_vip_generator(qdl); if (!vip_gen) return; SHA256Update(&vip_gen->ctx, (uint8_t *)buf, len); } void vip_gen_chunk_store(struct qdl_device *qdl) { struct vip_table_generator *vip_gen; vip_gen = sim_get_vip_generator(qdl); if (!vip_gen) return; SHA256Final(vip_gen->hash, &vip_gen->ctx); print_digest(vip_gen->hash); if (fwrite(vip_gen->hash, SHA256_DIGEST_LENGTH, 1, vip_gen->digest_table_fd) != 1) { ux_err("Failed to write digest to the " DIGEST_FULL_TABLE_FILE); goto out_cleanup; } vip_gen->digest_num_written++; return; out_cleanup: fclose(vip_gen->digest_table_fd); } static int write_output_file(const char *filename, bool append, const void *data, size_t len) { FILE *fp; char *mode = "wb"; if (append) mode = "ab"; fp = fopen(filename, mode); if (!fp) { ux_err("Failed to open file for appending\n"); return -1; } if (fwrite(data, 1, len, fp) != len) { ux_err("Failed to append to file\n"); fclose(fp); return -1; } fclose(fp); return 0; } static int calculate_hash_of_file(const char *filename, unsigned char *hash) { unsigned char buf[1024]; SHA2_CTX ctx; FILE *fp = fopen(filename, "rb"); if (!fp) { ux_err("Failed to open file for hashing\n"); return -1; } SHA256Init(&ctx); size_t bytes; while ((bytes = fread(buf, 1, sizeof(buf), fp)) > 0) { SHA256Update(&ctx, (uint8_t *)buf, bytes); } fclose(fp); SHA256Final(hash, &ctx); return 0; } static int write_digests_to_table(char *src_table, char *dest_table, size_t start_digest, size_t count) { const size_t elem_size = SHA256_DIGEST_LENGTH; unsigned char buf[MAX_DIGESTS_PER_BUF * SHA256_DIGEST_LENGTH]; size_t written = 0; int ret; int fd = open(src_table, O_RDONLY | O_BINARY); if (fd < 0) { ux_err("Failed to open %s for reading\n", src_table); return -1; } /* Seek to offset of start_digest */ off_t offset = elem_size * start_digest; if (lseek(fd, offset, SEEK_SET) != offset) { ux_err("Failed to seek in %s\n", src_table); goto out_cleanup; } while (written < (count * elem_size)) { size_t to_read = count * elem_size - written; if (to_read > sizeof(buf)) to_read = sizeof(buf); ssize_t bytes = read(fd, buf, to_read); if (bytes < 0 || (size_t)bytes != to_read) { ux_err("Failed to read from %s\n", src_table); goto out_cleanup; } ret = write_output_file(dest_table, (written != 0), buf, bytes); if (ret < 0) { ux_err("Can't write digests to %s\n", dest_table); goto out_cleanup; } written += to_read; } close(fd); return 0; out_cleanup: close(fd); return -1; } static int create_chained_tables(struct vip_table_generator *vip_gen) { size_t chained_num = 0; size_t tosign_count = 0; size_t total_digests = vip_gen->digest_num_written; char src_table[PATH_MAX]; char dest_table[PATH_MAX]; unsigned char hash[SHA256_DIGEST_LENGTH]; int ret; snprintf(src_table, sizeof(src_table), "%s/%s", vip_gen->path, DIGEST_FULL_TABLE_FILE); /* Step 1: Write digest table to DigestsToSign.bin */ snprintf(dest_table, sizeof(dest_table), "%s/%s", vip_gen->path, DIGEST_TABLE_TO_SIGN_FILE); tosign_count = total_digests < MAX_DIGESTS_PER_SIGNED_TABLE ? total_digests : MAX_DIGESTS_PER_SIGNED_TABLE; ret = write_digests_to_table(src_table, dest_table, 0, tosign_count); if (ret) { ux_err("Writing digests to %s failed\n", dest_table); return ret; } /* Step 2: Write remaining digests to ChainedTableOfDigests.bin */ if (total_digests > MAX_DIGESTS_PER_SIGNED_TABLE) { size_t remaining_digests = total_digests - MAX_DIGESTS_PER_SIGNED_TABLE; while (remaining_digests > 0) { size_t table_digests = remaining_digests > MAX_DIGESTS_PER_CHAINED_TABLE ? MAX_DIGESTS_PER_CHAINED_TABLE : remaining_digests; snprintf(dest_table, sizeof(dest_table), "%s/%s%zu.bin", vip_gen->path, CHAINED_TABLE_FILE_PREF, chained_num); ret = write_digests_to_table(src_table, dest_table, total_digests - remaining_digests, table_digests); if (ret) { ux_err("Writing digests to %s failed\n", dest_table); return ret; } remaining_digests -= table_digests; if (!remaining_digests) { /* Add zero (the packet can't be multiple of 512 bytes) */ ret = write_output_file(dest_table, true, "\0", 1); if (ret < 0) { ux_err("Can't write 0 to %s\n", dest_table); return ret; } } chained_num++; } } /* Step 3: Recursively hash and append backwards */ for (ssize_t i = chained_num - 1; i >= 0; --i) { snprintf(src_table, sizeof(src_table), "%s/%s%zd.bin", vip_gen->path, CHAINED_TABLE_FILE_PREF, i); ret = calculate_hash_of_file(src_table, hash); if (ret < 0) { ux_err("Failed to hash %s\n", src_table); return ret; } if (i == 0) { snprintf(dest_table, sizeof(dest_table), "%s/%s", vip_gen->path, DIGEST_TABLE_TO_SIGN_FILE); ret = write_output_file(dest_table, true, hash, SHA256_DIGEST_LENGTH); if (ret < 0) { ux_err("Failed to append hash to %s\n", dest_table); return ret; } } else { snprintf(dest_table, sizeof(dest_table), "%s/%s%zd.bin", vip_gen->path, CHAINED_TABLE_FILE_PREF, (i - 1)); ret = write_output_file(dest_table, true, hash, SHA256_DIGEST_LENGTH); if (ret < 0) { ux_err("Failed to append hash to %s\n", dest_table); return ret; } } } return 0; } void vip_gen_finalize(struct qdl_device *qdl) { struct vip_table_generator *vip_gen; vip_gen = sim_get_vip_generator(qdl); if (!vip_gen) return; fclose(vip_gen->digest_table_fd); ux_debug("VIP TABLE DIGESTS: %lu\n", vip_gen->digest_num_written); if (create_chained_tables(vip_gen) < 0) ux_err("Error occurred when creating table of digests\n"); 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: vip_transfer_deinit(qdl); return -1; } void vip_transfer_deinit(struct qdl_device *qdl) { close(qdl->vip_data.signed_table_fd); if (qdl->vip_data.chained_num > 0) { for (size_t 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; ssize_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, 1000); 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; } qdl-2.4/vip.h000066400000000000000000000022761512113701100131050ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. */ #ifndef __VIP_H__ #define __VIP_H__ #include "sha2.h" 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); void vip_gen_chunk_store(struct qdl_device *qdl); void vip_gen_finalize(struct qdl_device *qdl); #endif /* __VIP_H__ */