pax_global_header00006660000000000000000000000064152674060160014521gustar00rootroot0000000000000052 comment=aa82b9ac06a8b27f1c5c6fd8aaa159e450d23708 vjardin-libqoriq-rcw-175bf56/000077500000000000000000000000001526740601600161305ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/.github/000077500000000000000000000000001526740601600174705ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/.github/workflows/000077500000000000000000000000001526740601600215255ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/.github/workflows/ci.yml000066400000000000000000000114621526740601600226470ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # Copyright 2026 Free Mobile -- Vincent Jardin # # CI for libqoriq-rcw. # Builds the library with sanitizers and runs the cmocka test suite on a # matrix of {x86_64, ARM64} x {gcc, clang}. Also runs shellcheck on any # shell scripts and verifies meson dist produces a clean source tarball. name: CI on: push: pull_request: permissions: contents: read jobs: build-and-test: strategy: fail-fast: false matrix: os: [ubuntu-24.04, ubuntu-24.04-arm] cc: [gcc, clang] runs-on: ${{ matrix.os }} name: build+test (${{ matrix.os }}, ${{ matrix.cc }}) env: CC: ${{ matrix.cc }} steps: - name: Checkout uses: actions/checkout@v5 - name: Install build dependencies run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ meson ninja-build pkg-config \ libcmocka-dev libmcpp-dev pandoc \ gcc clang libclang-rt-18-dev - name: meson setup (with AddressSanitizer + UBSan) # b_lundef=false is required when building shared libraries # with clang sanitizers: clang expects the sanitizer runtime # symbols to remain undefined at link time and be resolved # at load time, which conflicts with meson's default # -Wl,--no-undefined. run: meson setup build -Db_sanitize=address,undefined -Db_lundef=false - name: meson compile run: meson compile -C build - name: meson test run: meson test -C build --print-errorlogs - name: Upload meson logs on failure if: failure() uses: actions/upload-artifact@v5 with: name: meson-logs-${{ matrix.os }}-${{ matrix.cc }} path: build/meson-logs/ shellcheck: runs-on: ubuntu-24.04 name: shellcheck steps: - name: Checkout uses: actions/checkout@v5 - name: Install shellcheck run: | sudo apt-get update sudo apt-get install -y --no-install-recommends shellcheck - name: Run shellcheck on all shell scripts run: | shopt -s globstar nullglob files=(**/*.sh) if [ ${#files[@]} -eq 0 ]; then echo "No shell scripts found, skipping." exit 0 fi shellcheck --severity=style "${files[@]}" dist: runs-on: ubuntu-24.04 name: meson dist steps: - name: Checkout (full history) uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install build dependencies run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ meson ninja-build pkg-config libcmocka-dev libmcpp-dev pandoc - name: meson setup run: meson setup build - name: meson dist run: meson dist -C build --no-tests - name: Upload dist tarball uses: actions/upload-artifact@v5 with: name: dist-tarball path: build/meson-dist/*.tar.xz package: # Build libqoriq-rcw1 (runtime) and libqoriq-rcw-dev (headers/static/.pc) # .deb packages for the supported Ubuntu LTS releases (24.04, 26.04) and # the two latest Debian stable releases (12 bookworm, 13 trixie), on # both amd64 and arm64. All builds run inside a distro container so the # GitHub runner OS only provides the kernel and Docker. strategy: fail-fast: false matrix: arch: - { name: amd64, runner: ubuntu-24.04 } - { name: arm64, runner: ubuntu-24.04-arm } distro: - { image: 'ubuntu:24.04', slug: 'ubuntu-24.04' } - { image: 'ubuntu:26.04', slug: 'ubuntu-26.04' } - { image: 'debian:bookworm', slug: 'debian-12' } - { image: 'debian:trixie', slug: 'debian-13' } runs-on: ${{ matrix.arch.runner }} name: package (${{ matrix.distro.slug }}, ${{ matrix.arch.name }}) container: image: ${{ matrix.distro.image }} steps: - name: Install build dependencies run: | export DEBIAN_FRONTEND=noninteractive apt-get update apt-get install -y --no-install-recommends \ ca-certificates git \ build-essential debhelper devscripts fakeroot \ meson ninja-build pkg-config \ libmcpp-dev libcmocka-dev pandoc - name: Checkout uses: actions/checkout@v5 - name: dpkg-buildpackage run: dpkg-buildpackage -us -uc -b - name: Collect .deb artifacts run: | mkdir -p artifacts mv ../libqoriq-rcw*_*.deb artifacts/ ls -la artifacts/ - name: Upload .deb packages uses: actions/upload-artifact@v5 with: name: debs-${{ matrix.distro.slug }}-${{ matrix.arch.name }} path: artifacts/*.deb vjardin-libqoriq-rcw-175bf56/LICENSE000066400000000000000000000027451526740601600171450ustar00rootroot00000000000000Copyright 2026 Free Mobile - Vincent Jardin 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 holders 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 HOLDERS 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. vjardin-libqoriq-rcw-175bf56/README.md000066400000000000000000000156451526740601600174220ustar00rootroot00000000000000# libqoriq-rcw [![CI](https://github.com/vjardin/libqoriq-rcw/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/vjardin/libqoriq-rcw/actions/workflows/ci.yml) C library for compiling and decompiling NXP QorIQ Reset Configuration Word (RCW) files. ## What is the RCW? On NXP QorIQ and Layerscape SoCs, the Reset Configuration Word (RCW) is a 1024-bit block that controls the earliest stage of hardware initialization. At power-on reset, the SoC's on-chip Service Processor (TBC a dedicated Cortex-A5 core with its own 64 KB boot ROM) is released from reset and begins executing its Boot 0 Reset Handler from ROM. The Service Processor samples the `CFG_RCW_SRC` configuration pins to determine the boot interface (FlexSPI NOR, SD card, eMMC, or I2C), then loads 128 bytes of RCW data from that interface into the RCWSR registers of the Device Configuration block. The PLLs lock according to the clock ratios encoded in the RCW, and optional Pre-Boot Initialization (PBI) commands are executed to write CCSR registers or copy data into on-chip SRAM (OCRAM). Only then are the application cores released from reset and allowed to boot. The RCW therefore defines PLL ratios, SerDes protocol selection, boot source and location, pin muxing, and errata workarounds. The binary image produced by this library is what gets flashed onto the board's boot medium and consumed by the Service Processor's boot ROM at every power cycle. Supported platforms: LS1028, LS1088, LS2088, and LX2160 families (PBI format 2). ## Features - Forward compilation: `.rcw` source to `.bin` binary image - Reverse decompilation: `.bin` binary back to `.rcw` source text - Stable public C API with opaque context (no global state) - Built as both shared (`.so`) and static (`.a`) libraries - pkg-config support for downstream integration - Bitfield override API (equivalent to command-line `-D` overrides) - Preprocessor defines API - Buffer-based compilation API (`rcw_compile_buffer()` / `rcw_decompile_buffer()`) for embedding already-preprocessed source - No runtime dependency on an external C compiler: preprocessing is handled in-process by [mcpp](http://mcpp.sourceforge.net/) - Image checking API - The `.pbi` line of any decoded command (`rcw_pbi_to_source()`) - The command-line forms of the overrides and macros (`rcw_ctx_set_bitfield_text()`, `rcw_ctx_add_define()` with `NAME=VALUE`) - The PBI flow control (`loadc`, `jump`, `jumpc` and labels), printed back with labels by the decompiler ## TODO The legacy PBI formats (`%pbiformat=0` and `1`: P-, T- and B4-series, LS1012A, LS1021A, LS1043A, LS1046A) are not supported yet. ## Dependencies - C17 compiler (gcc or clang) - Meson, Ninja - [mcpp](http://mcpp.sourceforge.net/) (package: `libmcpp-dev`) - used in-process as the C preprocessor for `.rcw` / `.rcwi` files - cmocka (package: `libcmocka-dev`) - for the unit tests only - pandoc - for generating the man page ## Build From the repository root (`rcw/`): ```bash meson setup build meson compile -C build ``` ## Test ```bash meson test -C build ``` This runs the cmocka unit test suites: | Suite | What it tests | |-------|---------------| | `test_crc32` | Custom CRC-32 implementation | | `test_bits` | Bitfield packing and extraction | | `test_parse` | Source file parser | | `test_pbi` | PBI command encoder and expression evaluator | | `test_uboot` | `.uboot` subsection encoder (u-boot.bin xxd dump) | | `test_binary` | End-to-end binary generation | | `test_decompile` | Decompilation and round-trip verification | | `test_cond` | Flow control: `loadc`, `jump`, `jumpc` and labels | | `test_density` | Density commands (0x02 / 0x42) | | `test_secure` | Secure boot commands (0x20 / 0x22) | | `test_decode` | PBI command decoder | | `test_pbl` | PBL envelope reader, the CRC and Stop value, the PBI check and the image verdict | | `test_preprocess` | Macros handed to mcpp | | `test_source` | The `.pbi` line printed for a decoded command | | `test_context` | `NAME=VALUE` overrides and macros from the command line | | `check_exports` | The version script lists exactly the functions of `rcw.h` | ## Install ```bash meson install -C build ``` This installs: - `libqoriq-rcw.so` / `libqoriq-rcw.a` to `$libdir` - `qoriq-rcw/rcw.h` to `$includedir/qoriq-rcw/` - `libqoriq-rcw.pc` pkg-config file ## Releasing The version number shall be bumped for every release: | File | What to change | |---------------------------|-----------------------------------------------| | `meson.build` | the `version :` field of the `project()` call | | `include/qoriq-rcw/rcw.h` | `QORIQ_RCW_VERSION_MAJOR` / `_MINOR` / `_PATCH` | | `debian/changelog` | a new top entry `libqoriq-rcw (X.Y.Z-1) ...` | Add the changelog entry with the text timestamp: ```bash date -R # e.g. Mon, 29 Jun 2026 15:14:51 +0200 ``` Commit the three files, create an annotated tag, and push both: ```bash git add meson.build include/qoriq-rcw/rcw.h debian/changelog git commit -m "Release X.Y.Z" git tag -a vX.Y.Z -m "libqoriq-rcw X.Y.Z" git push origin main git push origin vX.Y.Z ``` Then, GitHub shall auto-generate the source archives (`.tar.gz` / `.zip`) for the tag; if needed use `gh release create`: ```bash gh release create vX.Y.Z --title "vX.Y.Z" --notes "..." ``` The tarball is then available at: ``` https://github.com/vjardin/libqoriq-rcw/archive/refs/tags/vX.Y.Z.tar.gz ``` For example: the 1.0.1 release was commit [`aa075dd`](https://github.com/vjardin/libqoriq-rcw/commit/aa075dd8fdb1b255fad40fb0f5692d4e2fee3cab) ("Release 1.0.1"), tagged `v1.0.1` ([release page](https://github.com/vjardin/libqoriq-rcw/releases/tag/v1.0.1)). ## Usage ```c #include rcw_ctx_t *ctx = rcw_ctx_new(); /* Compile from file (preprocessed in-process by mcpp) */ uint8_t *binary = NULL; size_t len = 0; rcw_error_t err = rcw_compile_file(ctx, "rcw_2000.rcw", &binary, &len); /* Or compile from a pre-processed buffer */ err = rcw_compile_buffer(ctx, source_text, source_len, &binary, &len); /* Override bitfields before compilation */ rcw_ctx_set_bitfield(ctx, "SYS_PLL_RAT", 10); /* Decompile a binary */ char *source = NULL; size_t source_len = 0; err = rcw_decompile_file(ctx, "rcw.bin", "lx2160a.rcwi", &source, &source_len); /* Clean up */ rcw_free(binary); rcw_free(source); rcw_ctx_free(ctx); /* * Judge an image the way the BootROM should do: the envelope, the * Load RCW checksum, a terminator at PBI_LENGTH, every word a command, * the CRC. A visitor in place of the NULLs sees each command and can * print it with rcw_pbi_to_source(), see libqoriq-rcw(3). */ rcw_pbl_report_t rep; rcw_error_t verdict = rcw_pbl_verify(binary, len, 1, NULL, NULL, &rep); if (verdict != RCW_OK) printf("the image does not boot: %s\n", rcw_strerror(verdict)); ``` Link with `-lqoriq-rcw`, or use pkg-config: ```bash gcc -o myapp myapp.c $(pkg-config --cflags --libs libqoriq-rcw) ``` See `libqoriq-rcw(3)` for the full API reference. vjardin-libqoriq-rcw-175bf56/debian/000077500000000000000000000000001526740601600173525ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/debian/changelog000066400000000000000000000102051526740601600212220ustar00rootroot00000000000000libqoriq-rcw (1.4.0-1) unstable; urgency=medium * rcw_pbl_check() follows the jumps * New error codes * The shared library exports the functions of rcw.h only * Debian packaging: the runtime package is libqoriq-rcw1, after the SONAME, with a symbols file; 3.0 (quilt) source format, watch file, autopkgtest, tests during the build with a nocheck profile. -- Vincent Jardin Sun, 25 Oct 2026 14:59:28 +0100 libqoriq-rcw (1.3.0-1) unstable; urgency=medium * rcw_pbl_verify(): the verdict such as BootROM would likely do * rcw_pbi_to_source(): add the .pbi line of a decoded command * rcw_pbl_from_rcw(): RCW[PBI_LENGTH] and RCW[SB_EN] of a bare RCW, as the RCWSR registers * rcw_ctx_set_bitfield_text(): the -D NAME=VALUE override same as rcw.py rcw_ctx_add_define() takes NAME=VALUE as a C compiler does. * Exported envelope constants: RCW_PBL_PREAMBLE, RCW_PBL_CMD_LOAD_RCW, RCW_PBL_CMD_LOAD_RCW_NOCS, RCW_PBL_TERMINATOR_BYTES. * An invalid bitfield value is reported on stderr unconditionally, no longer only with warnings enabled. -- Vincent Jardin Sun, 25 Oct 2026 02:32:45 +0200 libqoriq-rcw (1.2.0-1) unstable; urgency=medium * Image checking API: rcw_pbi_decode, rcw_pbi_op_name, rcw_pbl_parse, rcw_pbi_crc and rcw_pbl_check, with RCW_ERR_TRUNCATED and RCW_ERR_PBI_LENGTH. * PBI: loadc, jump, jumpc and labels as upstream rcw.py spells them, printed back with labels by the decompiler; loadcondition and jumpif kept as aliases. * PBI: awrite.b1 to awrite.b10, every AltConfig Write width. * PBI: operands evaluated like Python's eval(); trailing text and results outside 32 bits are errors. flush refused under pbiformat=2. * Parse: numbers read as Python's int(x, 0); %sysaddr refused with pbiformat=2. * Preprocess: rcw_ctx_add_define() for -D macros (RCW_CPPFLAGS). * Decompile: list the set bits no field covers. * Binary: Load RCW checksum summed in the image's word order. -- Vincent Jardin Sun, 25 Oct 2026 01:09:54 +0200 libqoriq-rcw (1.1.0-1) unstable; urgency=medium * PBI: the command set is now feature complete. New mnemonics for conditional execution (loadcondition 0x14, jump 0x84, jumpif 0x85), for the density commands (ccsrcopy 0x02, ccsrset / ccsrclear / ccsrreplace 0x42) and for secure boot (loadsecurityheader 0x20, loadboot1csfptr 0x22), on both the compile and decompile paths. * PBI: poll now emits the four words described by the reference manual. * Decompile: awrite keeps its width suffix. * Compile: warn when the generated PBL reaches 4096 bytes; 4092 is the measured LX2160A Service Processor ceiling above which the SoC stays silent. * Tests: new test_cond, test_density and test_secure suites, plus coverage for flush. -- Vincent Jardin Thu, 20 Aug 2026 00:52:20 +0200 libqoriq-rcw (1.0.2-1) unstable; urgency=medium * Build: pandoc man-page generation is now optional, gated by the 'man' meson option, so the library builds without pandoc installed. -- Vincent Jardin Tue, 30 Jun 2026 16:48:33 +0200 libqoriq-rcw (1.0.1-1) unstable; urgency=medium * Build: the cmocka-based unit test suite is now optional, so the library can be built without libcmocka-dev installed. -- Vincent Jardin Mon, 29 Jun 2026 15:14:51 +0200 libqoriq-rcw (1.0.0-1) unstable; urgency=medium * First stable release. Public C API is now considered stable. -- Vincent Jardin Fri, 19 Jun 2026 00:00:00 +0000 libqoriq-rcw (0.2.0-1) unstable; urgency=medium * New public API: rcw_preprocess_file(). Runs the embedded mcpp preprocessor on a path and returns the expanded text, ready for rcw_compile_buffer() / rcw_decompile_buffer(). Lets in-memory pipelines avoid the tempfile dance previously needed by callers that already had the binary in RAM (e.g. qoriq-rcw --dump). -- Vincent Jardin Mon, 13 Apr 2026 14:30:00 +0000 libqoriq-rcw (0.1.0-1) unstable; urgency=medium * Initial Debian/Ubuntu packaging. -- Vincent Jardin Mon, 13 Apr 2026 12:00:00 +0000 vjardin-libqoriq-rcw-175bf56/debian/control000066400000000000000000000026741526740601600207660ustar00rootroot00000000000000Source: libqoriq-rcw Section: libs Maintainer: Vincent Jardin Build-Depends: debhelper-compat (= 13), meson, ninja-build, pkgconf, libmcpp-dev, libcmocka-dev , pandoc Standards-Version: 4.7.3 Homepage: https://github.com/vjardin/libqoriq-rcw Rules-Requires-Root: no Package: libqoriq-rcw1 Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends} Depends: ${shlibs:Depends}, ${misc:Depends} Description: QorIQ Reset Configuration Word compiler library (runtime) libqoriq-rcw is a C library for compiling NXP QorIQ Reset Configuration Word source files into PBL/RCW binary images, and for decompiling those images back to source text. It targets the PBI format 2 platforms: LS1028, LS1088, LS2088 and LX2160 SoC families. . This package contains the shared library used at runtime. Package: libqoriq-rcw-dev Section: libdevel Architecture: any Multi-Arch: same Depends: libqoriq-rcw1 (= ${binary:Version}), libmcpp-dev, ${misc:Depends} Description: QorIQ Reset Configuration Word compiler library (development files) libqoriq-rcw is a C library for compiling NXP QorIQ Reset Configuration Word source files into PBL/RCW binary images, and for decompiling those images back to source text. It targets the PBI format 2 platforms: LS1028, LS1088, LS2088 and LX2160 SoC families. . This package contains the C header, static library, pkg-config file and man page needed to build software against libqoriq-rcw. vjardin-libqoriq-rcw-175bf56/debian/copyright000066400000000000000000000034171526740601600213120ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: libqoriq-rcw Upstream-Contact: Vincent Jardin Source: https://github.com/vjardin/libqoriq-rcw Files: * Copyright: 2026 Free Mobile -- Vincent Jardin License: BSD-3-Clause License: BSD-3-Clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. . * 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. . * 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. vjardin-libqoriq-rcw-175bf56/debian/libqoriq-rcw-dev.install000066400000000000000000000002231526740601600241260ustar00rootroot00000000000000usr/include/qoriq-rcw/*.h usr/lib/*/libqoriq-rcw.so usr/lib/*/libqoriq-rcw.a usr/lib/*/pkgconfig/libqoriq-rcw.pc usr/share/man/man3/libqoriq-rcw.3 vjardin-libqoriq-rcw-175bf56/debian/libqoriq-rcw1.install000066400000000000000000000000341526740601600234330ustar00rootroot00000000000000usr/lib/*/libqoriq-rcw.so.* vjardin-libqoriq-rcw-175bf56/debian/libqoriq-rcw1.symbols000066400000000000000000000014251526740601600234620ustar00rootroot00000000000000libqoriq-rcw.so.1 libqoriq-rcw1 #MINVER# * Build-Depends-Package: libqoriq-rcw-dev rcw_compile_buffer@Base 1.0.0 rcw_compile_file@Base 1.0.0 rcw_ctx_add_define@Base 1.2.0 rcw_ctx_add_include_path@Base 1.0.0 rcw_ctx_free@Base 1.0.0 rcw_ctx_last_error_detail@Base 1.0.0 rcw_ctx_new@Base 1.0.0 rcw_ctx_set_bitfield@Base 1.0.0 rcw_ctx_set_bitfield_text@Base 1.3.0 rcw_ctx_set_pbl@Base 1.0.0 rcw_ctx_set_warnings@Base 1.0.0 rcw_decompile_buffer@Base 1.0.0 rcw_decompile_file@Base 1.0.0 rcw_free@Base 1.0.0 rcw_pbi_crc@Base 1.2.0 rcw_pbi_decode@Base 1.2.0 rcw_pbi_op_name@Base 1.2.0 rcw_pbi_to_source@Base 1.3.0 rcw_pbl_check@Base 1.2.0 rcw_pbl_from_rcw@Base 1.3.0 rcw_pbl_parse@Base 1.2.0 rcw_pbl_verify@Base 1.3.0 rcw_preprocess_file@Base 1.0.0 rcw_strerror@Base 1.0.0 vjardin-libqoriq-rcw-175bf56/debian/rules000077500000000000000000000005531526740601600204350ustar00rootroot00000000000000#!/usr/bin/make -f # SPDX-License-Identifier: BSD-3-Clause # Copyright 2026 Free Mobile -- Vincent Jardin export DEB_BUILD_MAINT_OPTIONS = hardening=+all ifneq (,$(filter nocheck,$(DEB_BUILD_OPTIONS) $(DEB_BUILD_PROFILES))) TESTS = false else TESTS = true endif %: dh $@ --buildsystem=meson override_dh_auto_configure: dh_auto_configure -- -Dtests=$(TESTS) vjardin-libqoriq-rcw-175bf56/debian/source/000077500000000000000000000000001526740601600206525ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/debian/source/format000066400000000000000000000000141526740601600220600ustar00rootroot000000000000003.0 (quilt) vjardin-libqoriq-rcw-175bf56/debian/tests/000077500000000000000000000000001526740601600205145ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/debian/tests/build000077500000000000000000000021031526740601600215350ustar00rootroot00000000000000#!/bin/sh # SPDX-License-Identifier: BSD-3-Clause # Copyright 2026 Free Mobile -- Vincent Jardin set -eu cd "${AUTOPKGTEST_TMP:-$(mktemp -d)}" cat > rcwtest.c <<'SRC' #include #include #include static const char src[] = "%size=1024\n%pbiformat=2\n%classicbitnumbers=1\n%littleendian=1\n" "SYS_PLL_RAT[6:2]\nPBI_LENGTH[287:276]\nSYS_PLL_RAT=14\n" ".pbi\nwrite 0x01e00400,0x1800d000\n.end\n"; int main(void) { rcw_ctx_t *ctx = rcw_ctx_new(); uint8_t *bin = NULL; size_t len = 0; rcw_pbl_report_t rep; rcw_error_t err = rcw_compile_buffer(ctx, src, strlen(src), &bin, &len); if (err != RCW_OK) { fprintf(stderr, "compile: %s\n", rcw_strerror(err)); return 1; } err = rcw_pbl_verify(bin, len, 1, NULL, NULL, &rep); printf("%zu bytes, verdict: %s\n", len, rcw_strerror(err)); rcw_free(bin); rcw_ctx_free(ctx); return err == RCW_OK && rep.ok && rep.chk.stop_op == RCW_PBI_CRC_STOP ? 0 : 1; } SRC # shellcheck disable=SC2046 gcc -Wall -Werror -o rcwtest rcwtest.c $(pkgconf --cflags --libs libqoriq-rcw) ./rcwtest vjardin-libqoriq-rcw-175bf56/debian/tests/control000066400000000000000000000001341526740601600221150ustar00rootroot00000000000000Tests: build Depends: build-essential, pkgconf, libqoriq-rcw-dev Restrictions: allow-stderr vjardin-libqoriq-rcw-175bf56/debian/upstream/000077500000000000000000000000001526740601600212125ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/debian/upstream/metadata000066400000000000000000000003571526740601600227220ustar00rootroot00000000000000Bug-Database: https://github.com/vjardin/libqoriq-rcw/issues Bug-Submit: https://github.com/vjardin/libqoriq-rcw/issues/new Repository: https://github.com/vjardin/libqoriq-rcw.git Repository-Browse: https://github.com/vjardin/libqoriq-rcw vjardin-libqoriq-rcw-175bf56/debian/watch000066400000000000000000000003771526740601600204120ustar00rootroot00000000000000version=4 opts="searchmode=plain, \ filenamemangle=s%.*/v?@ANY_VERSION@%@PACKAGE@-$1.tar.gz%" \ https://api.github.com/repos/vjardin/libqoriq-rcw/tags?per_page=50 \ https://api.github.com/repos/vjardin/libqoriq-rcw/tarball/refs/tags/v?@ANY_VERSION@ vjardin-libqoriq-rcw-175bf56/include/000077500000000000000000000000001526740601600175535ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/include/qoriq-rcw/000077500000000000000000000000001526740601600214775ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/include/qoriq-rcw/rcw.h000066400000000000000000000302211526740601600224410ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * QorIQ Reset Configuration Word (RCW) compiler library. * Compiles .rcw source files into PBL/RCW binary images and * decompiles binaries back to source (pbiformat=2 platforms). */ #pragma once #include #include #ifdef __cplusplus extern "C" { #endif #define QORIQ_RCW_VERSION_MAJOR 1 #define QORIQ_RCW_VERSION_MINOR 4 #define QORIQ_RCW_VERSION_PATCH 0 /* Error codes */ typedef enum { RCW_OK = 0, RCW_ERR_NOMEM, RCW_ERR_IO, RCW_ERR_PREPROCESS, RCW_ERR_PARSE, RCW_ERR_OVERLAP, RCW_ERR_UNKNOWN_FIELD, RCW_ERR_VALUE_OVERFLOW, RCW_ERR_PBI_SYNTAX, RCW_ERR_MISSING_VAR, RCW_ERR_BAD_BINARY, RCW_ERR_UNSUPPORTED, RCW_ERR_TRUNCATED, RCW_ERR_PBI_LENGTH, RCW_ERR_CHECKSUM, RCW_ERR_CRC, RCW_ERR_PBI_OVERRUN, RCW_ERR_PBI_JUMP, RCW_ERR_PBI_NO_CONDITION, RCW_ERR_PBI_CONDITIONAL, } rcw_error_t; /* Opaque context - all state lives here, no globals. */ typedef struct rcw_ctx rcw_ctx_t; /* * Lifecycle */ rcw_ctx_t *rcw_ctx_new(void); void rcw_ctx_free(rcw_ctx_t *ctx); /* * Configuration (call before compilation) */ rcw_error_t rcw_ctx_add_include_path(rcw_ctx_t *ctx, const char *path); /* -Dname=value for mcpp; a NULL value takes "NAME" or "NAME=VALUE" */ rcw_error_t rcw_ctx_add_define(rcw_ctx_t *ctx, const char *name, const char *value); rcw_error_t rcw_ctx_set_pbl(rcw_ctx_t *ctx, int enable); rcw_error_t rcw_ctx_set_warnings(rcw_ctx_t *ctx, int enable); /* Override a bitfield value (equivalent to rcw.py -D FIELD=VALUE). * Can be called multiple times. Overrides apply after source parsing. */ rcw_error_t rcw_ctx_set_bitfield(rcw_ctx_t *ctx, const char *name, uint64_t value); /* The same from "NAME=VALUE", as rcw.py -D takes it */ rcw_error_t rcw_ctx_set_bitfield_text(rcw_ctx_t *ctx, const char *text); /* * Forward compilation: .rcw -> .bin * * rcw_compile_file() runs the embedded C preprocessor (mcpp) on * input_path, then parses and compiles the result. * * rcw_compile_buffer() takes already-preprocessed text (for embedding * or testing without a preprocessor). * * Both allocate *out_data; caller must free it with rcw_free(). */ rcw_error_t rcw_compile_file(rcw_ctx_t *ctx, const char *input_path, uint8_t **out_data, size_t *out_len); rcw_error_t rcw_compile_buffer(rcw_ctx_t *ctx, const char *preprocessed, size_t len, uint8_t **out_data, size_t *out_len); /* * Reverse decompilation: .bin -> .rcw text * * rcw_decompile_file() preprocesses rcwi_path with the embedded * preprocessor (mcpp) to obtain symbol definitions, then reads * bin_path and produces RCW source text. The output is prefixed with * #include * * rcw_decompile_buffer() takes already-preprocessed .rcwi text and an * in-memory binary buffer. No preprocessor is invoked and no file * I/O is performed - useful for embedding the library in environments * where the .rcwi text is generated on the fly, or for unit testing. * If rcwi_name is non-NULL, the output is prefixed with * "#include \n\n"; pass NULL to omit the header. * * Both allocate *out_source; caller must free it with rcw_free(). */ rcw_error_t rcw_decompile_file(rcw_ctx_t *ctx, const char *bin_path, const char *rcwi_path, char **out_source, size_t *out_len); rcw_error_t rcw_decompile_buffer(rcw_ctx_t *ctx, const char *rcwi_preprocessed, size_t rcwi_len, const uint8_t *binary, size_t binary_len, const char *rcwi_name, char **out_source, size_t *out_len); /* * Standalone preprocessing. * * Run the embedded mcpp preprocessor on input_path, honouring include * paths previously added via rcw_ctx_add_include_path(). The resulting * NUL-terminated text is suitable for direct use with * rcw_compile_buffer() or rcw_decompile_buffer(). * * On success, *out points to a newly-allocated buffer (length *out_len, * excluding the terminating NUL). The caller must free it with * rcw_free(). * * On preprocessor failure, returns RCW_ERR_PREPROCESS and mcpp * diagnostics are written to standard error. */ rcw_error_t rcw_preprocess_file(rcw_ctx_t *ctx, const char *input_path, char **out, size_t *out_len); /* PBI command decoding */ typedef enum { RCW_PBI_UNKNOWN = -1, RCW_PBI_BLOCKCOPY = 0x00, /* Block Copy */ RCW_PBI_CCSR_COPY = 0x02, /* CCSR Write from Address */ RCW_PBI_LOAD_RCW = 0x10, /* Load RCW with Checksum */ RCW_PBI_LOAD_RCW_NOCS = 0x11, /* Load RCW w/o Checksum */ RCW_PBI_LOADACWINDOW = 0x12, /* Load Alt Cfg Window */ RCW_PBI_LOADCONDITION = 0x14, /* Load Condition */ RCW_PBI_LOAD_SEC_HDR = 0x20, /* Load Security Header */ RCW_PBI_LOAD_CSF_PTR = 0x22, /* Load Boot 1 CSF Header Ptr */ RCW_PBI_CCSR_RMW = 0x42, /* CCSR Read, Modify, Write */ RCW_PBI_POLL_SHORT = 0x80, /* Poll Short */ RCW_PBI_POLL_LONG = 0x81, /* Poll Long */ RCW_PBI_WAIT = 0x82, /* Wait */ RCW_PBI_JUMP = 0x84, /* Jump */ RCW_PBI_JUMP_COND = 0x85, /* Jump Conditional */ RCW_PBI_CRC_STOP = 0x8F, /* CRC and Stop */ RCW_PBI_STOP = 0xFF, /* Stop */ RCW_PBI_CCSR_WRITE = 0x100, /* CCSR Write */ RCW_PBI_ALTCFG_WRITE = 0x101, /* AltConfig Write */ } rcw_pbi_op_t; /* The most operand words a command carries */ #define RCW_PBI_MAX_ARGS (512 / 4) typedef struct { rcw_pbi_op_t op; uint32_t word; /* the command word */ size_t size; /* whole command, in bytes */ size_t nargs; /* operand words after the command word */ uint32_t args[RCW_PBI_MAX_ARGS]; /* * The operand carried by the command word itself: * CCSR Write the 28-bit CCSR address * AltConfig Write the 26-bit ALTCFG_OFFSET * general commands the low 16 bits: Block Copy SRC, ALTCFG_WNDW, * the Wait count, the word type of CCSR Write from * Address, the ops and mask type of CCSR Read, * Modify, Write; zero for the others */ uint32_t param; uint32_t width; /* bytes written by a CCSR or AltConfig Write, else 0 */ } rcw_pbi_cmd_t; /* * Decode the command starting at buf[0]. Words are little-endian when * littleendian is non-zero (%littleendian=1, every pbiformat=2 platform). * * RCW_OK *cmd is filled in, the command is cmd->size bytes * RCW_ERR_BAD_BINARY the word is not a PBI command: a header the RM * reserves, an unknown CMD byte, a CCSR Write with a * byte count of 0 or 2, an AltConfig Write wider than * 512 bytes; cmd->word holds the word * RCW_ERR_TRUNCATED fewer than cmd->size bytes are available (cmd->op, * word, size and nargs are set, the args are not), or * len is below 4 */ rcw_error_t rcw_pbi_decode(const uint8_t *buf, size_t len, int littleendian, rcw_pbi_cmd_t *cmd); /* The .rcw mnemonic of op, "unknown" for RCW_PBI_UNKNOWN. */ const char *rcw_pbi_op_name(rcw_pbi_op_t op); /* The .pbi line of cmd, no newline, snprintf() semantics */ #define RCW_PBI_SOURCE_MAX 1536 /* an awrite.b10 and its 128 words */ int rcw_pbi_to_source(const rcw_pbi_cmd_t *cmd, char *buf, size_t size); /* * PBL image envelope * * A PBL image is the preamble, the Load RCW command with its 32 RCW words * and checksum, then the PBI commands: * * byte 0 preamble 0xAA55AA55 4 bytes * byte 4 Load RCW command 0x80100000 or 0x80110000 4 bytes * byte 8 RCW 32 words 128 bytes * byte 136 checksum, or zero padding 4 bytes * byte 140 the first PBI command * * RCW[PBI_LENGTH] counts the PBI words, terminator included: the RM calls * the field a size in bytes, but NXP's rcw.py and TF-A's create_pbl write * words, and those images boot ! */ #define RCW_PBL_RCW_OFFSET (4 + 4) /* preamble, Load RCW command word */ #define RCW_PBL_RCW_BYTES (1024 / 8) /* the 1024-bit RCW */ #define RCW_PBL_CHECKSUM_OFFSET (RCW_PBL_RCW_OFFSET + RCW_PBL_RCW_BYTES) /* 136 */ #define RCW_PBL_PBI_OFFSET (RCW_PBL_CHECKSUM_OFFSET + 4) /* 140 */ #define RCW_PBL_PREAMBLE 0xAA55AA55u #define RCW_PBL_CMD_LOAD_RCW 0x80100000u /* Load RCW with checksum */ #define RCW_PBL_CMD_LOAD_RCW_NOCS 0x80110000u /* Load RCW without checksum */ #define RCW_PBL_TERMINATOR_BYTES 8 /* Stop or CRC and Stop, and its CRC or zero word */ typedef struct { uint32_t load_rcw_cmd; /* word 1: 0x80100000, or 0x80110000 without checksum */ const uint8_t *rcw; /* the 128 RCW bytes, inside buf */ uint32_t checksum; /* as stored after the RCW words */ uint32_t checksum_computed; /* preamble + Load RCW command + the 32 RCW words */ uint32_t pbi_length; /* RCW[PBI_LENGTH], in 32-bit words */ int sb_en; /* RCW[SB_EN] */ const uint8_t *pbi; /* the first PBI command, inside buf */ size_t pbi_avail; /* bytes from pbi to the end of buf */ } rcw_pbl_t; /* * Read the envelope of the PBL image in buf. * * RCW_ERR_TRUNCATED len is below 140 * RCW_ERR_BAD_BINARY no preamble, or word 1 is not a Load RCW command */ rcw_error_t rcw_pbl_parse(const uint8_t *buf, size_t len, int littleendian, rcw_pbl_t *pbl); /* From the RCW alone (an RCWSR snapshot): rcw, pbi_length and sb_en */ void rcw_pbl_from_rcw(const uint8_t rcw[RCW_PBL_RCW_BYTES], rcw_pbl_t *pbl); uint32_t rcw_pbi_crc(const uint8_t *pbi, size_t len); /* * PBI check */ typedef void (*rcw_pbi_visit_t)(const rcw_pbi_cmd_t *cmd, size_t off, void *arg); typedef struct { size_t len; size_t end; size_t ncmd; rcw_pbi_op_t stop_op; uint32_t crc; uint32_t crc_computed; rcw_pbi_cmd_t cmd; } rcw_pbi_check_t; /* * Check the PBI of a parsed PBL image * * RCW_OK the command at PBI_LENGTH is a Stop or a CRC and Stop * and every word executed before it is a command. The * CRC is reported, not judged: compare crc and * crc_computed. PBI_LENGTH 0 is a PBL without PBI. * RCW_ERR_PBI_JUMP the jump at end has an offset that is not a multiple * of 4 above 8, or lands at or past PBI_LENGTH * RCW_ERR_PBI_NO_CONDITION * the Jump Conditional at end comes before any Load * Condition * RCW_ERR_PBI_CONDITIONAL * the Jump Conditional at end depends on a register * read at boot * RCW_ERR_PBI_OVERRUN the command at end runs beyond PBI_LENGTH * RCW_ERR_PBI_LENGTH no terminator at PBI_LENGTH: the walk reaches it * without a Stop (end is PBI_LENGTH) * RCW_ERR_BAD_BINARY the word at end is not a PBI command, cmd.word holds it * RCW_ERR_TRUNCATED pbl->pbi_avail is below 4 * PBI_LENGTH, so the command * at PBI_LENGTH cannot be read; the walk ran to end */ rcw_error_t rcw_pbl_check(const rcw_pbl_t *pbl, int littleendian, rcw_pbi_visit_t visit, void *arg, rcw_pbi_check_t *chk); /* The verdict of rcw_pbl_verify() */ typedef struct { rcw_error_t envelope; /* rcw_pbl_parse() */ uint32_t word0, word1; int preamble_ok; int load_rcw_ok; rcw_pbl_t pbl; /* when envelope is RCW_OK */ int has_checksum; int checksum_ok; rcw_error_t pbi; /* rcw_pbl_check() */ rcw_pbi_check_t chk; /* when envelope is RCW_OK */ int terminated; /* a Stop or CRC and Stop was read whole */ int crc_ok; size_t image_end; /* bytes of the image in buf */ int ok; } rcw_pbl_report_t; /* RCW_OK when rep->ok, else the first failure; see libqoriq-rcw(3) */ rcw_error_t rcw_pbl_verify(const uint8_t *buf, size_t len, int littleendian, rcw_pbi_visit_t visit, void *arg, rcw_pbl_report_t *rep); /* * Error information */ const char *rcw_strerror(rcw_error_t err); const char *rcw_ctx_last_error_detail(const rcw_ctx_t *ctx); /* * Memory management - free any buffer allocated by the library. */ void rcw_free(void *ptr); #ifdef __cplusplus } #endif vjardin-libqoriq-rcw-175bf56/man/000077500000000000000000000000001526740601600167035ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/man/libqoriq-rcw.3.md000066400000000000000000000540131526740601600220040ustar00rootroot00000000000000--- title: LIBQORIQ-RCW section: 3 header: Library Functions footer: libqoriq-rcw 1.4.0 date: 2026-10-25 --- # NAME libqoriq-rcw - QorIQ Reset Configuration Word (RCW) compiler library # SYNOPSIS ```c #include rcw_ctx_t *rcw_ctx_new(void); void rcw_ctx_free(rcw_ctx_t *ctx); rcw_error_t rcw_ctx_add_include_path(rcw_ctx_t *ctx, const char *path); rcw_error_t rcw_ctx_add_define(rcw_ctx_t *ctx, const char *name, const char *value); rcw_error_t rcw_ctx_set_pbl(rcw_ctx_t *ctx, int enable); rcw_error_t rcw_ctx_set_warnings(rcw_ctx_t *ctx, int enable); rcw_error_t rcw_ctx_set_bitfield(rcw_ctx_t *ctx, const char *name, uint64_t value); rcw_error_t rcw_ctx_set_bitfield_text(rcw_ctx_t *ctx, const char *text); rcw_error_t rcw_compile_file(rcw_ctx_t *ctx, const char *input_path, uint8_t **out_data, size_t *out_len); rcw_error_t rcw_compile_buffer(rcw_ctx_t *ctx, const char *preprocessed, size_t len, uint8_t **out_data, size_t *out_len); rcw_error_t rcw_decompile_file(rcw_ctx_t *ctx, const char *bin_path, const char *rcwi_path, char **out_source, size_t *out_len); rcw_error_t rcw_decompile_buffer(rcw_ctx_t *ctx, const char *rcwi_preprocessed, size_t rcwi_len, const uint8_t *binary, size_t binary_len, const char *rcwi_name, char **out_source, size_t *out_len); rcw_error_t rcw_preprocess_file(rcw_ctx_t *ctx, const char *input_path, char **out, size_t *out_len); rcw_error_t rcw_pbi_decode(const uint8_t *buf, size_t len, int littleendian, rcw_pbi_cmd_t *cmd); const char *rcw_pbi_op_name(rcw_pbi_op_t op); int rcw_pbi_to_source(const rcw_pbi_cmd_t *cmd, char *buf, size_t size); rcw_error_t rcw_pbl_parse(const uint8_t *buf, size_t len, int littleendian, rcw_pbl_t *pbl); void rcw_pbl_from_rcw(const uint8_t rcw[128], rcw_pbl_t *pbl); uint32_t rcw_pbi_crc(const uint8_t *pbi, size_t len); typedef void (*rcw_pbi_visit_t)(const rcw_pbi_cmd_t *cmd, size_t off, void *arg); rcw_error_t rcw_pbl_check(const rcw_pbl_t *pbl, int littleendian, rcw_pbi_visit_t visit, void *arg, rcw_pbi_check_t *chk); rcw_error_t rcw_pbl_verify(const uint8_t *buf, size_t len, int littleendian, rcw_pbi_visit_t visit, void *arg, rcw_pbl_report_t *rep); const char *rcw_strerror(rcw_error_t err); const char *rcw_ctx_last_error_detail(const rcw_ctx_t *ctx); void rcw_free(void *ptr); ``` Link with **-lqoriq-rcw**. # DESCRIPTION **libqoriq-rcw** is a C library for compiling NXP QorIQ Reset Configuration Word source files into PBL/RCW binary images, and for decompiling binary images back to source text. It supports PBI format 2 platforms: LS1028, LS1088, LS2088, and LX2160 SoC families. All library state is held in an opaque context object (**rcw_ctx_t**). There are no global variables, so multiple contexts can be used concurrently in separate threads. ## Context Lifecycle **rcw_ctx_new()** : Allocate and initialize a new context. PBL generation is enabled by default. Returns **NULL** on allocation failure. **rcw_ctx_free**(*ctx*) : Free a context and all associated resources. *ctx* may be **NULL**. ## Configuration These functions configure the context before compilation or decompilation. **rcw_ctx_add_include_path**(*ctx*, *path*) : Add *path* to the preprocessor include search list. The current directory (**.**) is always included automatically. May be called multiple times (up to 32 paths). **rcw_ctx_add_define**(*ctx*, *name*, *value*) : Define a preprocessor macro for **rcw_compile_file**(), **rcw_decompile_file**() and **rcw_preprocess_file**(): *-Dname=value* to **mcpp**, or *-Dname* (defined as 1) when *value* is NULL. With *value* NULL, *name* may carry the value itself, *NAME=VALUE*, as the **-D** option of a C compiler takes it. Returns **RCW_ERR_PARSE** unless the name is an identifier. **rcw_ctx_set_pbl**(*ctx*, *enable*) : Enable (*enable* != 0) or disable (*enable* == 0) generation of the PBL preamble, Load RCW command, checksum, and terminator. Default: enabled. When disabled, only the raw 128-byte RCW data is produced. **rcw_ctx_set_warnings**(*ctx*, *enable*) : Enable or disable warning messages for duplicate bitfield assignments. Default: disabled. **rcw_ctx_set_bitfield**(*ctx*, *name*, *value*) : Register a bitfield override. When compilation runs, the bitfield *name* will be set to *value* after source parsing, overriding any assignment in the source file. This is equivalent to **qoriq-rcw -D** on the command line. May be called multiple times (up to 64 overrides). **rcw_ctx_set_bitfield_text**(*ctx*, *text*) : The same override from its command-line form *NAME=VALUE*, as **rcw.py -D** takes it: *VALUE* reads like Python's **int(x, 0)** - decimal, **0x**, **0o** or **0b**, a decimal with a leading zero refused. Returns **RCW_ERR_PARSE** on bad syntax, with the detail in **rcw_ctx_last_error_detail**(). ## Forward Compilation **rcw_compile_file**(*ctx*, *input_path*, *out_data*, *out_len*) : Compile an RCW source file to a binary image. The file at *input_path* is first run through the C preprocessor (**mcpp**), then parsed and compiled. On success, *\*out_data* is set to a newly allocated buffer containing the binary, and *\*out_len* is set to its size in bytes. The caller must free the buffer with **rcw_free()**. The context is reset before each compilation; a single context can be reused for multiple compilations. **rcw_compile_buffer**(*ctx*, *preprocessed*, *len*, *out_data*, *out_len*) : Like **rcw_compile_file()** but takes already-preprocessed source text instead of a file path. No C preprocessor is invoked. This is useful for embedding the library where the caller controls its own preprocessing pipeline, or for unit testing. ## Reverse Decompilation **rcw_decompile_file**(*ctx*, *bin_path*, *rcwi_path*, *out_source*, *out_len*) : Decompile a binary image back to RCW source text. The bitfield definition file *rcwi_path* is preprocessed (via **mcpp**) and parsed to obtain symbol names and positions. Then the binary at *bin_path* is decoded: field values are extracted and PBI commands are disassembled. The output is prefixed with **#include <**basename(*rcwi_path*)**>**, so it can be recompiled directly. On success, *\*out_source* is set to a newly allocated NUL-terminated string, and *\*out_len* is set to its length (excluding the NUL). The caller must free the buffer with **rcw_free()**. **rcw_decompile_buffer**(*ctx*, *rcwi_preprocessed*, *rcwi_len*, *binary*, *binary_len*, *rcwi_name*, *out_source*, *out_len*) : Like **rcw_decompile_file()** but takes already-preprocessed *.rcwi* text and an in-memory binary buffer. No preprocessor is invoked and no file I/O is performed - useful for embedding the library where the caller controls preprocessing, or for unit testing. If *rcwi_name* is non-NULL, the output is prefixed with **#include <***rcwi_name***>\\n\\n**; pass **NULL** to omit the header. ## Standalone Preprocessing **rcw_preprocess_file**(*ctx*, *input_path*, *out*, *out_len*) : Run the embedded **mcpp** preprocessor on *input_path*, honouring include paths previously added via **rcw_ctx_add_include_path()**. On success, *\*out* is a newly-allocated NUL-terminated buffer holding the preprocessed text and *\*out_len* is its length (excluding the NUL). The caller must free it with **rcw_free()**. This function exists so that callers can hand pre-expanded text to **rcw_compile_buffer()** or **rcw_decompile_buffer()** without having to write the source to a file first. Useful for in-memory pipelines (e.g. **qoriq-rcw \-\-dump**, where the binary is already a 128-byte buffer and a tempfile would be wasteful). ## PBI Command Decoding These functions read the PBI commands of an image one at a time. The decoder delimits and decodes. **rcw_pbi_decode**(*buf*, *len*, *littleendian*, *cmd*) : Decode the command that starts at *buf*[0], reading at most *len* bytes. Words are little-endian when *littleendian* is non-zero. On success *cmd* is filled in. Returns **RCW_ERR_BAD_BINARY** when the word is not a PBI command. **rcw_pbi_op_name**(*op*) : Return the *.rcw* mnemonic of *op* (**"write"**, **"blockcopy"**, **"loadsecurityheader"**, ...), **"loadrcw"**, **"crcstop"** and **"stop"** for the three commands that have none, and **"unknown"** for **RCW_PBI_UNKNOWN** or any other value. **rcw_pbi_to_source**(*cmd*, *buf*, *size*) : Write into *buf* the *.pbi* source line of the decoded *cmd*, as the decompiler prints it: **write 0x01e00620,0x80000000**, **awrite.b4 0x00100000,0x...,0x...**, **jump 0x0000000c** (a byte offset: the labels are the decompiler's) and a comment for the three commands without a mnemonic. No newline. Returns the length the line would have, as **snprintf**(3) does; **RCW_PBI_SOURCE_MAX** bytes hold any line. ## PBL Image Envelope A PBL image is the 4-byte preamble, the 4-byte Load RCW command word, the 128-byte RCW and the 4-byte checksum, then the PBI commands. **rcw_pbl_parse**(*buf*, *len*, *littleendian*, *pbl*) : Read the envelope of the PBL image in *buf*: the preamble, the Load RCW command with its 32 RCW words and checksum, and where the PBI commands start. On success *pbl* is filled in. Returns **RCW_ERR_TRUNCATED** when *len* is below 140 and **RCW_ERR_BAD_BINARY** when the preamble is missing or word 1 is not a Load RCW command. The two words are **RCW_PBL_PREAMBLE** and **RCW_PBL_CMD_LOAD_RCW** or **RCW_PBL_CMD_LOAD_RCW_NOCS**; the terminator is **RCW_PBL_TERMINATOR_BYTES** long. **rcw_pbl_from_rcw**(*rcw*, *pbl*) : The same for the 128-byte RCW alone, as the RCWSR registers hold it: *pbl*->**rcw**, **pbi_length** and **sb_en** are set, the envelope fields are 0 and **pbi** is NULL. **rcw_pbi_crc**(*pbi*, *len*) : Return the CRC. ## PBI Check Before executing the PBI, the Boot 0 Reset Handler checks it as a whole (RM Section 8.2.6): the command RCW[PBI_LENGTH] points at must be a Stop or a CRC and Stop, and a CRC and Stop must carry the CRC of every command before it. Execution then runs from the first command to the first Stop or CRC and Stop, and each word on the way must be a command. A Jump moves execution forward by its offset from its own start. A Jump Conditional compares a value loaded by Load Condition at boot, so which path runs is not known from the image. **rcw_pbl_check**(*pbl*, *littleendian*, *visit*, *arg*, *chk*) : Replay that check on the PBI of the image *pbl* describes, as returned by **rcw_pbl_parse()**. When *visit* is not NULL it is called for each command executed, in order, the terminator included, with the command, its byte offset from *pbl*->**pbi** and *arg*. On return *chk* is filled in: **len** is 4 * RCW[PBI_LENGTH], the bytes the Service Processor reads; **end** is where execution stops, the end of the first Stop or CRC and Stop, which is **len** unless a development buffer follows it; **ncmd** counts the commands executed, the ones a Jump skips left out; **stop_op** is the command at **len** - 8, **RCW_PBI_STOP** or **RCW_PBI_CRC_STOP** on success; **crc** is the value a CRC and Stop carries and **crc_computed** the CRC-32 of the **len** - 4 bytes before it, to compare, as a mismatch is reported and not judged; **cmd** is the last command decoded, the terminator on success and the word the walk stopped at otherwise. RCW[PBI_LENGTH] 0 is a PBL without PBI: nothing is visited and the check passes. Returns **RCW_ERR_PBI_OVERRUN** when the command at *chk*->**end** runs past RCW[PBI_LENGTH]. ## PBL Image Verdict **rcw_pbl_parse**() and **rcw_pbl_check**() report; **rcw_pbl_verify**() judges, as the BootROM should likely do before it runs the PBI. **rcw_pbl_verify**(*buf*, *len*, *littleendian*, *visit*, *arg*, *rep*) : Parse and check the image in *buf*, calling *visit* as **rcw_pbl_check**() does, and fill *rep*: **envelope** is the result of **rcw_pbl_parse**() and **pbl** its output; **word0** and **word1** are the first two words of *buf* and **preamble_ok** and **load_rcw_ok** say whether they are the preamble and a Load RCW command; **has_checksum** and **checksum_ok** judge the Load RCW checksum (no checksum passes); **pbi** is the result of **rcw_pbl_check**() and **chk** its output; **terminated** says the walk read a Stop or CRC and Stop whole, which **chk**.**cmd** cannot tell since a decode cut short still names the command; **crc_ok** says the pre-execution check passed, **pbi** being **RCW_OK** or **RCW_ERR_PBI_CONDITIONAL**, and either the terminator is a plain Stop or the CRC matches; **ok** is **checksum_ok**, **pbi** == **RCW_OK** and **crc_ok** together, so never for an image with a Jump Conditional: the verdict stops at the path the image leaves to the hardware. **image_end** is what to hand **rcw_decompile_buffer**() so that whatever follows the image, flash padding for instance, stays out of the source: when **ok**, the **RCW_PBL_PBI_OFFSET** + 4 * RCW[PBI_LENGTH] bytes the Service Processor reads; when **terminated**, through that Stop or CRC and Stop, although RCW[PBI_LENGTH] disagrees with it or the buffer ends early; *len* otherwise. Returns **RCW_OK** when *rep*->**ok**, else the first failure: the envelope's error, **RCW_ERR_CHECKSUM**, the PBI check's error, **RCW_ERR_CRC**, and **RCW_ERR_PBI_CONDITIONAL** last, since it is a limit of the verifier rather than a defect of the image. ## Error Handling All functions that can fail return an **rcw_error_t** value. **RCW_OK** (0) indicates success; all other values indicate an error. **rcw_strerror**(*err*) : Return a short static string describing the error code. **rcw_ctx_last_error_detail**(*ctx*) : Return a detailed error message from the last failed operation on *ctx*. The returned string is valid until the next library call on the same context. May be empty if no detail is available. ## Memory Management **rcw_free**(*ptr*) : Free a buffer allocated by the library (*\*out_data* from compilation or *\*out_source* from decompilation). *ptr* may be **NULL**. # SOURCE FORMAT The text consumed by **rcw_compile_file**() (after preprocessing) and by **rcw_compile_buffer**() supports two kinds of subsection blocks: **.pbi** / **.end** : Pre-Boot Initialization commands. Each line inside the block is a single PBI command (e.g. **write**, **awrite.b4**, **wait**, **poll**, **blockcopy**, **loadacwindow**). See **qoriq-rcw**(1) for the full command list. An AltConfig Write names its width with the suffix **.b1** to **.b10**. 2^(B-1) bytes, 1 to 512, in one data word per 4 bytes; a bare **awrite** is **.b3**, one 4-byte word. The decompiler prints the same suffixes, so a wide write recompiles to the same bytes. Flow control: **loadc** *addr*,*mask* (Load Condition, 0x14), **jump** *target* (0x84), **jumpc** *target*,*condition* (Jump Conditional, 0x85), **loadcondition** **jumpif** are accepted as aliases. The target is a byte offset counted from the start of the jump itself, a multiple of 4 above 8, or a label: **flush** is a command of the legacy PBI formats and has no pbiformat=2 encoding. The flush is a Poll with an all-zero mask: **poll** *addr*,0,0. **.uboot** / **.end** : Embeds a u-boot binary image into the PBI stream for SPI/SD/NAND boot chains. The body is an **xxd**(1) hex dump of *u-boot.bin* (8 16-bit words per line, 16 bytes per line); each line has the form *ADDRESS*: *WORD WORD WORD WORD WORD WORD WORD WORD*. The encoder packs the lines into PBI write blocks at fixed on-chip addresses (0x81F80000 base for 64-byte quad blocks; 0xC1F80000 base for the trailing 32-byte pair blocks). Output is always big-endian regardless of **%littleendian**. No upstream pbiformat=2 board uses this subsection; it is provided for parity with **rcw.py**. The body is generated from a u-boot binary by: ``` xxd u-boot.bin | cut -d ' ' -f1-10 > u-boot.xxd ``` then pasted between **.uboot** and **.end** in the source. # ERROR CODES **RCW_OK** : Success (value 0). **RCW_ERR_NOMEM** : Memory allocation failed. **RCW_ERR_IO** : File I/O error (open, read, or write failure). **RCW_ERR_PREPROCESS** : The C preprocessor (**mcpp**) returned a non-zero exit status. Preprocessing errors are printed to standard error. **RCW_ERR_PARSE** : Syntax error in the RCW source file. **RCW_ERR_OVERLAP** : A bitfield definition overlaps with a previously defined field. **RCW_ERR_UNKNOWN_FIELD** : An assignment or **-D** override references a bitfield name that was not defined. **RCW_ERR_VALUE_OVERFLOW** : The assigned value is too large for the bitfield width. **RCW_ERR_PBI_SYNTAX** : A PBI command line could not be parsed or has the wrong number of parameters. **RCW_ERR_MISSING_VAR** : A required **%variable** (such as **%size** or **%pbiformat**) is missing or invalid. **RCW_ERR_BAD_BINARY** : The binary file does not have a valid PBL preamble or is too short. **RCW_ERR_UNSUPPORTED** : The source specifies a feature not supported by this library (e.g. **%pbiformat!=2**). **RCW_ERR_TRUNCATED** : A PBI command runs past the end of the data given to **rcw_pbi_decode()**, or the image given to **rcw_pbl_check()** holds fewer bytes than RCW[PBI_LENGTH] says. **RCW_ERR_PBI_LENGTH** : No Stop or CRC and Stop at RCW[PBI_LENGTH] (**rcw_pbl_check()**). **RCW_ERR_PBI_OVERRUN** : A PBI command runs past RCW[PBI_LENGTH] (**rcw_pbl_check()**). **RCW_ERR_PBI_JUMP** : A PBI Jump has an offset that is not a multiple of 4 above 8, or lands at or past RCW[PBI_LENGTH] (**rcw_pbl_check()**). **RCW_ERR_PBI_NO_CONDITION** : A PBI Jump Conditional comes before any Load Condition (**rcw_pbl_check()**). **RCW_ERR_PBI_CONDITIONAL** : The PBI holds a Jump Conditional: the path the Service Processor takes depends on a register read at boot, so the walk stopped there (**rcw_pbl_check()**, **rcw_pbl_verify()**). **RCW_ERR_CHECKSUM** : The Load RCW checksum does not match (**rcw_pbl_verify()**). **RCW_ERR_CRC** : The CRC and Stop value does not match (**rcw_pbl_verify()**). # EXAMPLES ## Compile an RCW file ```c #include #include int main(void) { rcw_ctx_t *ctx = rcw_ctx_new(); if (!ctx) return 1; uint8_t *binary = NULL; size_t len = 0; rcw_error_t err; err = rcw_compile_file(ctx, "config/rcw_2000.rcw", &binary, &len); if (err != RCW_OK) { fprintf(stderr, "Error: %s: %s\n", rcw_strerror(err), rcw_ctx_last_error_detail(ctx)); rcw_ctx_free(ctx); return 1; } FILE *f = fopen("output.bin", "wb"); fwrite(binary, 1, len, f); fclose(f); rcw_free(binary); rcw_ctx_free(ctx); return 0; } ``` ## Compile from a buffer (no preprocessor) ```c const char source[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "SYS_PLL_RAT=14\n"; uint8_t *bin = NULL; size_t len = 0; rcw_error_t err = rcw_compile_buffer(ctx, source, strlen(source), &bin, &len); ``` ## Override a bitfield ```c rcw_ctx_set_bitfield(ctx, "SYS_PLL_RAT", 10); rcw_compile_file(ctx, "rcw_2000.rcw", &binary, &len); ``` ## Decompile a binary ```c char *source = NULL; size_t source_len = 0; err = rcw_decompile_file(ctx, "rcw.bin", "ls1088rdb.rcwi", &source, &source_len); if (err == RCW_OK) { printf("%s", source); rcw_free(source); } ``` ## Check a PBL image and list its PBI commands ```c #include static void show(const rcw_pbi_cmd_t *cmd, size_t off, void *arg) { char line[RCW_PBI_SOURCE_MAX]; (void)arg; rcw_pbi_to_source(cmd, line, sizeof(line)); printf("%4zu: %s\n", RCW_PBL_PBI_OFFSET + off, line); } int check_pbl(const uint8_t *buf, size_t len) { rcw_pbl_report_t rep; rcw_error_t verdict = rcw_pbl_verify(buf, len, 1, show, NULL, &rep); if (verdict == RCW_OK) return 0; if (rep.envelope != RCW_OK) printf("not a PBL image: %s\n", rcw_strerror(rep.envelope)); else if (!rep.checksum_ok) printf("RCW checksum 0x%08x, computed 0x%08x\n", rep.pbl.checksum, rep.pbl.checksum_computed); else if (rep.pbi != RCW_OK) printf("PBI byte %zu: %s\n", rep.chk.end, rcw_strerror(rep.pbi)); else printf("PBI CRC 0x%08x, computed 0x%08x\n", rep.chk.crc, rep.chk.crc_computed); return -1; } ``` # NOTES - Only PBI format 2 platforms are supported (**%pbiformat=2**). Older PowerPC platforms using **%pbiformat=0** or **%pbiformat=1** are not supported and will return **RCW_ERR_UNSUPPORTED**. A **%sysaddr** variable, which only the legacy PBL header carries, is refused with **RCW_ERR_PARSE** when the PBL header is generated, as **rcw.py** refuses it. - A PBL image is the 4-byte preamble, the 4-byte Load RCW command word, the 128-byte RCW and the 4-byte checksum, then the PBI commands. - RCW[PBI_LENGTH] counts 32-bit words, terminator included. - **rcw_compile_file()** and **rcw_decompile_file()** use an embedded copy of **mcpp**(1) for C preprocessing - no external toolchain is required at runtime. The buffer-based variants (**rcw_compile_buffer()**, **rcw_decompile_buffer()**) bypass preprocessing entirely. - PBI command parameters are expressions à la Python - Numbers are read as Python's **int(x, 0)** reads them # SEE ALSO **qoriq-rcw**(1), **mcpp**(1), **pkg-config**(1), **xxd**(1) # AUTHORS Vincent Jardin, Free Mobile. vjardin-libqoriq-rcw-175bf56/meson.build000066400000000000000000000037671526740601600203070ustar00rootroot00000000000000project('libqoriq-rcw', 'c', version : '1.4.0', license : 'BSD-3-Clause', default_options : [ 'c_std=c17', 'warning_level=3', 'default_library=both', ]) inc = include_directories('include') src_inc = include_directories('src') # Locate libmcpp (Matsui C Preprocessor), used in-process by # rcw_preprocess.c instead of shelling out to gcc -E. Ubuntu/Debian # package: libmcpp-dev. libmcpp does not ship a pkg-config file, # so find it via the compiler. cc = meson.get_compiler('c') mcpp_dep = cc.find_library('mcpp', has_headers : ['mcpp_lib.h', 'mcpp_out.h'], required : true) lib_sources = files( 'src/rcw_context.c', 'src/rcw_crc32.c', 'src/rcw_bits.c', 'src/rcw_parse.c', 'src/rcw_pbi.c', 'src/rcw_csf.c', 'src/rcw_uboot.c', 'src/rcw_binary.c', 'src/rcw_decompile.c', 'src/rcw_decode.c', 'src/rcw_pbl.c', 'src/rcw_preprocess.c', ) mapfile = meson.current_source_dir() / 'src' / 'libqoriq-rcw.map' version_script = '-Wl,--version-script=' + mapfile if not cc.has_link_argument(version_script) version_script = [] endif libqoriq_rcw = library('qoriq-rcw', lib_sources, include_directories : [inc, src_inc], dependencies : [mcpp_dep], link_args : version_script, link_depends : mapfile, install : true, version : meson.project_version()) libqoriq_rcw_dep = declare_dependency( link_with : libqoriq_rcw, include_directories : inc) install_headers('include/qoriq-rcw/rcw.h', subdir : 'qoriq-rcw') pkg = import('pkgconfig') pkg.generate(libqoriq_rcw, name : 'libqoriq-rcw', description : 'QorIQ Reset Configuration Word compiler library (RCW)', libraries_private : ['-lmcpp']) pandoc = find_program('pandoc', required : get_option('man')) if pandoc.found() man3 = custom_target('libqoriq-rcw.3', input : 'man/libqoriq-rcw.3.md', output : 'libqoriq-rcw.3', command : [pandoc, '-s', '-t', 'man', '@INPUT@', '-o', '@OUTPUT@'], install : true, install_dir : get_option('mandir') / 'man3') endif if get_option('tests') subdir('tests') endif vjardin-libqoriq-rcw-175bf56/meson_options.txt000066400000000000000000000004361526740601600215700ustar00rootroot00000000000000option('tests', type : 'boolean', value : true, description : 'Build the cmocka-based unit test suite (developers & CI). Set false for packaging.') option('man', type : 'feature', value : 'auto', description : 'Build man pages (requires pandoc). auto = build if pandoc available.') vjardin-libqoriq-rcw-175bf56/src/000077500000000000000000000000001526740601600167175ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/src/libqoriq-rcw.map000066400000000000000000000013461526740601600220350ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * The ABI of libqoriq-rcw.so.1: the functions of include/qoriq-rcw/rcw.h. */ { global: rcw_compile_buffer; rcw_compile_file; rcw_ctx_add_define; rcw_ctx_add_include_path; rcw_ctx_free; rcw_ctx_last_error_detail; rcw_ctx_new; rcw_ctx_set_bitfield; rcw_ctx_set_bitfield_text; rcw_ctx_set_pbl; rcw_ctx_set_warnings; rcw_decompile_buffer; rcw_decompile_file; rcw_free; rcw_pbi_crc; rcw_pbi_decode; rcw_pbi_op_name; rcw_pbi_to_source; rcw_pbl_check; rcw_pbl_from_rcw; rcw_pbl_parse; rcw_pbl_verify; rcw_preprocess_file; rcw_strerror; local: *; }; vjardin-libqoriq-rcw-175bf56/src/rcw_binary.c000066400000000000000000000120551526740601600212250ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Binary generation - forward compilation of parsed RCW to .bin. * Mirrors rcw.py create_binary() (lines 597-760), pbiformat=2 only. */ #include #include #include #include #include "rcw_internal.h" /* Pack a 32-bit word in the given endianness into buf. */ static void pack_le32(uint8_t *buf, uint32_t val) { buf[0] = (uint8_t)(val & 0xFF); buf[1] = (uint8_t)((val >> 8) & 0xFF); buf[2] = (uint8_t)((val >> 16) & 0xFF); buf[3] = (uint8_t)((val >> 24) & 0xFF); } static void pack_be32(uint8_t *buf, uint32_t val) { buf[0] = (uint8_t)((val >> 24) & 0xFF); buf[1] = (uint8_t)((val >> 16) & 0xFF); buf[2] = (uint8_t)((val >> 8) & 0xFF); buf[3] = (uint8_t)(val & 0xFF); } typedef void (*pack_fn)(uint8_t *buf, uint32_t val); rcw_error_t rcw_binary_generate(rcw_ctx_t *ctx, uint8_t **out_data, size_t *out_len) { pack_fn pack = ctx->vars.littleendian ? pack_le32 : pack_be32; /* * Auto-calculate PBI_LENGTH if defined but not assigned. * Matches rcw.py lines 638-644. */ for (size_t i = 0; i < ctx->symbols.count; i++) { rcw_symbol_t *sym = &ctx->symbols.entries[i]; if (strcmp(sym->name, "PBI_LENGTH") == 0 && !sym->has_value) { uint64_t pbilen = ctx->pbi.len / PBI_WORD_BYTES; if (ctx->pbl) pbilen += PBL_TERMINATOR_WORDS; /* CRC+Stop or Stop */ sym->has_value = true; sym->value = pbilen; break; } } /* Pack the 1024-bit RCW into 128 bytes */ uint8_t rcw_bytes[RCW_SIZE_BYTES]; rcw_error_t err = rcw_bits_pack(&ctx->symbols, &ctx->vars, rcw_bytes); if (err != RCW_OK) return err; /* * Compute the output size: * PBL header: 8 bytes (preamble + load RCW cmd) * RCW: 128 bytes * Checksum: 4 bytes * PBI data: ctx->pbi.len bytes * Terminator: 8 bytes (stop/CRC + value) */ size_t total = 0; if (ctx->pbl) total += RCW_PBL_RCW_OFFSET; /* preamble, Load RCW command word */ total += RCW_SIZE_BYTES; if (ctx->pbl) total += PBI_WORD_BYTES; /* checksum word */ total += ctx->pbi.len; if (ctx->pbl) total += PBL_TERMINATOR_BYTES; /* Stop, or CRC and Stop */ /* * LX2160A SP scratch ceiling: see PBL_SP_SCRATCH_CEILING_BYTES * in rcw_internal.h for the rationale and trade-offs. */ if (total >= PBL_SP_SCRATCH_CEILING_BYTES) { size_t pbi_body = ctx->pbi.len; size_t header_overhead = total - pbi_body - RCW_SIZE_BYTES; fprintf(stderr, "warning: total PBL size is %zu bytes -- on LX2160A this " "exceeds the SP scratch ceiling at %u bytes; " "the chip will boot SILENTLY (no UART banner, no BL2 output).\n" " RCW header: %d bytes (fixed)\n" " PBI body: %zu bytes\n" " Other: %zu bytes (preamble + checksum + terminator)\n" " Trim PBI commands or drop errata to bring total below %u. " "See libqoriq-rcw/src/rcw_binary.c for bench evidence.\n", total, PBL_SP_SCRATCH_CEILING_BYTES, RCW_SIZE_BYTES, pbi_body, header_overhead, PBL_SP_SCRATCH_CEILING_BYTES); } uint8_t *binary = malloc(total); if (!binary) return RCW_ERR_NOMEM; size_t pos = 0; /* Preamble */ if (ctx->pbl) { pack(binary + pos, PBL_PREAMBLE); pos += PBI_WORD_BYTES; if (ctx->vars.loadwochecksum) pack(binary + pos, PBL_CMD_LOAD_RCW_NOCS); else pack(binary + pos, PBL_CMD_LOAD_RCW); pos += PBI_WORD_BYTES; } /* RCW data */ memcpy(binary + pos, rcw_bytes, RCW_SIZE_BYTES); pos += RCW_SIZE_BYTES; /* * Checksum: the 32-bit sum of every word so far, the preamble, the Load * RCW command and the 32 RCW words */ if (ctx->pbl) { if (ctx->vars.loadwochecksum) { pack(binary + pos, 0x00000000); } else { uint32_t checksum = 0; for (size_t i = 0; i < pos; i += PBI_WORD_BYTES) checksum += rcw_unpack32(binary + i, ctx->vars.littleendian); pack(binary + pos, checksum); } pos += PBI_WORD_BYTES; } /* PBI data */ if (ctx->pbi.len > 0) { memcpy(binary + pos, ctx->pbi.data, ctx->pbi.len); pos += ctx->pbi.len; } /* Terminator */ if (ctx->pbl) { if (ctx->vars.nocrc) { /* Stop command (no CRC) */ pack(binary + pos, PBL_CMD_STOP); pos += PBI_WORD_BYTES; pack(binary + pos, 0x00000000); pos += PBI_WORD_BYTES; } else { /* CRC and Stop */ uint32_t cmd = PBL_CMD_CRC_STOP; uint8_t cmd_bytes[4]; pack(cmd_bytes, cmd); /* * CRC covers PBI data + stop command word. */ bool classic = ctx->vars.classicbitnumbers; uint32_t crc = rcw_crc32_update(RCW_CRC32_INIT, ctx->pbi.data, ctx->pbi.len, classic); crc = rcw_crc32_update(crc, cmd_bytes, sizeof(cmd_bytes), classic); if (classic) crc = rcw_reverse_word(crc); crc ^= RCW_CRC32_INIT; pack(binary + pos, cmd); pos += PBI_WORD_BYTES; pack(binary + pos, crc); pos += PBI_WORD_BYTES; } } *out_data = binary; *out_len = pos; return RCW_OK; } vjardin-libqoriq-rcw-175bf56/src/rcw_bits.c000066400000000000000000000107651526740601600207100ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Bitfield packing/extraction for the 1024-bit RCW register. */ #include #include #include #include "rcw_internal.h" /* Reverse the bit order within a byte. */ static uint8_t reverse_byte(uint8_t b) { b = (uint8_t)(((b & 0xF0) >> 4) | ((b & 0x0F) << 4)); b = (uint8_t)(((b & 0xCC) >> 2) | ((b & 0x33) << 2)); b = (uint8_t)(((b & 0xAA) >> 1) | ((b & 0x55) << 1)); return b; } /* Reverse the bit order of the low 'nbits' bits of v. */ static uint64_t reverse_bits(uint64_t v, int nbits) { uint64_t result = 0; for (int i = 0; i < nbits; i++) { result <<= 1; result |= (v & 1); v >>= 1; } return result; } /* * Set a range of bits [b..e] (b <= e, MSB-first layout where bit 0 is the * MSB of byte 0) in a 128-byte buffer. * * Matches rcw.py: bits += v << ((size - 1) - e) * where 'size' is 1024 and bit 0 is the most-significant bit. */ static void set_bits(uint8_t buf[RCW_SIZE_BYTES], int b, int e, uint64_t v) { int width = 1 + e - b; for (int i = 0; i < width; i++) { int bitpos = e - i; /* MSB-first: bit 0 = MSB of byte 0 */ int bytepos = bitpos / 8; int bitoff = 7 - (bitpos % 8); if ((v >> i) & 1) buf[bytepos] |= (uint8_t)(1 << bitoff); } } rcw_error_t rcw_bits_pack(const rcw_symtab_t *syms, const rcw_vars_t *vars, uint8_t out[RCW_SIZE_BYTES]) { memset(out, 0, RCW_SIZE_BYTES); for (size_t i = 0; i < syms->count; i++) { const rcw_symbol_t *sym = &syms->entries[i]; if (!sym->has_value) continue; int bb = sym->begin; int ee = sym->end; int b = (bb < ee) ? bb : ee; int e = (bb < ee) ? ee : bb; int s = 1 + e - b; uint64_t v = sym->value; /* Check overflow */ if ((s < 64) && (v >= ((uint64_t)1 << s))) { /* Caller should have checked, but be safe */ return RCW_ERR_VALUE_OVERFLOW; } /* * If begin > end (classic bit numbering where the declared * first bit is the higher-numbered one), reverse the value * bits before packing. * Matches rcw.py line 661-662: * if b != bb: * v = int(bin(int(v))[2:].zfill(s)[::-1], 2) */ if (b != bb) v = reverse_bits(v, s); set_bits(out, b, e, v); } /* * Apply per-byte bit-reversal for classicbitnumbers. * Matches rcw.py lines 688-691: * if classicbitnumbers: * byte = int(bin(byte)[2:].zfill(8)[::-1], 2) */ if (vars->classicbitnumbers) { for (int i = 0; i < RCW_SIZE_BYTES; i++) out[i] = reverse_byte(out[i]); } return RCW_OK; } uint64_t rcw_bits_extract(const uint8_t data[RCW_SIZE_BYTES], int begin, int end, bool classicbitnumbers) { uint8_t buf[RCW_SIZE_BYTES]; memcpy(buf, data, RCW_SIZE_BYTES); /* Undo per-byte bit-reversal */ if (classicbitnumbers) { for (int i = 0; i < RCW_SIZE_BYTES; i++) buf[i] = reverse_byte(buf[i]); } /* * rcw.py decompile (lines 858-876): * bitstring = ''.join(['{0:08b}'.format(x) for x in bitbytes])[::-1] * bits = int(bitstring, 2) * shift = b (= min(begin, end)) * mask = (1 << s) - 1 * v = (bits >> shift) & mask * * The [::-1] reversal means bit 0 of 'bits' is the LSB of the last * byte. In our MSB-first buffer, bit position N (LSB-indexed) is at: * byte_index = (RCW_SIZE_BITS - 1 - N) / 8 * bit_in_byte = (RCW_SIZE_BITS - 1 - N) % 8 (MSB=7, LSB=0) * But it is equivalent to: * bit N in the reversed representation * = bit (SIZE-1-N) in MSB-first * = position N counting from the end. * * TBC approach: convert to the same integer as rcw.py does. */ int bb = begin; int ee = end; int b = (bb < ee) ? bb : ee; int e = (bb < ee) ? ee : bb; int s = 1 + e - b; /* * rcw.py decompile: * bitstring = ''.join('{0:08b}'.format(x) for x in bytes)[::-1] * bits = int(bitstring, 2) * v = (bits >> b) & mask * * int(s, 2) treats s[0] as MSB. After [::-1], bit k (counting * from LSB) of 'bits' * = reversed_string[len-1-k] * = original[k]. * * original[k] = byte k/8, bit (7 - k % 8) within that byte. */ uint64_t v = 0; for (int i = 0; i < s; i++) { int k = b + i; int byte_idx = k / 8; int bit_idx = 7 - (k % 8); if (buf[byte_idx] & (1 << bit_idx)) v |= ((uint64_t)1 << i); } /* * rcw.py line 874: if b == bb: v = reverse * This applies when begin <= end (non-classic ordering). */ if (b == bb) v = reverse_bits(v, s); return v; } vjardin-libqoriq-rcw-175bf56/src/rcw_context.c000066400000000000000000000251121526740601600214230ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "rcw_internal.h" rcw_ctx_t * rcw_ctx_new(void) { rcw_ctx_t *ctx = calloc(1, sizeof(*ctx)); if (!ctx) return NULL; ctx->pbl = true; ctx->vars.pbladdr = RCW_DEFAULT_PBLADDR; /* rcw.py default */ return ctx; } void rcw_ctx_free(rcw_ctx_t *ctx) { if (!ctx) return; for (size_t i = 0; i < ctx->include_path_count; i++) free(ctx->include_paths[i]); for (size_t i = 0; i < ctx->define_count; i++) free(ctx->defines[i]); free(ctx->pbi.data); free(ctx); } rcw_error_t rcw_ctx_add_include_path(rcw_ctx_t *ctx, const char *path) { if (ctx->include_path_count >= RCW_MAX_INCLUDE_PATHS) return RCW_ERR_NOMEM; ctx->include_paths[ctx->include_path_count] = strdup(path); if (!ctx->include_paths[ctx->include_path_count]) return RCW_ERR_NOMEM; ctx->include_path_count++; return RCW_OK; } /* [A-Za-z_][A-Za-z0-9_]*: what mcpp -D takes as a macro name */ static size_t identifier_len(const char *s) { size_t n = 0; if (!s || !(isalpha((unsigned char)s[0]) || s[0] == '_')) return 0; while (isalnum((unsigned char)s[n]) || s[n] == '_') n++; return n; } rcw_error_t rcw_ctx_add_define(rcw_ctx_t *ctx, const char *name, const char *value) { size_t n = identifier_len(name); /* the name alone, or "NAME=VALUE" when no value comes separately */ if (n == 0 || (name[n] != '\0' && (name[n] != '=' || value))) return RCW_ERR_PARSE; if (ctx->define_count >= RCW_MAX_DEFINES) return RCW_ERR_NOMEM; /* one mcpp argument: "-Dname" or "-Dname=value" */ size_t len = strlen("-D") + strlen(name) + (value ? 1 + strlen(value) : 0) + 1; char *arg = malloc(len); if (!arg) return RCW_ERR_NOMEM; if (value) snprintf(arg, len, "-D%s=%s", name, value); else snprintf(arg, len, "-D%s", name); ctx->defines[ctx->define_count++] = arg; return RCW_OK; } rcw_error_t rcw_ctx_set_pbl(rcw_ctx_t *ctx, int enable) { ctx->pbl = enable != 0; return RCW_OK; } rcw_error_t rcw_ctx_set_warnings(rcw_ctx_t *ctx, int enable) { ctx->warnings = enable != 0; return RCW_OK; } rcw_error_t rcw_ctx_set_bitfield(rcw_ctx_t *ctx, const char *name, uint64_t value) { if (ctx->override_count >= RCW_MAX_OVERRIDES) return RCW_ERR_NOMEM; snprintf(ctx->overrides[ctx->override_count].name, RCW_FIELD_NAME_MAX, "%s", name); ctx->overrides[ctx->override_count].value = value; ctx->override_count++; return RCW_OK; } rcw_error_t rcw_ctx_set_bitfield_text(rcw_ctx_t *ctx, const char *text) { const char *eq = text ? strchr(text, '=') : NULL; size_t n = eq ? (size_t)(eq - text) : 0; char name[RCW_FIELD_NAME_MAX]; const char *end; uint64_t value; if (n == 0 || n >= sizeof(name)) { rcw_set_error(ctx, "Expected NAME=VALUE: %s", text ? text : "(null)"); return RCW_ERR_PARSE; } for (size_t i = 0; i < n; i++) { if (!(isalnum((unsigned char)text[i]) || text[i] == '_')) { rcw_set_error(ctx, "Bad bitfield name in %s", text); return RCW_ERR_PARSE; } } /* python like: int(value, 0), behind a regex that passes [0-9a-zA-Z]+ only */ if (eq[1] == '\0' || rcw_parse_literal(eq + 1, &end, &value) != RCW_OK || *end != '\0') { rcw_set_error(ctx, "Invalid value for %.*s: %s", (int)n, text, eq + 1); return RCW_ERR_PARSE; } memcpy(name, text, n); name[n] = '\0'; return rcw_ctx_set_bitfield(ctx, name, value); } static void ctx_reset_state(rcw_ctx_t *ctx) { memset(&ctx->symbols, 0, sizeof(ctx->symbols)); memset(&ctx->vars, 0, sizeof(ctx->vars)); ctx->vars.pbladdr = RCW_DEFAULT_PBLADDR; free(ctx->pbi.data); memset(&ctx->pbi, 0, sizeof(ctx->pbi)); ctx->label_count = 0; ctx->fixup_count = 0; ctx->error_detail[0] = '\0'; } static rcw_error_t ctx_apply_overrides(rcw_ctx_t *ctx) { for (size_t i = 0; i < ctx->override_count; i++) { const char *name = ctx->overrides[i].name; uint64_t value = ctx->overrides[i].value; bool found = false; for (size_t j = 0; j < ctx->symbols.count; j++) { if (strcmp(ctx->symbols.entries[j].name, name) == 0) { ctx->symbols.entries[j].has_value = true; ctx->symbols.entries[j].value = value; found = true; break; } } if (!found) { rcw_set_error(ctx, "Unknown bitfield: %s", name); return RCW_ERR_UNKNOWN_FIELD; } } return RCW_OK; } rcw_error_t rcw_compile_buffer(rcw_ctx_t *ctx, const char *preprocessed, size_t len, uint8_t **out_data, size_t *out_len) { ctx_reset_state(ctx); rcw_error_t err = rcw_parse(ctx, preprocessed, len); if (err != RCW_OK) return err; err = ctx_apply_overrides(ctx); if (err != RCW_OK) return err; if (ctx->vars.pbiformat != 2) { rcw_set_error(ctx, "Only pbiformat=2 is supported (got %d)", ctx->vars.pbiformat); return RCW_ERR_UNSUPPORTED; } if (ctx->pbl && ctx->vars.has_sysaddr) { rcw_set_error(ctx, "PBL format does not use %%sysaddr"); return RCW_ERR_PARSE; } if (ctx->vars.size != RCW_SIZE_BITS) { rcw_set_error(ctx, "%%size must be %d (got %d)", RCW_SIZE_BITS, ctx->vars.size); return RCW_ERR_MISSING_VAR; } return rcw_binary_generate(ctx, out_data, out_len); } rcw_error_t rcw_compile_file(rcw_ctx_t *ctx, const char *input_path, uint8_t **out_data, size_t *out_len) { char *preprocessed = NULL; size_t pp_len = 0; rcw_error_t err = rcw_preprocess(ctx, input_path, &preprocessed, &pp_len); if (err != RCW_OK) return err; err = rcw_compile_buffer(ctx, preprocessed, pp_len, out_data, out_len); free(preprocessed); return err; } /* * Prepend "#include \n\n" to body, matching rcw.py line 814: * source = '#include <%s>\n\n' % os.path.basename(options.rcwi) * Frees body unconditionally and returns the new buffer in *out / *out_len. */ static rcw_error_t prepend_include(char *body, size_t body_len, const char *name, char **out, size_t *out_len) { size_t header_len = strlen("#include <") + strlen(name) + strlen(">\n\n"); size_t total = header_len + body_len; char *buf = malloc(total + 1); if (!buf) { free(body); return RCW_ERR_NOMEM; } int n = snprintf(buf, header_len + 1, "#include <%s>\n\n", name); if (n < 0) { free(buf); free(body); return RCW_ERR_IO; } memcpy(buf + (size_t)n, body, body_len); buf[(size_t)n + body_len] = '\0'; free(body); *out = buf; *out_len = (size_t)n + body_len; return RCW_OK; } rcw_error_t rcw_decompile_buffer(rcw_ctx_t *ctx, const char *rcwi_preprocessed, size_t rcwi_len, const uint8_t *binary, size_t binary_len, const char *rcwi_name, char **out_source, size_t *out_len) { ctx_reset_state(ctx); rcw_error_t err = rcw_parse(ctx, rcwi_preprocessed, rcwi_len); if (err != RCW_OK) return err; char *body = NULL; size_t body_len = 0; err = rcw_decompile(ctx, binary, binary_len, &body, &body_len); if (err != RCW_OK) return err; if (rcwi_name == NULL) { *out_source = body; *out_len = body_len; return RCW_OK; } return prepend_include(body, body_len, rcwi_name, out_source, out_len); } rcw_error_t rcw_decompile_file(rcw_ctx_t *ctx, const char *bin_path, const char *rcwi_path, char **out_source, size_t *out_len) { /* Preprocess the .rcwi with mcpp to expand its macros */ char *rcwi_pp = NULL; size_t rcwi_len = 0; rcw_error_t err = rcw_preprocess(ctx, rcwi_path, &rcwi_pp, &rcwi_len); if (err != RCW_OK) return err; /* Read the binary file */ FILE *f = fopen(bin_path, "rb"); if (!f) { free(rcwi_pp); rcw_set_error(ctx, "Cannot open %s", bin_path); return RCW_ERR_IO; } fseek(f, 0, SEEK_END); long fsize = ftell(f); fseek(f, 0, SEEK_SET); uint8_t *binary = malloc((size_t)fsize); if (!binary) { fclose(f); free(rcwi_pp); return RCW_ERR_NOMEM; } if (fread(binary, 1, (size_t)fsize, f) != (size_t)fsize) { fclose(f); free(binary); free(rcwi_pp); rcw_set_error(ctx, "Failed to read %s", bin_path); return RCW_ERR_IO; } fclose(f); /* * Compute basename(rcwi_path) for the #include header. * basename(3) may modify its argument, so work on a copy. */ char *rcwi_copy = strdup(rcwi_path); if (!rcwi_copy) { free(binary); free(rcwi_pp); return RCW_ERR_NOMEM; } const char *base = basename(rcwi_copy); err = rcw_decompile_buffer(ctx, rcwi_pp, rcwi_len, binary, (size_t)fsize, base, out_source, out_len); free(rcwi_copy); free(binary); free(rcwi_pp); return err; } rcw_error_t rcw_preprocess_file(rcw_ctx_t *ctx, const char *input_path, char **out, size_t *out_len) { if (!ctx || !input_path || !out || !out_len) return RCW_ERR_IO; ctx->error_detail[0] = '\0'; return rcw_preprocess(ctx, input_path, out, out_len); } const char * rcw_strerror(rcw_error_t err) { switch (err) { case RCW_OK: return "Success"; case RCW_ERR_NOMEM: return "Out of memory"; case RCW_ERR_IO: return "I/O error"; case RCW_ERR_PREPROCESS: return "Preprocessing failed"; case RCW_ERR_PARSE: return "Parse error"; case RCW_ERR_OVERLAP: return "Bitfield overlap"; case RCW_ERR_UNKNOWN_FIELD: return "Unknown bitfield"; case RCW_ERR_VALUE_OVERFLOW: return "Value too large for field"; case RCW_ERR_PBI_SYNTAX: return "PBI syntax error"; case RCW_ERR_MISSING_VAR: return "Required variable not set"; case RCW_ERR_BAD_BINARY: return "Invalid binary format"; case RCW_ERR_UNSUPPORTED: return "Unsupported feature"; case RCW_ERR_TRUNCATED: return "Command runs past the end of the data"; case RCW_ERR_PBI_LENGTH: return "No Stop or CRC and Stop command at RCW[PBI_LENGTH]"; case RCW_ERR_CHECKSUM: return "Load RCW checksum mismatch"; case RCW_ERR_CRC: return "PBI CRC mismatch"; case RCW_ERR_PBI_OVERRUN: return "PBI command runs past RCW[PBI_LENGTH]"; case RCW_ERR_PBI_JUMP: return "PBI Jump offset not a multiple of 4 above 8, or beyond RCW[PBI_LENGTH]"; case RCW_ERR_PBI_NO_CONDITION: return "PBI Jump Conditional before any Load Condition"; case RCW_ERR_PBI_CONDITIONAL: return "PBI Jump Conditional: the path taken depends on hardware state"; } return "Unknown error"; } const char * rcw_ctx_last_error_detail(const rcw_ctx_t *ctx) { return ctx->error_detail; } void rcw_free(void *ptr) { free(ptr); } vjardin-libqoriq-rcw-175bf56/src/rcw_crc32.c000066400000000000000000000020221526740601600206460ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Custom CRC-32 matching rcw.py (polynomial 0x04C11DB7). * NOT compatible with zlib/binascii crc32. */ #include #include #include "rcw_internal.h" uint32_t rcw_crc32_update(uint32_t crc, const uint8_t *data, size_t len, bool reverse_bits) { /* Build the CRC table (matches rcw.py lines 125-132) */ uint32_t table[RCW_CRC32_TABLE_SIZE]; for (unsigned i = 0; i < RCW_CRC32_TABLE_SIZE; i++) { uint32_t mask = (uint32_t)i << 24; for (int j = 0; j < RCW_CRC32_BITS_PER_BYTE; j++) { if (mask & 0x80000000) mask = (mask << 1) ^ RCW_CRC32_POLY; else mask <<= 1; } table[i] = mask; } for (size_t i = 0; i < len; i++) { uint8_t b = reverse_bits ? rcw_reverse_byte(data[i]) : data[i]; crc = (crc << 8) ^ table[(crc >> 24) ^ b]; } return crc; } uint32_t rcw_crc32(const uint8_t *data, size_t len) { return rcw_crc32_update(RCW_CRC32_INIT, data, len, false); } vjardin-libqoriq-rcw-175bf56/src/rcw_csf.c000066400000000000000000000066671526740601600205300ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Helper for the Load Security Header PBI command (RM Table 41), which * carries 20 CSF words. Those words are produced by NXP's Code Signing * Tool, so they are normally kept in a file rather than pasted into the * .rcw source but let's be feature complete. */ #include #include #include #include #include "rcw_internal.h" /* Open path, honouring the ctx include search list. Caller fcloses. */ static FILE * csf_fopen(rcw_ctx_t *ctx, const char *path) { FILE *f = fopen(path, "r"); if (f) return f; for (size_t i = 0; i < ctx->include_path_count; i++) { char buf[RCW_PATH_MAX]; int n = snprintf(buf, sizeof(buf), "%s/%s", ctx->include_paths[i], path); if (n <= 0 || (size_t)n >= sizeof(buf)) continue; f = fopen(buf, "r"); if (f) return f; } return NULL; } /* * Read exactly PBI_CSF_HEADER_WORDS hex words from `path` into `out`. * * Strict on count in both directions: too few means a truncated CSF header * and too many means the wrong file entirely, and either would otherwise be * encoded into a PBL that the Service Processor rejects at boot with * nothing to explain why. */ rcw_error_t rcw_csf_read_words(rcw_ctx_t *ctx, const char *path, uint32_t *out, size_t want) { FILE *f = csf_fopen(ctx, path); if (!f) { rcw_set_error(ctx, "cannot open CSF header file \"%s\" " "(searched the working directory and %zu include path(s))", path, ctx->include_path_count); return RCW_ERR_PBI_SYNTAX; } size_t n = 0; int c; char tok[32]; while ((c = fgetc(f)) != EOF) { /* Line comments: # ... and // ... */ if (c == '#') { while ((c = fgetc(f)) != EOF && c != '\n') ; continue; } if (c == '/') { int c2 = fgetc(f); if (c2 == '/') { while ((c = fgetc(f)) != EOF && c != '\n') ; continue; } if (c2 != EOF) ungetc(c2, f); rcw_set_error(ctx, "stray '/' in CSF header file \"%s\"", path); fclose(f); return RCW_ERR_PBI_SYNTAX; } if (isspace(c) || c == ',') continue; /* Collect one token. */ size_t len = 0; do { if (len + 1 >= sizeof(tok)) { rcw_set_error(ctx, "over-long token in CSF header file \"%s\"", path); fclose(f); return RCW_ERR_PBI_SYNTAX; } tok[len++] = (char)c; c = fgetc(f); } while (c != EOF && !isspace(c) && c != ','); tok[len] = '\0'; if (n >= want) { rcw_set_error(ctx, "CSF header file \"%s\" holds more than %zu words", path, want); fclose(f); return RCW_ERR_PBI_SYNTAX; } char *end = NULL; unsigned long v = strtoul(tok, &end, 16); /* always hex, 0x optional */ if (!end || *end != '\0') { rcw_set_error(ctx, "\"%s\" in CSF header file \"%s\" is not a hex word", tok, path); fclose(f); return RCW_ERR_PBI_SYNTAX; } if (v > 0xFFFFFFFFul) { rcw_set_error(ctx, "\"%s\" in CSF header file \"%s\" exceeds 32 bits", tok, path); fclose(f); return RCW_ERR_PBI_SYNTAX; } out[n++] = (uint32_t)v; } fclose(f); if (n != want) { rcw_set_error(ctx, "CSF header file \"%s\" holds %zu words, expected exactly %zu", path, n, want); return RCW_ERR_PBI_SYNTAX; } return RCW_OK; } vjardin-libqoriq-rcw-175bf56/src/rcw_decode.c000066400000000000000000000176421526740601600211730ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * PBI command decoder */ #include #include "rcw_internal.h" /* * Operand words of a general command, from its CMD byte * Load RCW is listed for completeness: it never appears in a * PBI sequence, it precedes it. */ static bool general_layout(uint8_t cmd, rcw_pbi_op_t *op, size_t *nargs) { switch (cmd) { case PBI_GENCMD_BLOCKCOPY: *op = RCW_PBI_BLOCKCOPY; *nargs = 3; return true; case PBI_GENCMD_CCSR_COPY: *op = RCW_PBI_CCSR_COPY; *nargs = 3; return true; case PBI_GENCMD_LOAD_RCW_CS: *op = RCW_PBI_LOAD_RCW; *nargs = PBL_LOAD_RCW_WORDS; return true; case PBI_GENCMD_LOAD_RCW_NOCS: *op = RCW_PBI_LOAD_RCW_NOCS; *nargs = PBL_LOAD_RCW_WORDS; return true; case PBI_GENCMD_LOADACWINDOW: *op = RCW_PBI_LOADACWINDOW; *nargs = 0; return true; case PBI_GENCMD_LOADCONDITION: *op = RCW_PBI_LOADCONDITION; *nargs = 2; return true; case PBI_GENCMD_LOAD_SEC_HDR: *op = RCW_PBI_LOAD_SEC_HDR; *nargs = PBI_CSF_HEADER_WORDS; return true; case PBI_GENCMD_LOAD_CSF_PTR: *op = RCW_PBI_LOAD_CSF_PTR; *nargs = 1; return true; case PBI_GENCMD_CCSR_RMW: *op = RCW_PBI_CCSR_RMW; *nargs = 3; return true; case PBI_GENCMD_POLL_SHORT: *op = RCW_PBI_POLL_SHORT; *nargs = 3; return true; case PBI_GENCMD_POLL_LONG: *op = RCW_PBI_POLL_LONG; *nargs = 3; return true; case PBI_GENCMD_WAIT: *op = RCW_PBI_WAIT; *nargs = 0; return true; case PBI_GENCMD_JUMP: *op = RCW_PBI_JUMP; *nargs = 1; return true; case PBI_GENCMD_JUMP_COND: *op = RCW_PBI_JUMP_COND; *nargs = 2; return true; case PBI_GENCMD_CRC_STOP: *op = RCW_PBI_CRC_STOP; *nargs = 1; return true; case PBI_GENCMD_STOP: *op = RCW_PBI_STOP; *nargs = 1; return true; } return false; } rcw_error_t rcw_pbi_decode(const uint8_t *buf, size_t len, int littleendian, rcw_pbi_cmd_t *cmd) { memset(cmd, 0, sizeof(*cmd)); cmd->op = RCW_PBI_UNKNOWN; if (len < PBI_WORD_BYTES) return RCW_ERR_TRUNCATED; uint32_t w = rcw_unpack32(buf, littleendian); uint8_t hdr = (uint8_t)(w >> PBI_HDR_SHIFT); cmd->word = w; if ((hdr & (PBI_HDR_ACS | PBI_HDR_RSVD)) == 0) { uint32_t b = (hdr & PBI_CCSR_TYPE_MASK) >> PBI_CCSR_TYPE_SHIFT; if (b != PBI_CCSR_WRITE_B1 && b != PBI_CCSR_WRITE_B4) return RCW_ERR_BAD_BINARY; cmd->op = RCW_PBI_CCSR_WRITE; cmd->nargs = 1; cmd->param = w & PBI_CCSR_ADDR_MASK; cmd->width = 1u << (b - 1); /* N = 2^(B-1): 1 or 4 bytes */ } else if ((hdr & (PBI_HDR_ACS | PBI_HDR_RSVD)) == PBI_HDR_ACS) { uint32_t b = (hdr & PBI_AWRITE_B_MASK) >> PBI_AWRITE_B_RSHIFT; if (b != 0) { if (b > PBI_AWRITE_B_MAX) return RCW_ERR_BAD_BINARY; cmd->op = RCW_PBI_ALTCFG_WRITE; cmd->width = 1u << (b - 1); cmd->nargs = (cmd->width + PBI_WORD_BYTES - 1) / PBI_WORD_BYTES; cmd->param = w & PBI_AWRITE_ADDR_MASK; } else if (hdr == PBI_HDR_GENERAL) { if (!general_layout((uint8_t)((w & PBI_CMD_MASK) >> PBI_CMD_SHIFT), &cmd->op, &cmd->nargs)) return RCW_ERR_BAD_BINARY; cmd->param = w & PBI_PARAM_MASK; } else { return RCW_ERR_BAD_BINARY; } } else { return RCW_ERR_BAD_BINARY; } cmd->size = PBI_WORD_BYTES * (1 + cmd->nargs); /* the command word and its operands */ if (cmd->size > len) return RCW_ERR_TRUNCATED; for (size_t i = 0; i < cmd->nargs; i++) cmd->args[i] = rcw_unpack32(buf + PBI_WORD_BYTES * (1 + i), littleendian); return RCW_OK; } const char * rcw_pbi_op_name(rcw_pbi_op_t op) { switch (op) { case RCW_PBI_UNKNOWN: break; case RCW_PBI_BLOCKCOPY: return "blockcopy"; case RCW_PBI_CCSR_COPY: return "ccsrcopy"; case RCW_PBI_LOAD_RCW: return "loadrcw"; case RCW_PBI_LOAD_RCW_NOCS: return "loadrcw.nochecksum"; case RCW_PBI_LOADACWINDOW: return "loadacwindow"; case RCW_PBI_LOADCONDITION: return "loadc"; case RCW_PBI_LOAD_SEC_HDR: return "loadsecurityheader"; case RCW_PBI_LOAD_CSF_PTR: return "loadboot1csfptr"; case RCW_PBI_CCSR_RMW: return "ccsrrmw"; case RCW_PBI_POLL_SHORT: return "poll.short"; case RCW_PBI_POLL_LONG: return "poll.long"; case RCW_PBI_WAIT: return "wait"; case RCW_PBI_JUMP: return "jump"; case RCW_PBI_JUMP_COND: return "jumpc"; case RCW_PBI_CRC_STOP: return "crcstop"; case RCW_PBI_STOP: return "stop"; case RCW_PBI_CCSR_WRITE: return "write"; case RCW_PBI_ALTCFG_WRITE: return "awrite"; } return "unknown"; } typedef struct { char *buf; size_t size; size_t len; } source_out_t; __attribute__((format(printf, 2, 3))) static void out_printf(source_out_t *o, const char *fmt, ...) { size_t room = o->len < o->size ? o->size - o->len : 0; va_list ap; int n; va_start(ap, fmt); n = vsnprintf(room ? o->buf + o->len : NULL, room, fmt, ap); va_end(ap); if (n > 0) o->len += (size_t)n; } int rcw_pbi_to_source(const rcw_pbi_cmd_t *c, char *buf, size_t size) { static const char *wsuf[4] = { "?", "b1", "b2", "b4" }; static const char *opnm[4] = { "ccsrset", "ccsrclear", "ccsrreplace", "ccsrreserved" }; source_out_t o = { .buf = buf, .size = size, .len = 0 }; if (size > 0) buf[0] = '\0'; switch (c->op) { case RCW_PBI_CCSR_WRITE: out_printf(&o, "%s 0x%08x,0x%08x", c->width == 1 ? "write.b1" : "write", c->param, c->args[0]); break; case RCW_PBI_ALTCFG_WRITE: { uint32_t b = (c->word >> PBI_AWRITE_B_SHIFT) & PBI_AWRITE_B_BITS; if (b == PBI_AWRITE_B3) out_printf(&o, "awrite 0x%08x", c->param); else out_printf(&o, "awrite.b%u 0x%08x", b, c->param); for (size_t k = 0; k < c->nargs; k++) out_printf(&o, ",0x%08x", c->args[k]); break; } case RCW_PBI_BLOCKCOPY: out_printf(&o, "blockcopy 0x%02x,0x%08x,0x%08x,0x%08x", c->param & PBI_BLOCKCOPY_SRC_MAX, c->args[0], c->args[1], c->args[2]); break; case RCW_PBI_CCSR_COPY: out_printf(&o, "ccsrcopy.%s 0x%08x,0x%08x,0x%08x", wsuf[c->param & PBI_CCSR_COPY_TYPE_MASK], c->args[0], c->args[1], c->args[2]); break; case RCW_PBI_CCSR_RMW: { uint32_t mt = c->param & PBI_RMW_MASK_TYPE_MASK; uint32_t ops = (c->param >> PBI_RMW_OPS_SHIFT) & PBI_RMW_OPS_MASK; if (ops == PBI_RMW_OPS_REPLACE) out_printf(&o, "%s.%s 0x%08x,0x%08x,0x%08x", opnm[ops], wsuf[mt], c->args[0], c->args[1], c->args[2]); else out_printf(&o, "%s.%s 0x%08x,0x%08x", opnm[ops], wsuf[mt], c->args[0], c->args[1]); break; } case RCW_PBI_LOAD_RCW: case RCW_PBI_LOAD_RCW_NOCS: out_printf(&o, "/* Disassemble not implemented for word 0x%08x */", c->word); break; case RCW_PBI_LOADACWINDOW: out_printf(&o, "loadacwindow 0x%08x", c->param & PBI_ACWINDOW_MAX); break; case RCW_PBI_LOADCONDITION: out_printf(&o, "loadc 0x%08x,0x%08x", c->args[0], c->args[1]); break; case RCW_PBI_LOAD_SEC_HDR: out_printf(&o, "loadsecurityheader "); for (size_t k = 0; k < c->nargs; k++) out_printf(&o, "%s0x%08x", (k == 0) ? "" : ",", c->args[k]); break; case RCW_PBI_LOAD_CSF_PTR: out_printf(&o, "loadboot1csfptr 0x%08x", c->args[0]); break; case RCW_PBI_POLL_SHORT: case RCW_PBI_POLL_LONG: out_printf(&o, "%s 0x%08x,0x%08x,0x%08x", rcw_pbi_op_name(c->op), c->args[0], c->args[1], c->args[2]); break; case RCW_PBI_WAIT: out_printf(&o, "wait 0x%08x", c->param); break; case RCW_PBI_JUMP: out_printf(&o, "jump 0x%08x", c->args[0]); break; case RCW_PBI_JUMP_COND: out_printf(&o, "jumpc 0x%08x,0x%08x", c->args[0], c->args[1]); break; case RCW_PBI_CRC_STOP: out_printf(&o, "/* CRC and Stop command (CRC 0x%08x)*/", c->args[0]); break; case RCW_PBI_STOP: out_printf(&o, "/* Stop command */"); break; case RCW_PBI_UNKNOWN: out_printf(&o, "/* Unknown word 0x%08x */", c->word); break; } return (int)o.len; } vjardin-libqoriq-rcw-175bf56/src/rcw_decompile.c000066400000000000000000000167171526740601600217130ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Reverse decompilation: .bin -> .rcw source text. * Mirrors rcw.py create_source() (lines 763-1046), pbiformat=2 only. */ #include #include #include #include #include "rcw_internal.h" /* Dynamic string buffer for building source output. */ typedef struct { char *data; size_t len; size_t cap; } strbuf_t; static rcw_error_t strbuf_init(strbuf_t *sb) { sb->cap = 4096; sb->len = 0; sb->data = malloc(sb->cap); if (!sb->data) return RCW_ERR_NOMEM; sb->data[0] = '\0'; return RCW_OK; } __attribute__((format(printf, 2, 3))) static rcw_error_t strbuf_printf(strbuf_t *sb, const char *fmt, ...) { va_list ap; for (;;) { size_t avail = sb->cap - sb->len; va_start(ap, fmt); int n = vsnprintf(sb->data + sb->len, avail, fmt, ap); va_end(ap); if (n < 0) return RCW_ERR_IO; if ((size_t)n < avail) { sb->len += (size_t)n; return RCW_OK; } size_t newcap = sb->cap * 2; while (newcap < sb->len + (size_t)n + 1) newcap *= 2; char *p = realloc(sb->data, newcap); if (!p) return RCW_ERR_NOMEM; sb->data = p; sb->cap = newcap; } } static size_t pbi_step(const uint8_t *pbi, size_t pbi_len, size_t i, int littleendian, rcw_pbi_cmd_t *c, rcw_error_t *err) { *err = rcw_pbi_decode(pbi + i, pbi_len - i, littleendian, c); if (*err != RCW_OK) return PBI_WORD_BYTES; return c->size; } static long jump_target(size_t i, uint32_t offset, size_t pbi_len) { uint64_t t = (uint64_t)i + offset; if (t >= pbi_len || t % PBI_WORD_BYTES != 0) return -1; return (long)(t / PBI_WORD_BYTES); } static int * collect_labels(const uint8_t *pbi, size_t pbi_len, int littleendian) { size_t nwords = pbi_len / PBI_WORD_BYTES; if (nwords == 0) return NULL; int *label_of = malloc(nwords * sizeof(*label_of)); uint8_t *starts = calloc(nwords, 1); if (!label_of || !starts) { free(label_of); free(starts); return NULL; } for (size_t w = 0; w < nwords; w++) label_of[w] = -1; for (size_t i = 0; i + PBI_WORD_BYTES <= pbi_len;) { rcw_pbi_cmd_t c; rcw_error_t err; starts[i / PBI_WORD_BYTES] = 1; size_t step = pbi_step(pbi, pbi_len, i, littleendian, &c, &err); if (err == RCW_OK && (c.op == RCW_PBI_JUMP || c.op == RCW_PBI_JUMP_COND)) { long t = jump_target(i, c.args[0], pbi_len); if (t >= 0) label_of[t] = 0; /* a target; numbered below */ } i += step; } int n = 0; for (size_t w = 0; w < nwords; w++) label_of[w] = (label_of[w] == 0 && starts[w]) ? n++ : -1; free(starts); return label_of; } static int jump_label(const int *label_of, size_t pbi_len, size_t i, uint32_t offset) { long t = jump_target(i, offset, pbi_len); return (label_of && t >= 0) ? label_of[t] : -1; } static rcw_error_t disassemble_pbi(strbuf_t *sb, const uint8_t *pbi, size_t pbi_len, int littleendian) { int *label_of = collect_labels(pbi, pbi_len, littleendian); size_t i = 0; while (i + PBI_WORD_BYTES <= pbi_len) { rcw_pbi_cmd_t c; rcw_error_t err; size_t step = pbi_step(pbi, pbi_len, i, littleendian, &c, &err); if (label_of && label_of[i / PBI_WORD_BYTES] >= 0) strbuf_printf(sb, "L%d:\n", label_of[i / PBI_WORD_BYTES]); if (err == RCW_ERR_TRUNCATED && c.op == RCW_PBI_STOP) { strbuf_printf(sb, "/* Stop command */\n"); i += step; continue; } if (err != RCW_OK) { strbuf_printf(sb, "/* Unknown word 0x%08x */\n", c.word); i += step; continue; } int label = (c.op == RCW_PBI_JUMP || c.op == RCW_PBI_JUMP_COND) ? jump_label(label_of, pbi_len, i, c.args[0]) : -1; if (label >= 0) { if (c.op == RCW_PBI_JUMP) strbuf_printf(sb, "jump L%d\n", label); else strbuf_printf(sb, "jumpc L%d,0x%08x\n", label, c.args[1]); } else { char line[RCW_PBI_SOURCE_MAX]; rcw_pbi_to_source(&c, line, sizeof(line)); strbuf_printf(sb, "%s\n", line); } i += step; } free(label_of); return RCW_OK; } rcw_error_t rcw_decompile(rcw_ctx_t *ctx, const uint8_t *binary, size_t len, char **out_source, size_t *out_len) { strbuf_t sb; rcw_error_t err = strbuf_init(&sb); if (err != RCW_OK) return err; const uint8_t *rcw_data; const uint8_t *pbi_data = NULL; size_t pbi_len = 0; /* Detect and skip preamble */ uint8_t preamble_bytes[4]; if (ctx->vars.littleendian) { preamble_bytes[0] = (uint8_t)(PBL_PREAMBLE & 0xFF); preamble_bytes[1] = (uint8_t)((PBL_PREAMBLE >> 8) & 0xFF); preamble_bytes[2] = (uint8_t)((PBL_PREAMBLE >> 16) & 0xFF); preamble_bytes[3] = (uint8_t)((PBL_PREAMBLE >> 24) & 0xFF); } else { preamble_bytes[0] = (uint8_t)((PBL_PREAMBLE >> 24) & 0xFF); preamble_bytes[1] = (uint8_t)((PBL_PREAMBLE >> 16) & 0xFF); preamble_bytes[2] = (uint8_t)((PBL_PREAMBLE >> 8) & 0xFF); preamble_bytes[3] = (uint8_t)(PBL_PREAMBLE & 0xFF); } /* * Need at least preamble(4) + load-cmd(4) + RCW(128) + checksum(4) * = 140 bytes before we can treat the buffer as PBL-wrapped. The * older check (len > RCW_SIZE_BYTES) accepted any 129+-byte input * starting with the preamble, which then over-read the RCW area. */ size_t pbl_min = RCW_PBL_PBI_OFFSET; if (len >= pbl_min && memcmp(binary, preamble_bytes, sizeof(preamble_bytes)) == 0) { /* Has preamble: the RCW follows the preamble and the Load RCW command word */ rcw_data = binary + RCW_PBL_RCW_OFFSET; /* The PBI follows the checksum word */ size_t pbi_start = RCW_PBL_PBI_OFFSET; if (pbi_start < len) { pbi_data = binary + pbi_start; pbi_len = len - pbi_start; } } else { /* * No preamble: the whole buffer is the raw 128-byte RCW. Anything * shorter cannot be decoded - rcw_bits_extract reads exactly * RCW_SIZE_BYTES from rcw_data. */ if (len < RCW_SIZE_BYTES) { free(sb.data); return RCW_ERR_BAD_BINARY; } rcw_data = binary; } /* Extract field values from RCW bits */ uint8_t covered[RCW_SIZE_BITS / 8] = {0}; for (size_t i = 0; i < ctx->symbols.count; i++) { const rcw_symbol_t *sym = &ctx->symbols.entries[i]; uint64_t v = rcw_bits_extract(rcw_data, sym->begin, sym->end, ctx->vars.classicbitnumbers); int b = (sym->begin < sym->end) ? sym->begin : sym->end; int e = (sym->begin < sym->end) ? sym->end : sym->begin; int s = 1 + e - b; for (int k = b; k <= e; k++) covered[k / 8] |= (uint8_t)(1u << (k % 8)); if (v != 0) { if (s > RCW_DECOMPILE_HEX_THRESHOLD) strbuf_printf(&sb, "%s=0x%lx\n", sym->name, (unsigned long)v); else strbuf_printf(&sb, "%s=%lu\n", sym->name, (unsigned long)v); } } bool unknown = false; for (int k = 0; k < RCW_SIZE_BITS; k++) { if (covered[k / 8] & (1u << (k % 8))) continue; if (rcw_bits_extract(rcw_data, k, k, ctx->vars.classicbitnumbers) == 0) continue; strbuf_printf(&sb, "%s %d", unknown ? "" : "/* Unknown bits in positions:", k); unknown = true; } if (unknown) strbuf_printf(&sb, " */\n"); /* Disassemble PBI if present */ if (pbi_data && pbi_len > 0) { strbuf_printf(&sb, "\n.pbi\n"); err = disassemble_pbi(&sb, pbi_data, pbi_len, ctx->vars.littleendian); if (err != RCW_OK) { free(sb.data); return err; } strbuf_printf(&sb, ".end\n"); } *out_source = sb.data; *out_len = sb.len; return RCW_OK; } vjardin-libqoriq-rcw-175bf56/src/rcw_internal.h000066400000000000000000000467061526740601600215740ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #pragma once #include #include #include #include #include #include #include #include /* * Limits - sized for the largest known .rcwi (ls1028a: 144 entries). */ #define RCW_MAX_SYMBOLS 512 #define RCW_MAX_OVERRIDES 64 #define RCW_MAX_INCLUDE_PATHS 32 #define RCW_MAX_DEFINES 64 #define RCW_FIELD_NAME_MAX 64 #define RCW_ERROR_DETAIL_MAX 512 #define RCW_MAX_LABELS 128 #define RCW_LABEL_NAME_MAX 64 /* * Fixed RCW size for pbiformat=2 platforms. * The RCW is 1024 bits (32 x 32-bit RCWSR registers, 128 bytes). * See LX2160ARM Chapter 4.9.4 stage 13: "stores the 128 bytes of * data to the RCWSR registers within the Device Configuration block." */ #define RCW_SIZE_BITS 1024 #define RCW_SIZE_BYTES (RCW_SIZE_BITS / 8) _Static_assert(RCW_SIZE_BYTES == RCW_PBL_RCW_BYTES, "the public envelope disagrees with the RCW size"); /* * Every PBI command is a whole number of 32-bit words: the * command word, then its operand words. */ #define PBI_WORD_BYTES 4 /* * Load RCW carries the 32 RCW words and then the checksum word, * 33 operands: 128 / 4 + 1. PBL_RCW_WORDS is also the index of the checksum * among them. */ #define PBL_RCW_WORDS (RCW_SIZE_BYTES / PBI_WORD_BYTES) #define PBL_LOAD_RCW_WORDS (PBL_RCW_WORDS + 1) /* * CRC-32 parameters for PBI command validation. * See LX2160ARM Section 8.2.6 "CRC Checking of PBI Commands": * Polynomial: 1+X^1+X^2+X^4+X^5+X^7+X^8+X^10+X^11+X^12+X^16+ * X^22+X^23+X^26+X^32 * Initial condition: 0xFFFF_FFFF * Final XOR: 0x0000_0000 */ #define RCW_CRC32_POLY 0x04c11db7 #define RCW_CRC32_INIT 0xffffffff #define RCW_CRC32_TABLE_SIZE 256 #define RCW_CRC32_BITS_PER_BYTE 8 /* * PBL preamble - identifies a valid PBI image. * See LX2160ARM Section 8.3.1 "Preamble": * "The preamble is a 4-byte pattern arbitrarily defined as * 0xAA55_AA55." Must be the first record in the PBI image. * The Boot 0 Reset Handler generates an error if the pattern * is not found (error code 0x50 ERROR_PREAMBLE). */ #define PBL_PREAMBLE 0xAA55AA55 /* * PBI general command format (LX2160ARM Section 8.2.1, Table 27): * Word 0: [31:24]=HDR [23:16]=CMD [15:0]=varies * * All general commands have HDR=0x80 (ACS=1, bit 31 set). * The CMD byte identifies the specific operation. */ /* * Load RCW commands (Section 8.3.5, Table 38): * CMD=0x10: Load RCW with 32-bit checksum validation * CMD=0x11: Load RCW without checksum (padding with zeroes) * Checksum = unsigned 32-bit sum of preamble + Load RCW command + * 32 RCW words (Section 8.3.5 pseudocode). */ #define PBL_CMD_LOAD_RCW 0x80100000 #define PBL_CMD_LOAD_RCW_NOCS 0x80110000 /* * Stop command (Section 8.3.16, Table 49): * CMD=0xFF: ends PBI sequence immediately, no CRC check. * Followed by 4 bytes of zero padding. */ #define PBL_CMD_STOP 0x80FF0000 /* * CRC and Stop command (Section 8.3.15, Table 48): * CMD=0x8F: ends PBI sequence with CRC-32 validation. * "CRC covers all commands from the first command after the RCW * up to and including this CRC and Stop command." */ #define PBL_CMD_CRC_STOP 0x808F0000 /* * Wait command (Section 8.3.12, Table 45): * CMD=0x82, low 16 bits = iteration count. * "Forces the PBI sequence to wait for an approximate amount of * time counted by iterations through a tight loop." */ #define PBL_CMD_WAIT 0x80820000 /* * Poll commands (Section 8.3.11, Table 44): * CMD=0x80: Poll Short - timeout after 100,000 accesses (~25 ms) * CMD=0x81: Poll Long - timeout after 10,000,000 accesses (~2.5 s) * "Repeated reads of a specified address until a specified condition * is found. A mask is applied to both the read result and condition." */ #define PBL_CMD_POLL_SHORT 0x80800000 #define PBL_CMD_POLL_LONG 0x80810000 /* * Load Alternate Configuration Window (Section 8.3.6, Table 39): * CMD=0x12, low 14 bits = ALTCFG_WNDW. * Sets up a 64 MB ATU translation window at SP address 0x8000_0000 * so that AltConfig Write commands can reach the full 40-bit system * address space. */ #define PBL_CMD_LOADACWINDOW 0x80120000 /* * Block Copy command (Section 8.3.3, Table 36): * HDR=0x80, CMD=0x00, low 8 bits = SRC interface encoding. * "Used to move larger blocks of data efficiently from non-volatile * memory to on-chip RAM (OCRAM)." * Format: {HDR,CMD,SRC}, SRC_ADDR, DEST_ADDR, BLOCK_SIZE */ #define PBL_CMD_BLOCKCOPY 0x80000000 /* * General command bit - ACS=1 (bit 31 of the header word). * Used by AltConfig Write commands (Section 8.3.2.2, Table 34): * "ACS=1 and B[3-0] != 0" * Also the base for all general commands (HDR=0x80). */ #define PBL_CMD_GENERAL 0x80000000 /* * Flush command - legacy pbiformat=0/1 only. * Encoded as a CCSR write to the PBL address space that triggers * a read-back of the last write address to force completion. * Not used in pbiformat=2 but kept for compatibility. */ #define PBL_CMD_FLUSH 0x09000000 /* * CCSR Write command header encoding (Section 8.3.2.1, Table 32-33): * Byte 0: [7]=ACS(0), [6]=RSVD(0), [5:4]=B[1:0], [3:0]=ADDR[27:24] * B encodes the byte count: N = 2^(B-1). * B=1 (0b01): 1-byte write (I2C, UART registers) * B=3 (0b11): 4-byte write (standard CCSR register write) * B=0,2: reserved/illegal * The B field occupies bits [29:28] of the full 32-bit command word. */ #define PBI_WRITE_4BYTE 3 /* B=0b11: 4-byte CCSR write */ #define PBI_WRITE_1BYTE 1 /* B=0b01: 1-byte CCSR write */ #define PBI_WRITE_SHIFT 28 /* B field position in word */ /* * AltConfig Write B field (Section 8.3.2.2, Table 34-35): * Byte 0: [7]=ACS(1), [6]=RSVD(0), [5:2]=B[3:0], [1:0]=OFFSET[25:24] * Byte count N = 2^(B-1). B in range 1-10 (1 to 512 bytes). * B=3: 4 bytes (1 data word) * B=4: 8 bytes (2 data words) * B=5: 16 bytes (4 data words) * The B field occupies bits [27:26] of the full 32-bit command word * (after masking ACS at bit 31). */ #define PBI_AWRITE_B_MIN 1 /* 1 byte */ #define PBI_AWRITE_B3 3 /* 4 bytes, a bare "awrite" */ #define PBI_AWRITE_B4 4 #define PBI_AWRITE_B5 5 #define PBI_AWRITE_B_MAX 10 /* 512 bytes */ #define PBI_AWRITE_B_SHIFT 26 /* * PBI terminator size: Stop or CRC-and-Stop commands are both 2 words * (8 bytes): the command word + the CRC value or zero padding. * See Tables 48-49 in Section 8.3.15-8.3.16. */ #define PBL_TERMINATOR_WORDS 2 #define PBL_TERMINATOR_BYTES (PBL_TERMINATOR_WORDS * PBI_WORD_BYTES) /* * LX2160A Service Processor scratch ceiling for the total PBL image * (RCW + preamble + PBI + terminator): undocumented in the LX2160A * Reference Manual, but the boundary has been tested manually: * * 4092 bytes -> boots * 4096 bytes -> SILENT * * Other Layerscape SoCs (LS1028, LS1088, LS2088) may have * different ceilings. I don't have test evidence; treat this value * as the LX2160A-conservative threshold. */ #define PBL_SP_SCRATCH_CEILING_BYTES 4096 /* * PBI command word field masks for decoding (decompile). * Word 0 layout (general commands): [31:24]=HDR [23:16]=CMD [15:0]=param */ #define PBI_HDR_MASK 0xFF000000 #define PBI_HDR_SHIFT 24 #define PBI_CMD_MASK 0x00FF0000 #define PBI_CMD_SHIFT 16 #define PBI_PARAM_MASK 0x0000FFFF /* * Header byte: * - bit 7 is ACS (bit 31 of the word, the one PBL_CMD_GENERAL sets), * - bit 6 is reserved and always 0. * A general command has the header 0x80: ACS set and every other bit clear. */ #define PBI_HDR_ACS 0x80 #define PBI_HDR_RSVD 0x40 #define PBI_HDR_GENERAL PBI_HDR_ACS /* CCSR Write: 28-bit address in bits [27:0] (Section 8.3.2.1). */ #define PBI_CCSR_ADDR_MASK 0x0FFFFFFF /* AltConfig Write: 26-bit offset in bits [25:0] (Section 8.3.2.2). */ #define PBI_AWRITE_ADDR_MASK 0x03FFFFFF /* AltConfig Write: B field in header byte, bits [5:2] (Section 8.3.2.2). */ #define PBI_AWRITE_B_MASK 0x3C #define PBI_AWRITE_B_RSHIFT 2 /* The 4-bit B field once shifted down: bits [29:26] of the word, PBI_AWRITE_B_SHIFT. */ #define PBI_AWRITE_B_BITS (PBI_AWRITE_B_MASK >> PBI_AWRITE_B_RSHIFT) /* * Bounds of the fields the encoder fills in a command word, and the masks * the decompiler reads them back with. Anything past these values * OR-overflows into the command-type bits and silently corrupts the word. * * write/write.b1 : CCSR address [27:0] * awrite[.b3-b5] : AltConfig offset [25:0] * loadacwindow : ALTCFG_WNDW [13:0] * blockcopy SRC : interface byte [7:0] */ #define PBI_CCSR_ADDR_MAX 0x0FFFFFFFu #define PBI_AWRITE_ADDR_MAX 0x03FFFFFFu #define PBI_ACWINDOW_MAX 0x00003FFFu #define PBI_BLOCKCOPY_SRC_MAX 0x000000FFu /* CCSR Write: B field in header byte, bits [5:4] (Section 8.3.2.1). */ #define PBI_CCSR_TYPE_MASK 0x30 #define PBI_CCSR_TYPE_SHIFT 4 /* * PBI general command numbers - CMD byte (bits [23:16] of word 0). * See LX2160ARM Section 8.2.1, Table 27 "PBI Command Summary". */ #define PBI_GENCMD_BLOCKCOPY 0x00 /* Section 8.3.3: Block Copy */ #define PBI_GENCMD_CCSR_COPY 0x02 /* Section 8.3.4: CCSR Write from Address */ #define PBI_GENCMD_CCSR_RMW 0x42 /* Section 8.3.10: CCSR Read-Modify-Write */ #define PBI_GENCMD_LOAD_RCW_CS 0x10 /* Section 8.3.5: Load RCW w/ checksum */ #define PBI_GENCMD_LOAD_RCW_NOCS 0x11 /* Section 8.3.5: Load RCW w/o checksum */ #define PBI_GENCMD_LOADACWINDOW 0x12 /* Section 8.3.6: Load Alt Cfg Window */ #define PBI_GENCMD_LOADCONDITION 0x14 /* Section 8.3.7: Load Condition */ #define PBI_GENCMD_LOAD_SEC_HDR 0x20 /* Section 8.3.8: Load Security Header */ #define PBI_GENCMD_LOAD_CSF_PTR 0x22 /* Section 8.3.9: Load Boot 1 CSF Hdr Ptr */ #define PBI_GENCMD_POLL_SHORT 0x80 /* Section 8.3.11: Poll Short (100k iters) */ #define PBI_GENCMD_POLL_LONG 0x81 /* Section 8.3.11: Poll Long (10M iters) */ #define PBI_GENCMD_WAIT 0x82 /* Section 8.3.12: Wait N iterations */ #define PBI_GENCMD_JUMP 0x84 /* Section 8.3.13: Jump forward */ #define PBI_GENCMD_JUMP_COND 0x85 /* Section 8.3.14: Conditional Jump */ #define PBI_JUMP_MIN_OFFSET 8 #define PBI_GENCMD_CRC_STOP 0x8F /* Section 8.3.15: CRC and Stop */ #define PBI_GENCMD_STOP 0xFF /* Section 8.3.16: Stop (no CRC) */ /* * CCSR Write B-field values for decoding (Section 8.3.2.1): * B=0b01 -> 1-byte write, B=0b11 -> 4-byte write. */ #define PBI_CCSR_WRITE_B1 0x1 #define PBI_CCSR_WRITE_B4 0x3 /* * CCSR Write from Address and CCSR Read, Modify, Write * keep their options in the low byte of the command word: * - the word type in bits [1:0] * - the ops type in bits [3:2] * - the mask type in bits [1:0] * Types 1, 2 and 3 are 1, 2 and 4 bytes; 0 is invalid. */ #define PBI_CCSR_COPY_TYPE_MASK 0x3u #define PBI_RMW_MASK_TYPE_MASK 0x3u #define PBI_RMW_OPS_SHIFT 2 #define PBI_RMW_OPS_MASK 0x3u #define PBI_RMW_OPS_SET 0u #define PBI_RMW_OPS_CLEAR 1u #define PBI_RMW_OPS_REPLACE 2u /* * Decompile output: fields wider than this many bits are printed * in hexadecimal; narrower fields use decimal. Matches rcw.py * create_source() behavior: "if s > 8: hex else decimal". */ #define RCW_DECOMPILE_HEX_THRESHOLD 8 /* * Default %pbladdr value. In rcw.py this is initialized to 0x138000 * in the global vars dict. Used by the legacy flush command encoding * (pbiformat=0/1 only), where the PBL address space base is masked * into the command word. */ #define RCW_DEFAULT_PBLADDR 0x138000 #define RCW_PBLADDR_MASK 0x00ffff00 /* PBI buffer initial allocation size (bytes). */ #define RCW_PBI_BUF_INIT_CAP 4096 /* * Operands on one PBI line. The widest command is an AltConfig Write of * 512 bytes (B=10): the offset and 128 data words. Load * Security Header carries 20 CSF words. Everything else * takes at most 5. The cap is enforced per command so that a surplus * operand stays an error instead of being silently dropped. */ #define PBI_MAX_PARAMS (1 + RCW_PBI_MAX_ARGS) #define PBI_AWRITE_MAX_PARAMS (1 + RCW_PBI_MAX_ARGS) #define PBI_STD_MAX_PARAMS 5 #define PBI_CSF_HEADER_WORDS 20 /* Longest path we will build when searching the -I list for a CSF file. */ #define RCW_PATH_MAX 4096 /* * .uboot subsection encoder constants. * * The .uboot subsection contains an xxd hex dump of u-boot.bin (8 * 16-bit words per line, 16 bytes per line). Mirrors rcw.py * build_pbi_uboot() (lines 219-272). * * Lines are grouped: * - Most lines: every 4 lines (64 bytes) -> one block written at * RCW_UBOOT_QUAD_BASE + (line_addr - 0x30). * - Last 4 lines: emitted as 2 pairs (32 bytes each) at * RCW_UBOOT_PAIR_BASE + (line_addr - 0x10). * * Output is always big-endian regardless of %littleendian. * * The base addresses below come from rcw.py's struct.pack('>L', ...) * with values 0x0C1F80000 and 0x081F80000. These are 33-bit values * truncated by struct.pack to 32-bit (high bit is set, but value * fits in uint32). We use the truncated forms directly. */ #define RCW_UBOOT_QUAD_BASE 0x81F80000u /* 64-byte block base */ #define RCW_UBOOT_PAIR_BASE 0xC1F80000u /* 32-byte block base */ #define RCW_UBOOT_LINE_BYTES 16 /* bytes per xxd line */ #define RCW_UBOOT_WORDS_PER_LINE 8 /* 16-bit words/line */ #define RCW_UBOOT_QUAD_LINES 4 /* lines per quad block*/ #define RCW_UBOOT_PAIR_LINES 2 /* lines per pair block*/ #define RCW_UBOOT_TAIL_LINES 4 /* last N lines use pairs */ #define RCW_UBOOT_LINE_MAX 256 /* max chars per line */ /* * Bitfield symbol definition (one entry per NAME[begin:end] declaration). */ typedef struct { char name[RCW_FIELD_NAME_MAX]; int begin; /* first bit position as declared */ int end; /* last bit position as declared */ bool has_value; /* true if an assignment exists */ uint64_t value; /* assigned value */ } rcw_symbol_t; /* * Ordered symbol table - array preserving insertion order. * Linear scan for lookup (few +100s entries, fine without a hash). */ typedef struct { rcw_symbol_t entries[RCW_MAX_SYMBOLS]; size_t count; } rcw_symtab_t; /* * Parsed %variable values. * Typed fields instead of a string map - pbiformat=2 variable set is known. */ typedef struct { int size; /* %size (shall be 1024) */ int pbiformat; /* %pbiformat (shall be 2 for our supported cases) */ bool classicbitnumbers; /* %classicbitnumbers=1 */ bool littleendian; /* %littleendian=1 */ bool nocrc; /* %nocrc=1 */ bool loadwochecksum; /* %loadwochecksum=1 */ uint32_t pbladdr; /* %pbladdr (default 0x138000) */ bool has_sysaddr; /* %sysaddr seen: a legacy PBL header field */ } rcw_vars_t; /* * Dynamic byte buffer for PBI binary output. */ typedef struct { uint8_t *data; size_t len; size_t cap; } rcw_pbi_buf_t; typedef struct { char name[RCW_LABEL_NAME_MAX]; size_t offset; /* byte offset in ctx->pbi */ } rcw_label_t; typedef struct { char name[RCW_LABEL_NAME_MAX]; size_t insn; /* byte offset of the jump command in ctx->pbi */ char op[8]; /* its mnemonic, for the message */ } rcw_fixup_t; /* * Full context. */ struct rcw_ctx { /* Configuration */ char *include_paths[RCW_MAX_INCLUDE_PATHS]; size_t include_path_count; char *defines[RCW_MAX_DEFINES]; /* mcpp arguments, "-Dname=value" */ size_t define_count; bool pbl; /* generate PBL preamble/terminator (default true) */ bool warnings; /* Command-line bitfield overrides (-D) */ struct { char name[RCW_FIELD_NAME_MAX]; uint64_t value; } overrides[RCW_MAX_OVERRIDES]; size_t override_count; /* State populated during parse */ rcw_symtab_t symbols; rcw_vars_t vars; rcw_pbi_buf_t pbi; rcw_label_t labels[RCW_MAX_LABELS]; size_t label_count; rcw_fixup_t fixups[RCW_MAX_LABELS]; size_t fixup_count; /* Error detail buffer */ char error_detail[RCW_ERROR_DETAIL_MAX]; }; /* rcw_csf.c -- read 20 hex CSF words from a file, honouring -I paths. */ rcw_error_t rcw_csf_read_words(rcw_ctx_t *ctx, const char *path, uint32_t *out, size_t want); /* * Internal helpers */ /* rcw_crc32.c */ uint32_t rcw_crc32(const uint8_t *data, size_t len); uint32_t rcw_crc32_update(uint32_t crc, const uint8_t *data, size_t len, bool reverse_bits); /* rcw_bits.c */ rcw_error_t rcw_bits_pack(const rcw_symtab_t *syms, const rcw_vars_t *vars, uint8_t out[RCW_SIZE_BYTES]); uint64_t rcw_bits_extract(const uint8_t data[RCW_SIZE_BYTES], int begin, int end, bool classicbitnumbers); /* rcw_parse.c */ rcw_error_t rcw_parse(rcw_ctx_t *ctx, const char *source, size_t len); rcw_error_t rcw_parse_literal(const char *s, const char **end, uint64_t *out); /* rcw_pbi.c */ rcw_error_t rcw_pbi_encode_line(rcw_ctx_t *ctx, const char *line); void rcw_pbi_block_begin(rcw_ctx_t *ctx); bool rcw_pbi_label_line(const char *line, const char **name, size_t *len); rcw_error_t rcw_pbi_define_label(rcw_ctx_t *ctx, const char *name, size_t len); rcw_error_t rcw_pbi_block_end(rcw_ctx_t *ctx); rcw_error_t rcw_eval_expr(const char *expr, uint32_t *result); #define RCW_EVAL_WHY_MAX 96 rcw_error_t rcw_eval_expr_why(const char *expr, uint32_t *result, char *why, size_t why_len); /* rcw_uboot.c */ rcw_error_t rcw_uboot_encode(rcw_ctx_t *ctx, const char *body, size_t len); /* rcw_binary.c */ rcw_error_t rcw_binary_generate(rcw_ctx_t *ctx, uint8_t **out_data, size_t *out_len); /* rcw_decompile.c */ rcw_error_t rcw_decompile(rcw_ctx_t *ctx, const uint8_t *binary, size_t len, char **out_source, size_t *out_len); /* rcw_preprocess.c */ rcw_error_t rcw_preprocess(const rcw_ctx_t *ctx, const char *input_path, char **out, size_t *out_len); /* Bit order and byte order helpers */ static inline uint8_t rcw_reverse_byte(uint8_t b) { b = (uint8_t)(((b & 0xF0) >> 4) | ((b & 0x0F) << 4)); b = (uint8_t)(((b & 0xCC) >> 2) | ((b & 0x33) << 2)); b = (uint8_t)(((b & 0xAA) >> 1) | ((b & 0x55) << 1)); return b; } /* Matches rcw.py: int(bin(crc)[2:].zfill(32)[::-1], 2) */ static inline uint32_t rcw_reverse_word(uint32_t w) { uint32_t r = 0; for (int i = 0; i < 32; i++) { r <<= 1; r |= (w & 1); w >>= 1; } return r; } static inline uint32_t rcw_unpack32(const uint8_t *p, bool littleendian) { if (littleendian) return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; } /* PBI buffer helpers */ static inline rcw_error_t rcw_pbi_append(rcw_pbi_buf_t *buf, const void *data, size_t len) { if (buf->len + len > buf->cap) { size_t newcap = (buf->cap == 0) ? RCW_PBI_BUF_INIT_CAP : buf->cap * 2; while (newcap < buf->len + len) newcap *= 2; uint8_t *p = realloc(buf->data, newcap); if (!p) return RCW_ERR_NOMEM; buf->data = p; buf->cap = newcap; } memcpy(buf->data + buf->len, data, len); buf->len += len; return RCW_OK; } /* Set error detail (printf-style). */ __attribute__((format(printf, 2, 3))) static inline void rcw_set_error(rcw_ctx_t *ctx, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsnprintf(ctx->error_detail, RCW_ERROR_DETAIL_MAX, fmt, ap); va_end(ap); } vjardin-libqoriq-rcw-175bf56/src/rcw_parse.c000066400000000000000000000351241526740601600210550ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Parser for preprocessed .rcw/.rcwi source files. * Mirrors rcw.py parse_source_file() (lines 445-520). */ #include #include #include #include #include #include #include "rcw_internal.h" /* Read an unsigned integer literal the way Python's int(x, 0) does */ rcw_error_t rcw_parse_literal(const char *s, const char **end, uint64_t *out) { unsigned base = 10; const char *p = s; if (!s) return RCW_ERR_PARSE; if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { base = 16; p += 2; } else if (p[0] == '0' && (p[1] == 'o' || p[1] == 'O')) { base = 8; p += 2; } else if (p[0] == '0' && (p[1] == 'b' || p[1] == 'B')) { base = 2; p += 2; } uint64_t v = 0; size_t ndigits = 0; bool nonzero = false; bool leading_zero = (base == 10 && p[0] == '0'); for (;;) { unsigned d; if (*p >= '0' && *p <= '9') d = (unsigned)(*p - '0'); else if (*p >= 'a' && *p <= 'f') d = (unsigned)(*p - 'a' + 10); else if (*p >= 'A' && *p <= 'F') d = (unsigned)(*p - 'A' + 10); else if (*p == '_' && ndigits > 0 && p[1] != '_' && p[1] != '\0') { p++; continue; } else { break; } if (d >= base) break; if (v > (UINT64_MAX - d) / base) return RCW_ERR_PARSE; v = v * base + d; nonzero = nonzero || d != 0; ndigits++; p++; } if (ndigits == 0) return RCW_ERR_PARSE; /* "0", "00" are fine; "010" is not a Python literal */ if (leading_zero && nonzero) return RCW_ERR_PARSE; if (end) *end = p; *out = v; return RCW_OK; } /* A whole string as one literal: nothing before, nothing after. */ static rcw_error_t parse_whole_literal(const char *s, uint64_t *out) { const char *end; if (!s || !*s) return RCW_ERR_PARSE; if (rcw_parse_literal(s, &end, out) != RCW_OK) return RCW_ERR_PARSE; if (*end != '\0') return RCW_ERR_PARSE; return RCW_OK; } /* * Parse a non-negative integer position that must fit inside the * 1024-bit RCW. Used for NAME[begin:end] / NAME[pos] declarations. * * Rejects a leading '-' (no negative bit positions), empty input, * trailing garbage after the digits, and a value >= RCW_SIZE_BITS. * * Without this, "FOO[2000:2000]" reaches set_bits() with bytepos=250 * and writes past the 128-byte stack buffer. */ static rcw_error_t parse_bitpos(const char *s, int *out) { uint64_t v; if (parse_whole_literal(s, &v) != RCW_OK) return RCW_ERR_PARSE; if (v >= (uint64_t)RCW_SIZE_BITS) return RCW_ERR_PARSE; *out = (int)v; return RCW_OK; } /* * Parse an unsigned 64-bit value for NAME=value. * * Rejects a leading '-' (assignment is to an unsigned bitfield), empty * input, trailing garbage and values beyond 64 bits. * * Width vs. field check happens later in rcw_bits_pack(). */ static rcw_error_t parse_field_value(const char *s, uint64_t *out) { return parse_whole_literal(s, out); } /* Find a symbol by name, return index or -1. */ static int symtab_find(const rcw_symtab_t *tab, const char *name) { for (size_t i = 0; i < tab->count; i++) { if (strcmp(tab->entries[i].name, name) == 0) return (int)i; } return -1; } /* Check for bitfield overlap. */ static rcw_error_t check_overlap(rcw_ctx_t *ctx, const char *name, int begin, int end) { int b = (begin < end) ? begin : end; int e = (begin < end) ? end : begin; if (symtab_find(&ctx->symbols, name) >= 0) { rcw_set_error(ctx, "Duplicate bitfield definition for %s", name); return RCW_ERR_OVERLAP; } for (size_t i = 0; i < ctx->symbols.count; i++) { const rcw_symbol_t *s = &ctx->symbols.entries[i]; int sb = (s->begin < s->end) ? s->begin : s->end; int se = (s->begin < s->end) ? s->end : s->begin; if ((sb <= b && b <= se) || (sb <= e && e <= se) || (b <= sb && sb <= e) || (b <= se && se <= e)) { rcw_set_error(ctx, "Bitfield %s overlaps with %s", name, s->name); return RCW_ERR_OVERLAP; } } return RCW_OK; } /* Parse a %variable assignment. */ static rcw_error_t parse_variable(rcw_ctx_t *ctx, const char *line) { /* Skip '%' */ line++; const char *eq = strchr(line, '='); if (!eq) return RCW_ERR_PARSE; char name[64]; size_t nlen = (size_t)(eq - line); if (nlen >= sizeof(name)) return RCW_ERR_PARSE; memcpy(name, line, nlen); name[nlen] = '\0'; const char *valstr = eq + 1; uint64_t val = 0; (void)parse_whole_literal(valstr, &val); if (strcmp(name, "size") == 0) ctx->vars.size = (int)val; else if (strcmp(name, "pbiformat") == 0) ctx->vars.pbiformat = (int)val; else if (strcmp(name, "classicbitnumbers") == 0) ctx->vars.classicbitnumbers = (val != 0); else if (strcmp(name, "littleendian") == 0) ctx->vars.littleendian = (val != 0); else if (strcmp(name, "nocrc") == 0) ctx->vars.nocrc = (val != 0); else if (strcmp(name, "loadwochecksum") == 0) ctx->vars.loadwochecksum = (val != 0); else if (strcmp(name, "pbladdr") == 0) ctx->vars.pbladdr = (uint32_t)strtoul(valstr, NULL, 16); else if (strcmp(name, "sysaddr") == 0) ctx->vars.has_sysaddr = true; /* its value only matters to the legacy header */ return RCW_OK; } /* * Strip all whitespace from a string (for non-PBI lines). * Returns dynamically allocated string. */ static char * strip_all_whitespace(const char *s, size_t len) { char *out = malloc(len + 1); if (!out) return NULL; size_t j = 0; for (size_t i = 0; i < len; i++) { if (!isspace((unsigned char)s[i])) out[j++] = s[i]; } out[j] = '\0'; return out; } /* * Strip leading and trailing whitespace (for PBI lines). * Returns dynamically allocated string. */ static char * strip_trim(const char *s, size_t len) { while (len > 0 && isspace((unsigned char)s[0])) { s++; len--; } while (len > 0 && isspace((unsigned char)s[len - 1])) len--; char *out = malloc(len + 1); if (!out) return NULL; memcpy(out, s, len); out[len] = '\0'; return out; } /* * Append a line (with newline) to a dynamic char buffer. * Used to accumulate .uboot subsection bodies. */ static rcw_error_t ubuf_append(char **buf, size_t *len, size_t *cap, const char *line, size_t line_len) { size_t need = *len + line_len + 1; /* +1 for newline */ if (need > *cap) { size_t newcap = (*cap == 0) ? 4096 : *cap; while (newcap < need) newcap *= 2; char *p = realloc(*buf, newcap); if (!p) return RCW_ERR_NOMEM; *buf = p; *cap = newcap; } memcpy(*buf + *len, line, line_len); (*buf)[*len + line_len] = '\n'; *len += line_len + 1; return RCW_OK; } /* Subsection types we recognize. */ enum sub_type { SUB_NONE = 0, SUB_PBI, /* .pbi ... .end */ SUB_UBOOT, /* .uboot ... .end */ }; rcw_error_t rcw_parse(rcw_ctx_t *ctx, const char *source, size_t len) { enum sub_type subsection = SUB_NONE; char *uboot_buf = NULL; size_t uboot_len = 0; size_t uboot_cap = 0; const char *p = source; const char *end = source + len; while (p < end) { /* Find end of current line */ const char *eol = memchr(p, '\n', (size_t)(end - p)); if (!eol) eol = end; size_t line_len = (size_t)(eol - p); if (subsection != SUB_NONE) { /* Subsection lines: strip leading/trailing whitespace only */ char *trimmed = strip_trim(p, line_len); if (!trimmed) { free(uboot_buf); return RCW_ERR_NOMEM; } /* Check for .end */ if (trimmed[0] == '.') { /* Strip all whitespace to check subsection marker */ char *stripped = strip_all_whitespace(p, line_len); if (!stripped) { free(trimmed); free(uboot_buf); return RCW_ERR_NOMEM; } if (strcmp(stripped, ".end") == 0) { enum sub_type closing = subsection; subsection = SUB_NONE; free(stripped); free(trimmed); if (closing == SUB_PBI) { rcw_error_t err = rcw_pbi_block_end(ctx); if (err != RCW_OK) { free(uboot_buf); return err; } } /* Flush a .uboot block at .end */ if (closing == SUB_UBOOT) { rcw_error_t err = rcw_uboot_encode(ctx, uboot_buf, uboot_len); free(uboot_buf); uboot_buf = NULL; uboot_len = 0; uboot_cap = 0; if (err != RCW_OK) return err; } p = eol + 1; continue; } free(stripped); } /* Skip empty lines and comment-only lines in subsections */ if (trimmed[0] != '\0' && !(trimmed[0] == '/' && trimmed[1] == '*')) { rcw_error_t err; const char *lname; size_t llen; if (subsection == SUB_PBI) { if (rcw_pbi_label_line(trimmed, &lname, &llen)) err = rcw_pbi_define_label(ctx, lname, llen); else err = rcw_pbi_encode_line(ctx, trimmed); } else { /* SUB_UBOOT */ err = ubuf_append(&uboot_buf, &uboot_len, &uboot_cap, trimmed, strlen(trimmed)); } if (err != RCW_OK) { free(trimmed); free(uboot_buf); return err; } } free(trimmed); p = eol + 1; continue; } /* Non-PBI: strip ALL whitespace */ char *line = strip_all_whitespace(p, line_len); if (!line) return RCW_ERR_NOMEM; /* Skip blank lines, and a comment alone on its line */ if (line[0] == '\0' || (line[0] == '/' && line[1] == '*')) { free(line); p = eol + 1; continue; } /* Subsection marker: .pbi or .uboot */ if (line[0] == '.') { if (strcmp(line + 1, "pbi") == 0) { subsection = SUB_PBI; rcw_pbi_block_begin(ctx); } else if (strcmp(line + 1, "uboot") == 0) subsection = SUB_UBOOT; /* else: unknown subsection, ignore */ free(line); p = eol + 1; continue; } /* Variable: %var=value */ if (line[0] == '%') { rcw_error_t err = parse_variable(ctx, line); free(line); if (err != RCW_OK) return err; p = eol + 1; continue; } /* * Try bitfield definition: NAME[begin:end] or NAME[pos] * and assignment: NAME=value * * We look for '[' first (definition), then '=' (assignment). */ char *bracket = strchr(line, '['); char *eq = strchr(line, '='); if (bracket && (!eq || bracket < eq)) { /* Bitfield definition */ *bracket = '\0'; const char *name = line; char *range = bracket + 1; char *endbr = strchr(range, ']'); if (!endbr) { free(line); return RCW_ERR_PARSE; } *endbr = '\0'; /* * Reject names that would silently truncate inside sym->name. * Truncation could otherwise alias two long names sharing a * 63-char prefix, defeating duplicate detection. */ if (strlen(name) >= RCW_FIELD_NAME_MAX) { rcw_set_error(ctx, "Bitfield name too long (max %d): %.40s...", RCW_FIELD_NAME_MAX - 1, name); free(line); if (ctx->warnings) fprintf(stderr, "%s\n", ctx->error_detail); p = eol + 1; continue; } char *colon = strchr(range, ':'); int begin, endpos; if (colon) { /* NAME[begin:end] */ *colon = '\0'; if (parse_bitpos(range, &begin) != RCW_OK || parse_bitpos(colon + 1, &endpos) != RCW_OK) { rcw_set_error(ctx, "Bitfield %s: bit position out of range " "(must be 0..%d)", name, RCW_SIZE_BITS - 1); free(line); if (ctx->warnings) fprintf(stderr, "%s\n", ctx->error_detail); p = eol + 1; continue; } } else { /* NAME[pos] (single bit) */ if (parse_bitpos(range, &begin) != RCW_OK) { rcw_set_error(ctx, "Bitfield %s: bit position out of range " "(must be 0..%d)", name, RCW_SIZE_BITS - 1); free(line); if (ctx->warnings) fprintf(stderr, "%s\n", ctx->error_detail); p = eol + 1; continue; } endpos = begin; } rcw_error_t err = check_overlap(ctx, name, begin, endpos); if (err != RCW_OK) { free(line); /* Warn but continue, like rcw.py */ if (ctx->warnings) fprintf(stderr, "%s\n", ctx->error_detail); p = eol + 1; continue; } if (ctx->symbols.count >= RCW_MAX_SYMBOLS) { free(line); return RCW_ERR_NOMEM; } rcw_symbol_t *sym = &ctx->symbols.entries[ctx->symbols.count++]; snprintf(sym->name, RCW_FIELD_NAME_MAX, "%s", name); sym->begin = begin; sym->end = endpos; sym->has_value = false; free(line); p = eol + 1; continue; } if (eq) { /* Assignment: NAME=value */ *eq = '\0'; const char *name = line; const char *valstr = eq + 1; int idx = symtab_find(&ctx->symbols, name); if (idx < 0) { /* rcw.py prints error but continues */ fprintf(stderr, "Error: Unknown bitfield %s\n", name); rcw_set_error(ctx, "Unknown bitfield: %s", name); free(line); p = eol + 1; continue; } if (ctx->warnings && ctx->symbols.entries[idx].has_value) fprintf(stderr, "Warning: Duplicate assignment for " "bitfield %s\n", name); uint64_t v; if (parse_field_value(valstr, &v) != RCW_OK) { rcw_set_error(ctx, "Invalid value for %s: %s", name, valstr); fprintf(stderr, "Error: %s\n", ctx->error_detail); free(line); p = eol + 1; continue; } ctx->symbols.entries[idx].has_value = true; ctx->symbols.entries[idx].value = v; free(line); p = eol + 1; continue; } /* Unknown line - print error like rcw.py but continue */ fprintf(stderr, "Error: unknown command %s\n", line); free(line); p = eol + 1; } /* * Source ended without closing a subsection. For .uboot, flush * what we have anyway (matches rcw.py: build_pbi_uboot is called * once at .end; if the file is truncated, the remainder is lost). * For .pbi we have nothing to flush since lines are encoded as * they arrive. */ if (subsection == SUB_UBOOT && uboot_buf) { rcw_error_t err = rcw_uboot_encode(ctx, uboot_buf, uboot_len); free(uboot_buf); if (err != RCW_OK) return err; } else { free(uboot_buf); } if (subsection == SUB_PBI) return rcw_pbi_block_end(ctx); return RCW_OK; } vjardin-libqoriq-rcw-175bf56/src/rcw_pbi.c000066400000000000000000001024241526740601600205130ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * PBI command encoder for pbiformat=2. * Includes a minimal expression evaluator for preprocessed macro expansions. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "rcw_internal.h" /* Operand expressions à la Python */ #define RCW_EVAL_MAX_DEPTH 64 typedef struct { const char *pos; const char *end; int depth; bool failed; char why[RCW_EVAL_WHY_MAX]; } eval_state_t; __attribute__((format(printf, 2, 3))) static void eval_fail(eval_state_t *st, const char *fmt, ...) { va_list ap; if (st->failed) return; /* the first reason is the useful one */ st->failed = true; va_start(ap, fmt); vsnprintf(st->why, sizeof(st->why), fmt, ap); va_end(ap); } static void skip_ws(eval_state_t *st) { while (st->pos < st->end && isspace((unsigned char)*st->pos)) st->pos++; } /* The operator at the cursor, if it is the one wanted, is consumed. */ static bool eval_accept(eval_state_t *st, const char *op) { size_t n = strlen(op); skip_ws(st); if ((size_t)(st->end - st->pos) < n || memcmp(st->pos, op, n) != 0) return false; /* "<<" is not "<", "**" is not "*", "//" is not "/" */ if (n == 1 && st->pos + 1 < st->end && st->pos[1] == op[0]) return false; st->pos += n; return true; } static int64_t eval_floordiv(eval_state_t *st, int64_t a, int64_t d) { if (d == 0) { eval_fail(st, "division by zero"); return 0; } if (a == INT64_MIN && d == -1) { eval_fail(st, "value does not fit 64 bits"); return 0; } int64_t q = a / d; if ((a % d) != 0 && ((a < 0) != (d < 0))) q--; return q; } static int64_t eval_mod(eval_state_t *st, int64_t a, int64_t d) { if (d == 0) { eval_fail(st, "division by zero"); return 0; } if (a == INT64_MIN && d == -1) return 0; int64_t r = a % d; if (r != 0 && ((r < 0) != (d < 0))) r += d; return r; } static int64_t eval_pow(eval_state_t *st, int64_t base, int64_t exp) { int64_t r = 1; if (exp < 0) { eval_fail(st, "negative exponent"); /* a float in Python */ return 0; } while (exp > 0) { if ((exp & 1) && __builtin_mul_overflow(r, base, &r)) { eval_fail(st, "value does not fit 64 bits"); return 0; } exp >>= 1; if (exp > 0 && __builtin_mul_overflow(base, base, &base)) { eval_fail(st, "value does not fit 64 bits"); return 0; } } return r; } static int64_t eval_shl(eval_state_t *st, int64_t a, int64_t n) { int64_t r; if (n < 0) { eval_fail(st, "negative shift count"); return 0; } if (a == 0) return 0; if (n >= 63 || __builtin_mul_overflow(a, (int64_t)1 << n, &r)) { eval_fail(st, "value does not fit 64 bits"); return 0; } return r; } static int64_t eval_shr(eval_state_t *st, int64_t a, int64_t n) { if (n < 0) { eval_fail(st, "negative shift count"); return 0; } if (n >= 63) return a < 0 ? -1 : 0; return eval_floordiv(st, a, (int64_t)1 << n); } static int64_t eval_or(eval_state_t *st); static int64_t eval_unary(eval_state_t *st); static int64_t eval_primary(eval_state_t *st) { int64_t v = 0; skip_ws(st); if (st->pos >= st->end) { eval_fail(st, "operand expected"); return 0; } if (++st->depth > RCW_EVAL_MAX_DEPTH) { eval_fail(st, "nested too deep"); --st->depth; return 0; } if (*st->pos == '(') { st->pos++; v = eval_or(st); skip_ws(st); if (st->pos < st->end && *st->pos == ')') st->pos++; else eval_fail(st, "')' expected"); } else if (isdigit((unsigned char)*st->pos)) { const char *endp; uint64_t u; if (rcw_parse_literal(st->pos, &endp, &u) != RCW_OK || u > INT64_MAX) eval_fail(st, "bad number at '%.12s'", st->pos); else { st->pos = endp; v = (int64_t)u; } } else { eval_fail(st, "unexpected '%c'", *st->pos); } --st->depth; return v; } static int64_t eval_power(eval_state_t *st) { int64_t v = eval_primary(st); if (!st->failed && eval_accept(st, "**")) v = eval_pow(st, v, eval_unary(st)); return v; } static int64_t eval_unary(eval_state_t *st) { skip_ws(st); if (st->pos < st->end && (*st->pos == '-' || *st->pos == '+' || *st->pos == '~')) { char op = *st->pos++; if (++st->depth > RCW_EVAL_MAX_DEPTH) { eval_fail(st, "nested too deep"); --st->depth; return 0; } int64_t v = eval_unary(st); --st->depth; if (op == '-') { if (v == INT64_MIN) { eval_fail(st, "value does not fit 64 bits"); return 0; } return -v; } if (op == '~') return ~v; return v; } return eval_power(st); } static int64_t eval_term(eval_state_t *st) { int64_t v = eval_unary(st); while (!st->failed) { if (eval_accept(st, "*")) { if (__builtin_mul_overflow(v, eval_unary(st), &v)) eval_fail(st, "value does not fit 64 bits"); } else if (eval_accept(st, "//")) { v = eval_floordiv(st, v, eval_unary(st)); } else if (eval_accept(st, "%")) { v = eval_mod(st, v, eval_unary(st)); } else if (eval_accept(st, "/")) { eval_fail(st, "'/' is a float division in Python, use '//'"); } else { break; } } return v; } static int64_t eval_sum(eval_state_t *st) { int64_t v = eval_term(st); while (!st->failed) { if (eval_accept(st, "+")) { if (__builtin_add_overflow(v, eval_term(st), &v)) eval_fail(st, "value does not fit 64 bits"); } else if (eval_accept(st, "-")) { if (__builtin_sub_overflow(v, eval_term(st), &v)) eval_fail(st, "value does not fit 64 bits"); } else { break; } } return v; } static int64_t eval_shift(eval_state_t *st) { int64_t v = eval_sum(st); while (!st->failed) { if (eval_accept(st, "<<")) v = eval_shl(st, v, eval_sum(st)); else if (eval_accept(st, ">>")) v = eval_shr(st, v, eval_sum(st)); else break; } return v; } static int64_t eval_and(eval_state_t *st) { int64_t v = eval_shift(st); while (!st->failed && eval_accept(st, "&")) v &= eval_shift(st); return v; } static int64_t eval_xor(eval_state_t *st) { int64_t v = eval_and(st); while (!st->failed && eval_accept(st, "^")) v ^= eval_and(st); return v; } static int64_t eval_or(eval_state_t *st) { int64_t v = eval_xor(st); while (!st->failed && eval_accept(st, "|")) v |= eval_xor(st); return v; } rcw_error_t rcw_eval_expr_why(const char *expr, uint32_t *result, char *why, size_t why_len) { eval_state_t st = { .pos = expr, .end = expr + strlen(expr), }; int64_t v = eval_or(&st); if (!st.failed) { skip_ws(&st); if (st.pos < st.end) eval_fail(&st, "unexpected '%.12s'", st.pos); } if (!st.failed && (v < 0 || v > (int64_t)UINT32_MAX)) eval_fail(&st, "%lld does not fit a 32-bit word", (long long)v); if (st.failed) { if (why && why_len) snprintf(why, why_len, "%s", st.why); return RCW_ERR_PBI_SYNTAX; } *result = (uint32_t)v; return RCW_OK; } rcw_error_t rcw_eval_expr(const char *expr, uint32_t *result) { return rcw_eval_expr_why(expr, result, NULL, 0); } /* Store a 32-bit word in the image's word order. */ static void pbi_put_word(const rcw_ctx_t *ctx, uint8_t *buf, uint32_t val) { if (ctx->vars.littleendian) { buf[0] = (uint8_t)(val & 0xFF); buf[1] = (uint8_t)((val >> 8) & 0xFF); buf[2] = (uint8_t)((val >> 16) & 0xFF); buf[3] = (uint8_t)((val >> 24) & 0xFF); } else { buf[0] = (uint8_t)((val >> 24) & 0xFF); buf[1] = (uint8_t)((val >> 16) & 0xFF); buf[2] = (uint8_t)((val >> 8) & 0xFF); buf[3] = (uint8_t)(val & 0xFF); } } /* Pack a 32-bit word in the current endianness and append to PBI buffer */ static rcw_error_t pbi_pack_word(rcw_ctx_t *ctx, uint32_t val) { uint8_t buf[PBI_WORD_BYTES]; pbi_put_word(ctx, buf, val); return rcw_pbi_append(&ctx->pbi, buf, sizeof(buf)); } void rcw_pbi_block_begin(rcw_ctx_t *ctx) { ctx->label_count = 0; ctx->fixup_count = 0; } bool rcw_pbi_label_line(const char *line, const char **name, size_t *len) { const char *p = line; while (isspace((unsigned char)*p)) p++; if (!(isalpha((unsigned char)*p) || *p == '_')) return false; const char *start = p; while (isalnum((unsigned char)*p) || *p == '_') p++; const char *stop = p; while (isspace((unsigned char)*p)) p++; if (*p != ':') return false; p++; while (isspace((unsigned char)*p)) p++; if (*p != '\0') return false; *name = start; *len = (size_t)(stop - start); return true; } static bool label_is(const char *label, const char *name, size_t len) { return strlen(label) == len && memcmp(label, name, len) == 0; } rcw_error_t rcw_pbi_define_label(rcw_ctx_t *ctx, const char *name, size_t len) { if (len >= RCW_LABEL_NAME_MAX) { rcw_set_error(ctx, "Label name too long (max %d): %.*s", RCW_LABEL_NAME_MAX - 1, (int)len, name); return RCW_ERR_PBI_SYNTAX; } for (size_t i = 0; i < ctx->label_count; i++) { if (label_is(ctx->labels[i].name, name, len)) { rcw_set_error(ctx, "Duplicate label \"%.*s\" in .pbi block", (int)len, name); return RCW_ERR_PBI_SYNTAX; } } if (ctx->label_count >= RCW_MAX_LABELS) { rcw_set_error(ctx, "Too many labels in one .pbi block (max %d)", RCW_MAX_LABELS); return RCW_ERR_NOMEM; } rcw_label_t *l = &ctx->labels[ctx->label_count++]; memcpy(l->name, name, len); l->name[len] = '\0'; l->offset = ctx->pbi.len; /* The jumps that named it, encoded with OFFSET 0, get their distance. */ for (size_t i = 0; i < ctx->fixup_count;) { rcw_fixup_t *fx = &ctx->fixups[i]; if (!label_is(fx->name, name, len)) { i++; continue; } size_t offset = l->offset - fx->insn; if (offset <= PBI_JUMP_MIN_OFFSET) { rcw_set_error(ctx, "\"%s %s\" would jump %zu bytes, onto the word after itself; " "the BootROM wants more than %d", fx->op, fx->name, offset, PBI_JUMP_MIN_OFFSET); return RCW_ERR_PBI_SYNTAX; } pbi_put_word(ctx, ctx->pbi.data + fx->insn + PBI_WORD_BYTES, (uint32_t)offset); *fx = ctx->fixups[--ctx->fixup_count]; } return RCW_OK; } rcw_error_t rcw_pbi_block_end(rcw_ctx_t *ctx) { if (ctx->fixup_count > 0) { const rcw_fixup_t *fx = &ctx->fixups[0]; rcw_set_error(ctx, "\"%s %s\": label \"%s\" is not defined in this .pbi block", fx->op, fx->name, fx->name); ctx->label_count = 0; ctx->fixup_count = 0; return RCW_ERR_PBI_SYNTAX; } ctx->label_count = 0; return RCW_OK; } static bool token_is_identifier(const char *tok, const char **start, size_t *len) { while (isspace((unsigned char)*tok)) tok++; if (!(isalpha((unsigned char)*tok) || *tok == '_')) return false; const char *s = tok; while (isalnum((unsigned char)*tok) || *tok == '_') tok++; const char *e = tok; while (isspace((unsigned char)*tok)) tok++; if (*tok != '\0') return false; *start = s; *len = (size_t)(e - s); return true; } static rcw_error_t encode_jump(rcw_ctx_t *ctx, const char *op, const char *line, uint32_t cmd, const char *label, const uint32_t *params, int nparam, int want) { if (nparam < want) { rcw_set_error(ctx, "\"%s\" requires %d parameter%s (%s): %s", op, want, want == 1 ? "" : "s", want == 1 ? "byte offset or label" : "byte offset or label, condition", line); return RCW_ERR_PBI_SYNTAX; } size_t insn = ctx->pbi.len; uint32_t offset = params[0]; if (label[0] != '\0') { for (size_t i = 0; i < ctx->label_count; i++) { if (strcmp(ctx->labels[i].name, label) == 0) { rcw_set_error(ctx, "\"%s %s\": the label is behind the jump, the SP jumps " "forward only: %s", op, label, line); return RCW_ERR_PBI_SYNTAX; } } if (ctx->fixup_count >= RCW_MAX_LABELS) { rcw_set_error(ctx, "Too many jumps to labels in one .pbi block (max %d)", RCW_MAX_LABELS); return RCW_ERR_NOMEM; } rcw_fixup_t *fx = &ctx->fixups[ctx->fixup_count++]; snprintf(fx->name, sizeof(fx->name), "%s", label); snprintf(fx->op, sizeof(fx->op), "%s", op); fx->insn = insn; offset = 0; /* patched by the label line */ } else { if (offset % PBI_WORD_BYTES != 0) { rcw_set_error(ctx, "\"%s\" offset 0x%x is not 4-byte aligned: %s", op, offset, line); return RCW_ERR_PBI_SYNTAX; } if (offset <= PBI_JUMP_MIN_OFFSET) { rcw_set_error(ctx, "\"%s\" offset 0x%x must be greater than %d (RM Table %s): %s", op, offset, PBI_JUMP_MIN_OFFSET, want == 1 ? "46" : "47", line); return RCW_ERR_PBI_SYNTAX; } } rcw_error_t err = pbi_pack_word(ctx, PBL_CMD_GENERAL | (cmd << PBI_CMD_SHIFT)); if (err != RCW_OK) return err; err = pbi_pack_word(ctx, offset); if (err != RCW_OK) return err; if (want == 2) return pbi_pack_word(ctx, params[1]); return RCW_OK; } /* * The CSF header can be given as a quoted file path instead of 20 inline * words. Detect and extract that before the numeric parameter parser runs, * since a path is not a number and would otherwise fail there. */ static bool line_has_quoted_arg(const char *line) { return strchr(line, '"') != NULL; } static rcw_error_t extract_quoted_arg(const char *line, char *out, size_t out_sz) { const char *a = strchr(line, '"'); if (!a) return RCW_ERR_PBI_SYNTAX; const char *b = strchr(a + 1, '"'); if (!b || (size_t)(b - a - 1) >= out_sz) return RCW_ERR_PBI_SYNTAX; memcpy(out, a + 1, (size_t)(b - a - 1)); out[b - a - 1] = '\0'; return RCW_OK; } /* * Parse a PBI command line and encode it. * Format: op[.suffix] param1[,param2[,...]] */ rcw_error_t rcw_pbi_encode_line(rcw_ctx_t *ctx, const char *line) { char op[32] = {0}; char suffix[16] = {0}; char label[RCW_LABEL_NAME_MAX] = {0}; /* a jump's target, when named */ uint32_t params[PBI_MAX_PARAMS]; int nparam = 0; /* Parse "op" or "op.suffix" */ const char *p = line; /* * isalnum, not isalpha: "loadboot1csfptr" (RM 8.3.9 "Load Boot 1 CSF * Header Pointer") carries a digit mid-mnemonic. Every pre-existing * mnemonic is all-alpha, so this cannot change how any of them tokenise; * the qoriq-rcw cross-validate suite proves that byte-for-byte across * every pbiformat=2 board. */ while (*p && isalnum((unsigned char)*p)) p++; size_t oplen = (size_t)(p - line); if (oplen == 0 || oplen >= sizeof(op)) { rcw_set_error(ctx, "Invalid PBI command: %s", line); return RCW_ERR_PBI_SYNTAX; } memcpy(op, line, oplen); /* Parse optional suffix (.b1, .b2, .b4, .b5, .short, .long) */ if (*p == '.') { p++; /* skip dot */ const char *suf_start = p; while (*p && (isalnum((unsigned char)*p))) p++; size_t suflen = (size_t)(p - suf_start); if (suflen >= sizeof(suffix)) { rcw_set_error(ctx, "Invalid PBI suffix: %s", line); return RCW_ERR_PBI_SYNTAX; } memcpy(suffix, suf_start, suflen); } /* Skip whitespace before parameters */ while (*p && isspace((unsigned char)*p)) p++; /* * Parse comma-separated parameters. * * Skipped entirely when the argument is a quoted string: the only such * command is loadsecurityheader with a CSF file path, and a path is not * a number, so the evaluator below would reject it before the encoder * ever sees it. The encoder re-reads the path off `line` itself. Any * other command given a quoted argument simply sees zero parameters and * fails with its own "requires N parameters" message. */ /* Only two commands exceed the standard operand count. */ const int param_cap = (strcmp(op, "awrite") == 0) ? PBI_AWRITE_MAX_PARAMS : (strcmp(op, "loadsecurityheader") == 0) ? (1 + PBI_CSF_HEADER_WORDS) : PBI_STD_MAX_PARAMS; /* ... and only the jumps take a label where a number is expected. */ const bool is_jump = strcmp(op, "jump") == 0 || strcmp(op, "jumpc") == 0 || strcmp(op, "jumpif") == 0; if (*p && strchr(p, '"') == NULL) { char *params_str = strdup(p); if (!params_str) return RCW_ERR_NOMEM; char *tok = params_str; while (tok && *tok) { if (nparam >= param_cap) { rcw_set_error(ctx, "Too many PBI parameters (max %d): %s", param_cap, line); free(params_str); return RCW_ERR_PBI_SYNTAX; } char *comma = NULL; int depth = 0; /* Find comma not inside parentheses */ for (char *c = tok; *c; c++) { if (*c == '(') depth++; else if (*c == ')') depth--; else if (*c == ',' && depth == 0) { comma = c; break; } } if (comma) *comma = '\0'; /* A jump's first operand may name a label instead of an offset */ const char *id; size_t idlen; if (is_jump && nparam == 0 && token_is_identifier(tok, &id, &idlen)) { if (idlen >= sizeof(label)) { rcw_set_error(ctx, "Label name too long (max %zu): %s", sizeof(label) - 1, line); free(params_str); return RCW_ERR_PBI_SYNTAX; } memcpy(label, id, idlen); label[idlen] = '\0'; params[nparam++] = 0; tok = comma ? comma + 1 : NULL; continue; } /* Evaluate expression */ char why[RCW_EVAL_WHY_MAX]; rcw_error_t err = rcw_eval_expr_why(tok, ¶ms[nparam], why, sizeof(why)); if (err != RCW_OK) { rcw_set_error(ctx, "Cannot evaluate PBI parameter '%s': %s, in: %s", tok, why, line); free(params_str); return err; } nparam++; tok = comma ? comma + 1 : NULL; } free(params_str); } /* Encode each command (pbiformat=2 only) */ rcw_error_t err; if (strcmp(op, "write") == 0) { if (nparam < 2) { rcw_set_error(ctx, "\"write\" requires 2 parameters: %s", line); return RCW_ERR_PBI_SYNTAX; } if (params[0] > PBI_CCSR_ADDR_MAX) { rcw_set_error(ctx, "\"write\" address 0x%x exceeds 28-bit CCSR field " "(max 0x%x); upper bits would corrupt the byte-count " "field: %s", params[0], PBI_CCSR_ADDR_MAX, line); return RCW_ERR_PBI_SYNTAX; } uint32_t opsizebytes = PBI_WRITE_4BYTE; if (strcmp(suffix, "b1") == 0) opsizebytes = PBI_WRITE_1BYTE; err = pbi_pack_word(ctx, (opsizebytes << PBI_WRITE_SHIFT) | params[0]); if (err != RCW_OK) return err; return pbi_pack_word(ctx, params[1]); } else if (strcmp(op, "awrite") == 0) { uint32_t b = PBI_AWRITE_B3; if (suffix[0] != '\0') { uint64_t sb = 0; const char *endp = NULL; if (suffix[0] != 'b' || rcw_parse_literal(suffix + 1, &endp, &sb) != RCW_OK || *endp != '\0' || sb < PBI_AWRITE_B_MIN || sb > PBI_AWRITE_B_MAX) { rcw_set_error(ctx, "\"awrite.%s\": the width suffix is .b1 to .b%d " "(1 to 512 bytes, RM Table 35): %s", suffix, PBI_AWRITE_B_MAX, line); return RCW_ERR_PBI_SYNTAX; } b = (uint32_t)sb; } uint32_t nbytes = 1u << (b - 1); int nwords = (int)((nbytes + PBI_WORD_BYTES - 1) / PBI_WORD_BYTES); if (nparam != 1 + nwords) { rcw_set_error(ctx, "\"awrite.b%u\" writes %u byte%s: the offset then %d data word%s, " "got %d parameter%s: %s", b, nbytes, nbytes == 1 ? "" : "s", nwords, nwords == 1 ? "" : "s", nparam, nparam == 1 ? "" : "s", line); return RCW_ERR_PBI_SYNTAX; } if (params[0] > PBI_AWRITE_ADDR_MAX) { rcw_set_error(ctx, "\"awrite\" offset 0x%x exceeds 26-bit field (max 0x%x): %s", params[0], PBI_AWRITE_ADDR_MAX, line); return RCW_ERR_PBI_SYNTAX; } if (b > 2 && (params[0] % PBI_WORD_BYTES) != 0) { rcw_set_error(ctx, "\"awrite.b%u\" offset 0x%x must be word aligned for writes " "wider than 2 bytes (RM Table 35): %s", b, params[0], line); return RCW_ERR_PBI_SYNTAX; } err = pbi_pack_word(ctx, PBL_CMD_GENERAL | (b << PBI_AWRITE_B_SHIFT) | params[0]); if (err != RCW_OK) return err; for (int i = 1; i <= nwords; i++) { err = pbi_pack_word(ctx, params[i]); if (err != RCW_OK) return err; } return RCW_OK; } else if (strcmp(op, "wait") == 0) { if (nparam < 1) { rcw_set_error(ctx, "\"wait\" requires 1 parameter: %s", line); return RCW_ERR_PBI_SYNTAX; } /* * The cycle count occupies bits [15:0] of the WAIT command word * (LX2160A RM Table 45 "Wait Command Format": HDR=0x80, CMD=0x82, * 16-bit operand). Larger values silently overflow into the CMD * field on both libqoriq-rcw and rcw.py, producing an opcode the * SP misinterprets (frequently it falls through to BOOTLOCPTR). * Reject up front so the misencoding cannot reach a flashed PBL. * Callers needing a longer delay should chain multiple waits. */ if (params[0] > 0xFFFFu) { rcw_set_error(ctx, "\"wait\" operand 0x%x exceeds 16-bit field (max 0xFFFF); " "chain multiple waits instead: %s", params[0], line); return RCW_ERR_PBI_SYNTAX; } return pbi_pack_word(ctx, PBL_CMD_WAIT | params[0]); } else if (strcmp(op, "poll") == 0) { if (nparam < 3) { rcw_set_error(ctx, "\"poll\" requires 3 parameters: %s", line); return RCW_ERR_PBI_SYNTAX; } /* * This intentionally diverges from rcw.py, which does the * same ORing and additionally computes 0x80000000 + (cmd << 24), * overflowing 32 bits for cmd=0x80, it cannot emit a valid poll * at all. No board in the upstream rcw/ tree uses poll, so the * byte-identity cross-validation never covered this path and is * unaffected by the correction. */ uint32_t cmd = (strcmp(suffix, "long") == 0) ? PBI_GENCMD_POLL_LONG : PBI_GENCMD_POLL_SHORT; err = pbi_pack_word(ctx, PBL_CMD_GENERAL | (cmd << PBI_CMD_SHIFT)); if (err != RCW_OK) return err; err = pbi_pack_word(ctx, params[0]); if (err != RCW_OK) return err; err = pbi_pack_word(ctx, params[1]); if (err != RCW_OK) return err; return pbi_pack_word(ctx, params[2]); } else if (strcmp(op, "loadsecurityheader") == 0) { /* * LX2160A RM Table 41 "Load Security Header", 84 bytes: * word 0 HDR=0x80, CMD=0x20 * words 1-20 CSF_WORD_0 .. CSF_WORD_19 * * Required for secure boot only; a non-secure PBI never emits it. The * 20 words are CSF header content produced by NXP's Code Signing Tool * -- their layout is in the QorIQ Trust Architecture UG, not the RM, * so they are carried verbatim and NOT interpreted here. * * Two source forms, both hex: * loadsecurityheader 0xw0,0xw1,...,0xw19 (20 inline words) * loadsecurityheader "csf_hdr.txt" (hex text file) * The file form resolves like #include: as given, then each -I path. * * NEVER EXECUTED ON nbxv3 SILICON. This board boots non-secure (ITS * unburned, DBG_LVL=000), so the encoding is asserted against the RM * table in tests and nothing more. Treat a first secure boot as the * real validation. */ uint32_t csf[PBI_CSF_HEADER_WORDS]; if (line_has_quoted_arg(line)) { char path[RCW_PATH_MAX]; if (extract_quoted_arg(line, path, sizeof(path)) != RCW_OK) { rcw_set_error(ctx, "\"loadsecurityheader\" file argument is not a closed " "quoted string: %s", line); return RCW_ERR_PBI_SYNTAX; } err = rcw_csf_read_words(ctx, path, csf, PBI_CSF_HEADER_WORDS); if (err != RCW_OK) return err; /* rcw_csf_read_words already set a precise message */ } else { if (nparam != (int)PBI_CSF_HEADER_WORDS) { rcw_set_error(ctx, "\"loadsecurityheader\" needs exactly %d hex words or a " "quoted file path, got %d: %s", (int)PBI_CSF_HEADER_WORDS, nparam, line); return RCW_ERR_PBI_SYNTAX; } for (size_t i = 0; i < PBI_CSF_HEADER_WORDS; i++) csf[i] = params[i]; } err = pbi_pack_word(ctx, PBL_CMD_GENERAL | (PBI_GENCMD_LOAD_SEC_HDR << PBI_CMD_SHIFT)); if (err != RCW_OK) return err; for (size_t i = 0; i < PBI_CSF_HEADER_WORDS; i++) { err = pbi_pack_word(ctx, csf[i]); if (err != RCW_OK) return err; } return RCW_OK; } else if (strcmp(op, "loadboot1csfptr") == 0) { if (nparam < 1) { rcw_set_error(ctx, "\"loadboot1csfptr\" requires 1 parameter (CSF pointer): %s", line); return RCW_ERR_PBI_SYNTAX; } /* * LX2160A RM Table 42 "Load Boot 1 CSF Header Pointer", 8 bytes: * word 0 HDR=0x80, CMD=0x22 * word 1 CSF_PTR -- must be 4-byte aligned (the RM's only check) * * Points at the CSF header authenticating the Boot 1 / SPL image. * Secure boot only, and likewise never executed on this board. */ if (params[0] % 4u != 0u) { rcw_set_error(ctx, "\"loadboot1csfptr\" pointer 0x%x must be 4-byte aligned " "(RM Table 42): %s", params[0], line); return RCW_ERR_PBI_SYNTAX; } err = pbi_pack_word(ctx, PBL_CMD_GENERAL | (PBI_GENCMD_LOAD_CSF_PTR << PBI_CMD_SHIFT)); if (err != RCW_OK) return err; return pbi_pack_word(ctx, params[0]); } else if (strcmp(op, "ccsrcopy") == 0) { if (nparam < 3) { rcw_set_error(ctx, "\"ccsrcopy\" requires 3 parameters (src, dest, words): %s", line); return RCW_ERR_PBI_SYNTAX; } /* * LX2160A RM Table 37 "CCSR Write from Address", 16 bytes: * word 0 HDR=0x80, CMD=0x02, low 2 bits = WORD TYPE * word 1 SRC_ADDR [31:0] -- randomly accessible memory (OCRAM/SPRAM) * word 2 DEST_ADDR [27:0] -- CCSR, upper nibble reserved * word 3 COPY LENGTH in words * * Word type is the width of the DESTINATION CCSR registers, not of the * copy: 1 = 8-bit, 2 = 16-bit, 3 = 32-bit, and 0 is explicitly invalid. * The suffix names the width in bytes (.b1/.b2/.b4) to match awrite. */ uint32_t wtype; if (strcmp(suffix, "b1") == 0) wtype = 1; else if (strcmp(suffix, "b2") == 0) wtype = 2; else if (strcmp(suffix, "b4") == 0) wtype = 3; else { rcw_set_error(ctx, "\"ccsrcopy\" needs a width suffix .b1, .b2 or .b4 " "(destination register width; word type 0 is invalid): %s", line); return RCW_ERR_PBI_SYNTAX; } if (params[1] > PBI_CCSR_ADDR_MAX) { rcw_set_error(ctx, "\"ccsrcopy\" destination 0x%x exceeds the 28-bit CCSR field: %s", params[1], line); return RCW_ERR_PBI_SYNTAX; } err = pbi_pack_word(ctx, PBL_CMD_GENERAL | (PBI_GENCMD_CCSR_COPY << PBI_CMD_SHIFT) | wtype); if (err != RCW_OK) return err; err = pbi_pack_word(ctx, params[0]); if (err != RCW_OK) return err; err = pbi_pack_word(ctx, params[1]); if (err != RCW_OK) return err; return pbi_pack_word(ctx, params[2]); } else if (strcmp(op, "ccsrset") == 0 || strcmp(op, "ccsrclear") == 0 || strcmp(op, "ccsrreplace") == 0) { /* * LX2160A RM Table 43 "CCSR Read, Modify and Write", 16 bytes: * word 0 HDR=0x80, CMD=0x42, bits [3:2] ops type, [1:0] mask type * word 1 Destination CCSR Address [27:0] * word 2 Mask * word 3 Data -- REPLACE only; ignored for set/clear * * ops type: 0 set (reg |= mask), 1 clear (reg &= ~mask), * 2 replace (reg = (reg & ~mask) | (data & mask)), 3 reserved. * mask type: 1 = MASK[7:0], 2 = MASK[15:0], 3 = MASK[31:0]; 0 invalid. * * DOC NOTE: RM Table 43 is titled "CCSR Write from Address" and prints * "CMD = 0x02", but its body is the read-modify-write semantics and * 0x02 is already Table 37's opcode. Table 27 "PBI Command Summary" * lists this command as 0x42, which is what we emit -- two commands * cannot share an opcode. * * The mask width is spelled explicitly rather than derived from the * mask value: it selects the ACCESS WIDTH of the register, so deriving * .b1 from a mask of 0x000000FF would silently turn a 32-bit register * access into an 8-bit one. */ uint32_t ops = (strcmp(op, "ccsrset") == 0) ? PBI_RMW_OPS_SET : (strcmp(op, "ccsrclear") == 0) ? PBI_RMW_OPS_CLEAR : PBI_RMW_OPS_REPLACE; int want = (ops == PBI_RMW_OPS_REPLACE) ? 3 : 2; if (nparam < want) { rcw_set_error(ctx, "\"%s\" requires %d parameters (addr, mask%s): %s", op, want, (ops == PBI_RMW_OPS_REPLACE) ? ", data" : "", line); return RCW_ERR_PBI_SYNTAX; } uint32_t mtype; if (strcmp(suffix, "b1") == 0) mtype = 1; else if (strcmp(suffix, "b2") == 0) mtype = 2; else if (strcmp(suffix, "b4") == 0) mtype = 3; else { rcw_set_error(ctx, "\"%s\" needs a mask-width suffix .b1, .b2 or .b4 " "(mask type 0 is invalid): %s", op, line); return RCW_ERR_PBI_SYNTAX; } /* RM: destination CCSR address must be above 0x100_0000. */ if (params[0] <= 0x1000000u) { rcw_set_error(ctx, "\"%s\" destination 0x%x must be greater than 0x1000000: %s", op, params[0], line); return RCW_ERR_PBI_SYNTAX; } if (params[0] > PBI_CCSR_ADDR_MAX) { rcw_set_error(ctx, "\"%s\" destination 0x%x exceeds the 28-bit CCSR field: %s", op, params[0], line); return RCW_ERR_PBI_SYNTAX; } /* RM: 4-byte mask needs a 4-byte aligned address, 2-byte needs 2. */ if (mtype == 3u && (params[0] % 4u) != 0u) { rcw_set_error(ctx, "\"%s.b4\" address 0x%x must be 4-byte aligned: %s", op, params[0], line); return RCW_ERR_PBI_SYNTAX; } if (mtype == 2u && (params[0] % 2u) != 0u) { rcw_set_error(ctx, "\"%s.b2\" address 0x%x must be 2-byte aligned: %s", op, params[0], line); return RCW_ERR_PBI_SYNTAX; } err = pbi_pack_word(ctx, PBL_CMD_GENERAL | (PBI_GENCMD_CCSR_RMW << PBI_CMD_SHIFT) | (ops << PBI_RMW_OPS_SHIFT) | mtype); if (err != RCW_OK) return err; err = pbi_pack_word(ctx, params[0]); if (err != RCW_OK) return err; err = pbi_pack_word(ctx, params[1]); if (err != RCW_OK) return err; /* Data word is present for all three; the SP ignores it unless REPLACE. */ return pbi_pack_word(ctx, (ops == PBI_RMW_OPS_REPLACE) ? params[2] : 0u); } else if (strcmp(op, "loadc") == 0 || strcmp(op, "loadcondition") == 0) { if (nparam < 2) { rcw_set_error(ctx, "\"%s\" requires 2 parameters (addr, mask): %s", op, line); return RCW_ERR_PBI_SYNTAX; } /* * LX2160A RM Table 40 "Load Condition Command Format", 12 bytes: * word 0 HDR=0x80, CMD=0x14 * word 1 ADDR -- read at PBI time, typically a CCSR register * word 2 MASK -- ANDed with the loaded value * The result is kept in an internal SP variable and consumed by the * next conditional jump. */ err = pbi_pack_word(ctx, PBL_CMD_GENERAL | (PBI_GENCMD_LOADCONDITION << PBI_CMD_SHIFT)); if (err != RCW_OK) return err; err = pbi_pack_word(ctx, params[0]); if (err != RCW_OK) return err; return pbi_pack_word(ctx, params[1]); } else if (strcmp(op, "jump") == 0) { return encode_jump(ctx, op, line, PBI_GENCMD_JUMP, label, params, nparam, 1); } else if (strcmp(op, "jumpc") == 0 || strcmp(op, "jumpif") == 0) { return encode_jump(ctx, op, line, PBI_GENCMD_JUMP_COND, label, params, nparam, 2); } else if (strcmp(op, "blockcopy") == 0) { if (nparam < 4) { rcw_set_error(ctx, "\"blockcopy\" requires 4 parameters: %s", line); return RCW_ERR_PBI_SYNTAX; } if (params[0] > PBI_BLOCKCOPY_SRC_MAX) { rcw_set_error(ctx, "\"blockcopy\" SRC 0x%x exceeds 8-bit interface field " "(max 0x%x): %s", params[0], PBI_BLOCKCOPY_SRC_MAX, line); return RCW_ERR_PBI_SYNTAX; } err = pbi_pack_word(ctx, PBL_CMD_BLOCKCOPY | params[0]); if (err != RCW_OK) return err; for (int i = 1; i < 4; i++) { err = pbi_pack_word(ctx, params[i]); if (err != RCW_OK) return err; } return RCW_OK; } else if (strcmp(op, "loadacwindow") == 0) { if (nparam < 1) { rcw_set_error(ctx, "\"loadacwindow\" requires 1 parameter: %s", line); return RCW_ERR_PBI_SYNTAX; } if (params[0] > PBI_ACWINDOW_MAX) { rcw_set_error(ctx, "\"loadacwindow\" 0x%x exceeds 14-bit field " "(max 0x%x); upper bits would corrupt the CMD byte: %s", params[0], PBI_ACWINDOW_MAX, line); return RCW_ERR_PBI_SYNTAX; } return pbi_pack_word(ctx, PBL_CMD_LOADACWINDOW | params[0]); } else if (strcmp(op, "flush") == 0) { if (ctx->vars.pbiformat == 2) { rcw_set_error(ctx, "\"flush\" is a legacy command with no pbiformat=2 encoding; " "use \"poll ,0,0\", a Poll with mask 0 is the flush: %s", line); return RCW_ERR_PBI_SYNTAX; } err = pbi_pack_word(ctx, PBL_CMD_FLUSH | (ctx->vars.pbladdr & RCW_PBLADDR_MASK)); if (err != RCW_OK) return err; return pbi_pack_word(ctx, 0); } rcw_set_error(ctx, "Unknown PBI command: %s", line); return RCW_ERR_PBI_SYNTAX; } vjardin-libqoriq-rcw-175bf56/src/rcw_pbl.c000066400000000000000000000137731526740601600205260ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * PBL image envelope, PBI check and image verdict */ #include #include "rcw_internal.h" #define PBL_RCW_PBI_LENGTH_BEGIN 287 #define PBL_RCW_PBI_LENGTH_END 276 #define PBL_RCW_SB_EN 266 static void rcw_fields(const uint8_t *rcw, rcw_pbl_t *pbl) { pbl->rcw = rcw; pbl->pbi_length = (uint32_t)rcw_bits_extract(rcw, PBL_RCW_PBI_LENGTH_BEGIN, PBL_RCW_PBI_LENGTH_END, true); pbl->sb_en = rcw_bits_extract(rcw, PBL_RCW_SB_EN, PBL_RCW_SB_EN, true) != 0; } rcw_error_t rcw_pbl_parse(const uint8_t *buf, size_t len, int littleendian, rcw_pbl_t *pbl) { rcw_pbi_cmd_t cmd; memset(pbl, 0, sizeof(*pbl)); if (len < RCW_PBL_PBI_OFFSET) return RCW_ERR_TRUNCATED; if (rcw_unpack32(buf, littleendian) != PBL_PREAMBLE) return RCW_ERR_BAD_BINARY; if (rcw_pbi_decode(buf + PBI_WORD_BYTES, len - PBI_WORD_BYTES, littleendian, &cmd) != RCW_OK || (cmd.op != RCW_PBI_LOAD_RCW && cmd.op != RCW_PBI_LOAD_RCW_NOCS)) return RCW_ERR_BAD_BINARY; pbl->load_rcw_cmd = cmd.word; pbl->checksum = cmd.args[PBL_RCW_WORDS]; pbl->checksum_computed = PBL_PREAMBLE + cmd.word; for (size_t i = 0; i < PBL_RCW_WORDS; i++) pbl->checksum_computed += cmd.args[i]; rcw_fields(buf + RCW_PBL_RCW_OFFSET, pbl); pbl->pbi = buf + RCW_PBL_PBI_OFFSET; pbl->pbi_avail = len - RCW_PBL_PBI_OFFSET; return RCW_OK; } void rcw_pbl_from_rcw(const uint8_t rcw[RCW_SIZE_BYTES], rcw_pbl_t *pbl) { memset(pbl, 0, sizeof(*pbl)); rcw_fields(rcw, pbl); } uint32_t rcw_pbi_crc(const uint8_t *pbi, size_t len) { uint32_t crc = rcw_crc32_update(RCW_CRC32_INIT, pbi, len, true); return rcw_reverse_word(crc) ^ RCW_CRC32_INIT; } #define PBI_JUMP_MIN_OFFSET 8 static bool terminator(rcw_pbi_op_t op) { return op == RCW_PBI_STOP || op == RCW_PBI_CRC_STOP; } static rcw_error_t pbl_walk(const rcw_pbl_t *pbl, int littleendian, rcw_pbi_visit_t visit, void *arg, rcw_pbi_check_t *chk, size_t *term_end) { size_t len = PBI_WORD_BYTES * (size_t)pbl->pbi_length; size_t avail = (len < pbl->pbi_avail) ? len : pbl->pbi_avail; size_t off = 0; bool condition = false, stopped = false, conditional = false; rcw_error_t err = RCW_OK; const rcw_pbi_cmd_t *last; rcw_pbi_cmd_t final; memset(chk, 0, sizeof(*chk)); *term_end = 0; chk->len = len; chk->stop_op = RCW_PBI_UNKNOWN; chk->cmd.op = RCW_PBI_UNKNOWN; if (len == 0) return RCW_OK; while (off < avail) { err = rcw_pbi_decode(pbl->pbi + off, avail - off, littleendian, &chk->cmd); chk->end = off; if (err == RCW_ERR_BAD_BINARY) return err; if (err != RCW_OK) break; chk->ncmd++; if (visit) visit(&chk->cmd, off, arg); if (terminator(chk->cmd.op)) { off += chk->cmd.size; chk->end = off; *term_end = off; stopped = true; break; } if (chk->cmd.op == RCW_PBI_LOADCONDITION) condition = true; if (chk->cmd.op == RCW_PBI_JUMP || chk->cmd.op == RCW_PBI_JUMP_COND) { uint32_t offset = chk->cmd.args[0]; if (offset % PBI_WORD_BYTES != 0 || offset <= PBI_JUMP_MIN_OFFSET || offset >= len - off) return RCW_ERR_PBI_JUMP; if (chk->cmd.op == RCW_PBI_JUMP_COND) { if (!condition) return RCW_ERR_PBI_NO_CONDITION; conditional = true; break; } off += offset; continue; } off += chk->cmd.size; chk->end = off; } if (avail < len) return RCW_ERR_TRUNCATED; if (!stopped && !conditional) return err == RCW_ERR_TRUNCATED ? RCW_ERR_PBI_OVERRUN : RCW_ERR_PBI_LENGTH; last = &chk->cmd; if (chk->end < len || conditional) { if (rcw_pbi_decode(pbl->pbi + len - PBL_TERMINATOR_BYTES, PBL_TERMINATOR_BYTES, littleendian, &final) != RCW_OK || !terminator(final.op)) return RCW_ERR_PBI_LENGTH; last = &final; } chk->stop_op = last->op; if (last->op == RCW_PBI_CRC_STOP) { chk->crc = last->args[0]; chk->crc_computed = rcw_pbi_crc(pbl->pbi, len - PBI_WORD_BYTES); } return conditional ? RCW_ERR_PBI_CONDITIONAL : RCW_OK; } rcw_error_t rcw_pbl_check(const rcw_pbl_t *pbl, int littleendian, rcw_pbi_visit_t visit, void *arg, rcw_pbi_check_t *chk) { size_t term_end; return pbl_walk(pbl, littleendian, visit, arg, chk, &term_end); } rcw_error_t rcw_pbl_verify(const uint8_t *buf, size_t len, int littleendian, rcw_pbi_visit_t visit, void *arg, rcw_pbl_report_t *rep) { size_t term_end = 0; size_t pbi_end; bool checked; memset(rep, 0, sizeof(*rep)); rep->image_end = len; if (len >= 2 * PBI_WORD_BYTES) { rep->word0 = rcw_unpack32(buf, littleendian); rep->word1 = rcw_unpack32(buf + PBI_WORD_BYTES, littleendian); rep->preamble_ok = rep->word0 == PBL_PREAMBLE; rep->load_rcw_ok = rep->word1 == PBL_CMD_LOAD_RCW || rep->word1 == PBL_CMD_LOAD_RCW_NOCS; } rep->envelope = rcw_pbl_parse(buf, len, littleendian, &rep->pbl); rep->pbi = rep->envelope; if (rep->envelope != RCW_OK) return rep->envelope; rep->has_checksum = rep->pbl.load_rcw_cmd == PBL_CMD_LOAD_RCW; rep->checksum_ok = !rep->has_checksum || rep->pbl.checksum == rep->pbl.checksum_computed; rep->pbi = pbl_walk(&rep->pbl, littleendian, visit, arg, &rep->chk, &term_end); rep->terminated = term_end > 0; checked = rep->pbi == RCW_OK || rep->pbi == RCW_ERR_PBI_CONDITIONAL; rep->crc_ok = checked && (rep->chk.stop_op != RCW_PBI_CRC_STOP || rep->chk.crc == rep->chk.crc_computed); rep->ok = rep->checksum_ok && rep->pbi == RCW_OK && rep->crc_ok; if (checked) pbi_end = rep->chk.len; else if (rep->terminated) pbi_end = term_end; else pbi_end = rep->pbl.pbi_avail; if (RCW_PBL_PBI_OFFSET + pbi_end < len) rep->image_end = RCW_PBL_PBI_OFFSET + pbi_end; if (!rep->checksum_ok) return RCW_ERR_CHECKSUM; if (!checked) return rep->pbi; if (!rep->crc_ok) return RCW_ERR_CRC; if (rep->pbi != RCW_OK) return rep->pbi; return RCW_OK; } vjardin-libqoriq-rcw-175bf56/src/rcw_preprocess.c000066400000000000000000000064571526740601600221370ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * C preprocessor wrapper - embeds mcpp (Matsui C Preprocessor) to * expand #include / #define / #ifdef / #if in .rcw/.rcwi files. * * No fork/exec, no gcc at runtime: the whole preprocessor runs * in-process via libmcpp, so the library works on targets that do * not ship a toolchain. * * See http://mcpp.sourceforge.net/ - mcpp is BSD-2-Clause licensed * and validated against the ISO C99/C11 preprocessor conformance tests. */ #include #include #include #include #include #include #include "rcw_internal.h" /* * Build an argv for mcpp_lib_main(): * * mcpp -P -I . [-I path]... [-Dname=value]... * * -P Suppress #line marker output. * -I Add a directory to the include search path. * -D Define a macro, from rcw_ctx_add_define() * * argv[0] is conventionally the program name. mcpp itself ignores it. * Max argv slots: "mcpp" + "-P" + "-I" + "." + 2*N includes + M defines * + input + NULL * = 6 + 2*N + M */ #define MCPP_ARGV_FIXED 6 rcw_error_t rcw_preprocess(const rcw_ctx_t *ctx, const char *input_path, char **out, size_t *out_len) { size_t max_argv = MCPP_ARGV_FIXED + 2 * ctx->include_path_count + ctx->define_count; char **argv = calloc(max_argv, sizeof(char *)); if (!argv) return RCW_ERR_NOMEM; int argc = 0; argv[argc++] = (char *)"mcpp"; argv[argc++] = (char *)"-P"; argv[argc++] = (char *)"-I"; argv[argc++] = (char *)"."; for (size_t i = 0; i < ctx->include_path_count; i++) { argv[argc++] = (char *)"-I"; argv[argc++] = ctx->include_paths[i]; } for (size_t i = 0; i < ctx->define_count; i++) argv[argc++] = ctx->defines[i]; argv[argc++] = (char *)input_path; argv[argc] = NULL; /* * Ask mcpp to buffer output in memory instead of writing to fp_out / * fp_err. The buffers are owned by mcpp; we strdup() what we need * before returning since the caller will call mcpp_lib_main() again * on the next preprocess invocation and that resets the buffers. */ mcpp_use_mem_buffers(1); int rc = mcpp_lib_main(argc, argv); char *mcpp_out = mcpp_get_mem_buffer(OUT); char *mcpp_err = mcpp_get_mem_buffer(ERR); /* Forward mcpp diagnostics to stderr (mirrors the gcc -E behavior) */ if (mcpp_err && *mcpp_err) fprintf(stderr, "%s", mcpp_err); free(argv); if (rc != 0) { /* * mcpp_get_mem_buffer(OUT) is still owned by mcpp after a failed * run; we do not free it here. The next mcpp_lib_main() call * (if any) will reset the buffers. */ return RCW_ERR_PREPROCESS; } if (!mcpp_out) { /* No error but also no output - treat as empty input */ char *empty = malloc(1); if (!empty) return RCW_ERR_NOMEM; empty[0] = '\0'; *out = empty; *out_len = 0; return RCW_OK; } /* * Copy the mcpp output buffer to a caller-owned allocation so the * caller can free it with free() / rcw_free() and so subsequent * mcpp invocations on the same thread do not clobber it. */ size_t len = strlen(mcpp_out); char *buf = malloc(len + 1); if (!buf) return RCW_ERR_NOMEM; memcpy(buf, mcpp_out, len + 1); *out = buf; *out_len = len; return RCW_OK; } vjardin-libqoriq-rcw-175bf56/src/rcw_uboot.c000066400000000000000000000165261526740601600211000ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * .uboot subsection encoder. * * Mirrors rcw.py build_pbi_uboot() (lines 219-272). Generates PBI * commands from an xxd hex dump of u-boot.bin for SPI/SD/NAND boot * chains (refer: Chapter 10, u-boot of QorIQ_SDK_Infocenter.pdf). * * No upstream pbiformat=2 board (LS1028, LS1088, LS2088, LX2160) uses * .uboot subsections, so this code path is provided for completeness * and parity with rcw.py - it is not exercised by the cross-validation * test suite. * * Input format (one xxd line, after `xxd u-boot.bin | cut -d ' ' -f1-10`): * * ADDRESS: WORD WORD WORD WORD WORD WORD WORD WORD * * where ADDRESS is hex with a trailing ':' and each WORD is a 4-character * hex value representing a 16-bit big-endian word (16 bytes per line). * * Grouping rules: * - The last 4 lines are emitted as 2 pairs (32 bytes each) prefixed * by RCW_UBOOT_PAIR_BASE + (line_addr - 0x10). * - All earlier lines are emitted in groups of 4 (64 bytes each) * prefixed by RCW_UBOOT_QUAD_BASE + (line_addr - 0x30). * * Output is always big-endian regardless of %littleendian. */ #include #include #include #include #include "rcw_internal.h" /* Pack a 32-bit big-endian word and append to PBI buffer. */ static rcw_error_t ub_pack_be32(rcw_ctx_t *ctx, uint32_t v) { uint8_t buf[4]; buf[0] = (uint8_t)((v >> 24) & 0xFF); buf[1] = (uint8_t)((v >> 16) & 0xFF); buf[2] = (uint8_t)((v >> 8) & 0xFF); buf[3] = (uint8_t)( v & 0xFF); return rcw_pbi_append(&ctx->pbi, buf, 4); } /* Pack a 16-bit big-endian word and append to PBI buffer. */ static rcw_error_t ub_pack_be16(rcw_ctx_t *ctx, uint16_t v) { uint8_t buf[2]; buf[0] = (uint8_t)((v >> 8) & 0xFF); buf[1] = (uint8_t)( v & 0xFF); return rcw_pbi_append(&ctx->pbi, buf, 2); } /* * Parse one xxd-style line: hex address followed by ':' followed by * RCW_UBOOT_WORDS_PER_LINE 16-bit hex words separated by whitespace. */ static rcw_error_t parse_xxd_line(const char *line, uint32_t *addr, uint16_t words[RCW_UBOOT_WORDS_PER_LINE]) { char *endp; unsigned long a = strtoul(line, &endp, 16); if (endp == line || *endp != ':') return RCW_ERR_PBI_SYNTAX; *addr = (uint32_t)a; endp++; /* skip ':' */ for (int i = 0; i < RCW_UBOOT_WORDS_PER_LINE; i++) { while (*endp == ' ' || *endp == '\t') endp++; char *next; unsigned long w = strtoul(endp, &next, 16); if (next == endp) return RCW_ERR_PBI_SYNTAX; words[i] = (uint16_t)w; endp = next; } return RCW_OK; } /* * Count non-blank lines in a body. * Needed for a first pass: the grouping rule depends on whether the * current line is in the last RCW_UBOOT_TAIL_LINES of the input. */ static size_t count_lines(const char *body, size_t len) { size_t n = 0; const char *p = body; const char *end = body + len; while (p < end) { const char *eol = memchr(p, '\n', (size_t)(end - p)); size_t llen = eol ? (size_t)(eol - p) : (size_t)(end - p); bool blank = true; for (size_t i = 0; i < llen; i++) { unsigned char c = (unsigned char)p[i]; /* Treat NUL as blank too - some callers pass C-string buffers * including the trailing NUL by accident, and a NUL byte in the * middle of a buffer can never be valid xxd input anyway. */ if (c != '\0' && !isspace(c)) { blank = false; break; } } if (!blank) n++; p = eol ? eol + 1 : end; } return n; } rcw_error_t rcw_uboot_encode(rcw_ctx_t *ctx, const char *body, size_t len) { size_t nlines = count_lines(body, len); if (nlines == 0) return RCW_OK; /* * Accumulator for pending words. Sized for the larger of the two * block types (quad block = 32 16-bit words). */ uint16_t buf[RCW_UBOOT_QUAD_LINES * RCW_UBOOT_WORDS_PER_LINE]; size_t buf_cnt = 0; size_t cnt = 0; /* 1-indexed line counter, matches rcw.py */ const char *p = body; const char *end = body + len; while (p < end) { const char *eol = memchr(p, '\n', (size_t)(end - p)); size_t llen = eol ? (size_t)(eol - p) : (size_t)(end - p); /* Skip blank lines */ bool blank = true; for (size_t i = 0; i < llen; i++) { unsigned char c = (unsigned char)p[i]; /* Treat NUL as blank too - some callers pass C-string buffers * including the trailing NUL by accident, and a NUL byte in the * middle of a buffer can never be valid xxd input anyway. */ if (c != '\0' && !isspace(c)) { blank = false; break; } } if (blank) { p = eol ? eol + 1 : end; continue; } /* Copy the line into a NUL-terminated buffer for parsing */ if (llen >= RCW_UBOOT_LINE_MAX) { rcw_set_error(ctx, ".uboot line too long (%zu bytes)", llen); return RCW_ERR_PBI_SYNTAX; } char line[RCW_UBOOT_LINE_MAX]; memcpy(line, p, llen); line[llen] = '\0'; uint32_t addr; uint16_t words[RCW_UBOOT_WORDS_PER_LINE]; rcw_error_t err = parse_xxd_line(line, &addr, words); if (err != RCW_OK) { rcw_set_error(ctx, "Bad .uboot xxd line: %s", line); return err; } cnt++; /* Append this line's words to the accumulator */ for (int i = 0; i < RCW_UBOOT_WORDS_PER_LINE; i++) buf[buf_cnt++] = words[i]; /* * Decide whether this line completes a block. * Matches rcw.py: * if (cnt % 2 == 0) and (cnt > len(lines) - 4): # last 4 lines * emit 32-byte pair * elif (cnt % 4 == 0): * emit 64-byte quad */ bool last_tail = (nlines >= RCW_UBOOT_TAIL_LINES) && (cnt > nlines - RCW_UBOOT_TAIL_LINES); if (last_tail && (cnt % RCW_UBOOT_PAIR_LINES == 0)) { if (addr < 0x10u) { rcw_set_error(ctx, ".uboot pair-line address 0x%x below 0x10 " "(would underflow header base)", addr); return RCW_ERR_PBI_SYNTAX; } uint32_t hdr = RCW_UBOOT_PAIR_BASE + (addr - 0x10); err = ub_pack_be32(ctx, hdr); if (err != RCW_OK) return err; size_t nwords = RCW_UBOOT_PAIR_LINES * RCW_UBOOT_WORDS_PER_LINE; for (size_t i = 0; i < nwords; i++) { err = ub_pack_be16(ctx, buf[i]); if (err != RCW_OK) return err; } buf_cnt = 0; } else if (cnt % RCW_UBOOT_QUAD_LINES == 0) { if (addr < 0x30u) { rcw_set_error(ctx, ".uboot quad-line address 0x%x below 0x30 " "(would underflow header base)", addr); return RCW_ERR_PBI_SYNTAX; } uint32_t hdr = RCW_UBOOT_QUAD_BASE + (addr - 0x30); err = ub_pack_be32(ctx, hdr); if (err != RCW_OK) return err; size_t nwords = RCW_UBOOT_QUAD_LINES * RCW_UBOOT_WORDS_PER_LINE; for (size_t i = 0; i < nwords; i++) { err = ub_pack_be16(ctx, buf[i]); if (err != RCW_OK) return err; } buf_cnt = 0; } p = eol ? eol + 1 : end; } /* * If anything remains in the accumulator, the input did not end on * a block boundary. rcw.py silently drops the trailing partial * group; do the same but emit a debug-style error detail. */ if (buf_cnt > 0) { rcw_set_error(ctx, ".uboot input has %zu line(s) past the last full block", buf_cnt / RCW_UBOOT_WORDS_PER_LINE); /* Not an error - rcw.py tolerates this silently */ } return RCW_OK; } vjardin-libqoriq-rcw-175bf56/tests/000077500000000000000000000000001526740601600172725ustar00rootroot00000000000000vjardin-libqoriq-rcw-175bf56/tests/check_exports.sh000077500000000000000000000010361526740601600224720ustar00rootroot00000000000000#!/bin/sh # SPDX-License-Identifier: BSD-3-Clause # Copyright 2026 Free Mobile - Vincent Jardin # # The version script must list exactly the functions rcw.h declares. set -eu header=$1 map=$2 tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT grep -oE '\brcw_[a-z0-9_]+\(' "$header" | tr -d '(' | sort -u > "$tmp/header" sed -n 's/^ *\(rcw_[a-z0-9_]*\);$/\1/p' "$map" | sort -u > "$tmp/map" if ! diff -u "$tmp/header" "$tmp/map"; then echo "src/libqoriq-rcw.map and include/qoriq-rcw/rcw.h disagree (- header only, + map only)" >&2 exit 1 fi vjardin-libqoriq-rcw-175bf56/tests/meson.build000066400000000000000000000024551526740601600214420ustar00rootroot00000000000000cmocka_dep = dependency('cmocka', required : true) test_sources = { 'test_crc32' : files('test_crc32.c'), 'test_bits' : files('test_bits.c'), 'test_parse' : files('test_parse.c'), 'test_pbi' : files('test_pbi.c'), 'test_uboot' : files('test_uboot.c'), 'test_binary' : files('test_binary.c'), 'test_decompile' : files('test_decompile.c'), 'test_cond' : files('test_cond.c'), 'test_density' : files('test_density.c'), 'test_secure' : files('test_secure.c'), 'test_decode' : files('test_decode.c'), 'test_pbl' : files('test_pbl.c'), 'test_preprocess': files('test_preprocess.c'), 'test_source' : files('test_source.c'), 'test_context' : files('test_context.c'), } # The suites call internal functions, which the shared library does not export. libqoriq_rcw_internal = static_library('qoriq-rcw-internal', lib_sources, include_directories : [inc, src_inc], dependencies : [mcpp_dep]) foreach name, src : test_sources exe = executable(name, src, link_with : libqoriq_rcw_internal, dependencies : [mcpp_dep, cmocka_dep], include_directories : [inc, src_inc]) test(name, exe) endforeach test('check_exports', find_program('check_exports.sh'), args : [meson.project_source_root() / 'include' / 'qoriq-rcw' / 'rcw.h', mapfile]) vjardin-libqoriq-rcw-175bf56/tests/test_binary.c000066400000000000000000000303661526740601600217710ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ /* * Tests use mkstemp/strdup/unlink (POSIX). Enable POSIX.1-2008 so the * prototypes are visible under -std=c17. Must come BEFORE any include. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "rcw_internal.h" /* * Minimal source for a nocrc board (lx2160a-style). * This is what gcc -E would produce after preprocessing. */ static const char nocrc_source[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n" "SYS_PLL_RAT=7\n" ".pbi\n" "write 0x01e00620,0x80000000\n" ".end\n"; /* Test nocrc binary generation (Stop command, no CRC) */ static void test_binary_nocrc(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); uint8_t *out = NULL; size_t len = 0; rcw_error_t err = rcw_compile_buffer(ctx, nocrc_source, strlen(nocrc_source), &out, &len); assert_int_equal(err, RCW_OK); assert_non_null(out); /* * Expected layout: * 4 bytes: preamble 0xAA55AA55 (LE) * 4 bytes: Load RCW cmd 0x80100000 (LE) 128 bytes: RCW data * 4 bytes: checksum * 8 bytes: PBI write command * 4 bytes: Stop 0x80FF0000 (LE) * 4 bytes: 0x00000000 * Total: 4+4+128+4+8+4+4 = 156 */ assert_int_equal(len, 156); /* Check preamble (LE) */ assert_int_equal(out[0], 0x55); assert_int_equal(out[1], 0xAA); assert_int_equal(out[2], 0x55); assert_int_equal(out[3], 0xAA); /* Check Stop command near the end (LE: 0x80FF0000) */ size_t stop_off = len - 8; uint32_t stop_word = (uint32_t)out[stop_off] | ((uint32_t)out[stop_off+1] << 8) | ((uint32_t)out[stop_off+2] << 16) | ((uint32_t)out[stop_off+3] << 24); assert_int_equal(stop_word, 0x80FF0000); rcw_free(out); rcw_ctx_free(ctx); } /* Test no-pbl mode (raw RCW bits only) */ static void test_binary_no_pbl(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "SYS_PLL_RAT[6:2]\n" "SYS_PLL_RAT=7\n"; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); rcw_ctx_set_pbl(ctx, 0); uint8_t *out = NULL; size_t len = 0; rcw_error_t err = rcw_compile_buffer(ctx, src, strlen(src), &out, &len); assert_int_equal(err, RCW_OK); assert_non_null(out); /* No PBL: just 128 bytes of RCW data */ assert_int_equal(len, 128); rcw_free(out); rcw_ctx_free(ctx); } static uint32_t word_in(const uint8_t *p, int littleendian) { return littleendian ? (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24) : ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; } static void test_binary_checksum_follows_word_order(void **state) { (void)state; for (int le = 1; le >= 0; le--) { char src[512]; snprintf(src, sizeof(src), "%%size=1024\n" "%%pbiformat=2\n" "%%classicbitnumbers=1\n" "%%littleendian=%d\n" "%%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "MEM_PLL_RAT[15:10]\n" "PBI_LENGTH[287:276]\n" "SYS_PLL_RAT=14\n" "MEM_PLL_RAT=21\n" ".pbi\n" "write 0x01e00620,0x80000000\n" ".end\n", le); rcw_ctx_t *ctx = rcw_ctx_new(); uint8_t *out = NULL; size_t len = 0; assert_int_equal(rcw_compile_buffer(ctx, src, strlen(src), &out, &len), RCW_OK); assert_int_equal(word_in(out, le), PBL_PREAMBLE); uint32_t sum = 0; for (size_t i = 0; i < RCW_PBL_CHECKSUM_OFFSET; i += 4) sum += word_in(out + i, le); assert_int_equal(word_in(out + RCW_PBL_CHECKSUM_OFFSET, le), sum); /* and the envelope reader agrees with itself on the same image */ rcw_pbl_t pbl; assert_int_equal(rcw_pbl_parse(out, len, le, &pbl), RCW_OK); assert_int_equal(pbl.checksum, pbl.checksum_computed); rcw_free(out); rcw_ctx_free(ctx); } } /* * Minimal source for a CRC board (ls1088-style, no %nocrc). */ static const char crc_source[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n" "SYS_PLL_RAT=7\n" ".pbi\n" "write 0x01e00620,0x80000000\n" ".end\n"; /* Test CRC binary generation (CRC-and-Stop command) */ static void test_binary_with_crc(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); uint8_t *out = NULL; size_t len = 0; rcw_error_t err = rcw_compile_buffer(ctx, crc_source, strlen(crc_source), &out, &len); assert_int_equal(err, RCW_OK); assert_non_null(out); /* Same size as nocrc: 156 bytes */ assert_int_equal(len, 156); /* Check CRC-and-Stop command near the end (LE: 0x808F0000) */ size_t crc_off = len - 8; uint32_t crc_word = (uint32_t)out[crc_off] | ((uint32_t)out[crc_off+1] << 8) | ((uint32_t)out[crc_off+2] << 16) | ((uint32_t)out[crc_off+3] << 24); assert_int_equal(crc_word, 0x808F0000); /* CRC value should be non-zero */ uint32_t crc_val = (uint32_t)out[crc_off+4] | ((uint32_t)out[crc_off+5] << 8) | ((uint32_t)out[crc_off+6] << 16) | ((uint32_t)out[crc_off+7] << 24); assert_true(crc_val != 0); rcw_free(out); rcw_ctx_free(ctx); } /* Test that PBI_LENGTH is auto-calculated */ static void test_binary_pbi_length_auto(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); uint8_t *out = NULL; size_t len = 0; rcw_error_t err = rcw_compile_buffer(ctx, nocrc_source, strlen(nocrc_source), &out, &len); assert_int_equal(err, RCW_OK); /* PBI_LENGTH should have been set: 1 write = 2 words of PBI, +2 for Stop = 4 */ bool found = false; for (size_t i = 0; i < ctx->symbols.count; i++) { if (strcmp(ctx->symbols.entries[i].name, "PBI_LENGTH") == 0) { assert_true(ctx->symbols.entries[i].has_value); assert_int_equal(ctx->symbols.entries[i].value, 4); found = true; break; } } assert_true(found); rcw_free(out); rcw_ctx_free(ctx); } /* * Helpers for testing the PBL_SP_SCRATCH_CEILING_BYTES warning. * * The warning is emitted to stderr inside rcw_binary_generate(); the * tests below redirect stderr to a temp file, run a compile, then * read the file back to assert presence/absence of the warning text. */ /* Redirect stderr to a fresh temp file. Returns the path (caller frees). */ static char * redirect_stderr_to_tmp(void) { char *path = strdup("/tmp/libqoriq_rcw_test_stderr_XXXXXX"); assert_non_null(path); int fd = mkstemp(path); assert_true(fd >= 0); close(fd); fflush(stderr); FILE *fp = freopen(path, "w", stderr); assert_non_null(fp); return path; } /* Restore stderr to /dev/tty (best-effort) and unlink the temp file. */ static void restore_stderr(char *path) { fflush(stderr); /* * Reattach stderr to /dev/null so subsequent prints don't go to the * old (now-deleted) temp file. Tests don't depend on a TTY here. */ freopen("/dev/null", "w", stderr); unlink(path); free(path); } /* Read a file fully into a malloc'd null-terminated buffer. */ static char * read_file(const char *path) { FILE *fp = fopen(path, "r"); assert_non_null(fp); fseek(fp, 0, SEEK_END); long n = ftell(fp); fseek(fp, 0, SEEK_SET); assert_true(n >= 0); char *buf = malloc((size_t)n + 1); assert_non_null(buf); size_t got = fread(buf, 1, (size_t)n, fp); buf[got] = '\0'; fclose(fp); return buf; } /* * Build a synthetic .rcw source whose PBI body is large enough to * push the total binary at-or-over the ceiling. * * Each `wait` PBI command is 4 bytes (one 32-bit word). The fixed * overhead in a PBL binary is: * 8 bytes (preamble + Load RCW cmd) * + RCW_SIZE_BYTES (= 128 bytes RCW body) * + 4 bytes (checksum) * + 8 bytes (terminator: Stop or CRC+Stop) * = 148 bytes. * * To hit total >= ceiling we need at least * ceil((ceiling - 148) / 4) wait commands. * The caller passes `target_total` and we round the wait count up so * the produced binary is `target_total` bytes (or a few bytes more). */ static char * build_oversized_source(size_t target_total) { const char header[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n" "SYS_PLL_RAT=7\n" ".pbi\n"; const char footer[] = ".end\n"; const char wait_line[] = "wait 0x0001\n"; size_t fixed_overhead = 148; size_t pbi_needed = (target_total > fixed_overhead) ? (target_total - fixed_overhead) : 0; size_t nwaits = (pbi_needed + 3) / 4; /* round up: each wait = 4 B */ size_t cap = sizeof(header) + sizeof(footer) + nwaits * sizeof(wait_line) + 64; char *src = malloc(cap); assert_non_null(src); size_t pos = 0; pos += (size_t)snprintf(src + pos, cap - pos, "%s", header); for (size_t i = 0; i < nwaits; i++) pos += (size_t)snprintf(src + pos, cap - pos, "%s", wait_line); pos += (size_t)snprintf(src + pos, cap - pos, "%s", footer); src[pos] = '\0'; return src; } /* * Test that compiling a SMALL PBL emits NO warning on stderr. * * Uses the existing nocrc_source (156 bytes total) -- well below * PBL_SP_SCRATCH_CEILING_BYTES. */ static void test_binary_no_warning_under_ceiling(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); char *log_path = redirect_stderr_to_tmp(); uint8_t *out = NULL; size_t len = 0; rcw_error_t err = rcw_compile_buffer(ctx, nocrc_source, strlen(nocrc_source), &out, &len); assert_int_equal(err, RCW_OK); assert_int_equal(len, 156); assert_true(len < PBL_SP_SCRATCH_CEILING_BYTES); fflush(stderr); char *captured = read_file(log_path); /* No warning text should appear at all -- stderr stays empty. */ assert_int_equal(strlen(captured), 0); free(captured); restore_stderr(log_path); rcw_free(out); rcw_ctx_free(ctx); } /* * Test that compiling a PBL at-or-over PBL_SP_SCRATCH_CEILING_BYTES * emits a warning to stderr. * * Builds a synthetic source large enough to land the binary just * over the ceiling, captures stderr, and asserts the expected * warning markers are present. */ static void test_binary_warning_at_ceiling(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); /* * Aim for ceiling + 4 bytes (one extra wait beyond the boundary) * so the warning fires on the very first oversized build. */ size_t target = PBL_SP_SCRATCH_CEILING_BYTES + 4; char *src = build_oversized_source(target); char *log_path = redirect_stderr_to_tmp(); uint8_t *out = NULL; size_t len = 0; rcw_error_t err = rcw_compile_buffer(ctx, src, strlen(src), &out, &len); assert_int_equal(err, RCW_OK); assert_true(len >= PBL_SP_SCRATCH_CEILING_BYTES); fflush(stderr); char *captured = read_file(log_path); /* * The warning is multi-line. We assert on a few stable substrings * rather than the whole text, so cosmetic re-wording of the * warning doesn't break this test. */ assert_non_null(strstr(captured, "warning:")); assert_non_null(strstr(captured, "SP scratch ceiling")); assert_non_null(strstr(captured, "boot SILENTLY")); /* The actual size and the ceiling should both appear in the text. */ char buf[64]; snprintf(buf, sizeof(buf), "%zu", len); assert_non_null(strstr(captured, buf)); snprintf(buf, sizeof(buf), "%u", PBL_SP_SCRATCH_CEILING_BYTES); assert_non_null(strstr(captured, buf)); free(captured); free(src); restore_stderr(log_path); rcw_free(out); rcw_ctx_free(ctx); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_binary_nocrc), cmocka_unit_test(test_binary_checksum_follows_word_order), cmocka_unit_test(test_binary_no_pbl), cmocka_unit_test(test_binary_with_crc), cmocka_unit_test(test_binary_pbi_length_auto), cmocka_unit_test(test_binary_no_warning_under_ceiling), cmocka_unit_test(test_binary_warning_at_ceiling), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_bits.c000066400000000000000000000111671526740601600214440ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #include #include #include #include #include #include #include "rcw_internal.h" /* Test packing a single field: SYS_PLL_RAT[6:2] = 14 */ static void test_pack_single_field(void **state) { (void)state; rcw_symtab_t syms = {0}; rcw_vars_t vars = { .size = 1024, .pbiformat = 2, .classicbitnumbers = false, .littleendian = true, }; /* SYS_PLL_RAT[6:2] - 5-bit field at bits 2..6 */ rcw_symbol_t *sym = &syms.entries[syms.count++]; snprintf(sym->name, RCW_FIELD_NAME_MAX, "SYS_PLL_RAT"); sym->begin = 6; sym->end = 2; sym->has_value = true; sym->value = 14; uint8_t out[RCW_SIZE_BYTES]; rcw_error_t err = rcw_bits_pack(&syms, &vars, out); assert_int_equal(err, RCW_OK); /* Without classicbitnumbers, bit 2..6 (MSB-first) should have value * 14 = 0b01110. In MSB-first layout: * bit 0 = MSB of byte 0 * bits 2..6 are in byte 0 * byte 0 bit pattern: 0b00_01110_0 = 0x38? Let's check. * * Actually, since begin(6) > end(2), the value gets bit-reversed. * 14 = 0b01110, reversed(5 bits) = 0b01110 (palindrome). * * Bit positions 2..6, value 14 = 0b01110. * set_bits sets bit e-i for bit i of value. * bit 6: (14>>0)&1 = 0 * bit 5: (14>>1)&1 = 1 * bit 4: (14>>2)&1 = 1 * bit 3: (14>>3)&1 = 1 * bit 2: (14>>4)&1 = 0 * In byte 0 (bits 0-7, MSB-first): bits 2-6 = 0,1,1,1,0 * byte 0 = 0b00_01110_0 = 0x1C * Wait: bit 0 is MSB (bit 7 of byte), bit 7 is LSB (bit 0 of byte) * byte0 = bit0<<7 | bit1<<6 | bit2<<5 | bit3<<4 | bit4<<3 | * bit5<<2 | bit6<<1 | bit7<<0 * = 0<<7 | 0<<6 | 0<<5 | 1<<4 | 1<<3 | 1<<2 | 0<<1 | 0<<0 * = 0x1C */ assert_int_equal(out[0], 0x1C); /* Rest should be zero */ for (int i = 1; i < RCW_SIZE_BYTES; i++) assert_int_equal(out[i], 0); } /* Test with classicbitnumbers=1 (per-byte bit reversal in output) */ static void test_pack_classicbitnumbers(void **state) { (void)state; rcw_symtab_t syms = {0}; rcw_vars_t vars = { .size = 1024, .pbiformat = 2, .classicbitnumbers = true, .littleendian = true, }; /* SYS_PLL_RAT[6:2] = 14 */ rcw_symbol_t *sym = &syms.entries[syms.count++]; snprintf(sym->name, RCW_FIELD_NAME_MAX, "SYS_PLL_RAT"); sym->begin = 6; sym->end = 2; sym->has_value = true; sym->value = 14; uint8_t out[RCW_SIZE_BYTES]; rcw_error_t err = rcw_bits_pack(&syms, &vars, out); assert_int_equal(err, RCW_OK); /* * Before bit-reversal, byte 0 = 0x1C = 0b00011100 * After per-byte bit-reversal: 0b00111000 = 0x38 */ assert_int_equal(out[0], 0x38); } /* Test extracting a field value (round-trip with pack) */ static void test_extract_roundtrip(void **state) { (void)state; rcw_symtab_t syms = {0}; rcw_vars_t vars = { .size = 1024, .pbiformat = 2, .classicbitnumbers = true, .littleendian = true, }; /* Set up several fields */ rcw_symbol_t *s; s = &syms.entries[syms.count++]; snprintf(s->name, RCW_FIELD_NAME_MAX, "SYS_PLL_RAT"); s->begin = 6; s->end = 2; s->has_value = true; s->value = 14; s = &syms.entries[syms.count++]; snprintf(s->name, RCW_FIELD_NAME_MAX, "MEM_PLL_RAT"); s->begin = 15; s->end = 10; s->has_value = true; s->value = 21; s = &syms.entries[syms.count++]; snprintf(s->name, RCW_FIELD_NAME_MAX, "BOOT_LOC"); s->begin = 264; s->end = 260; s->has_value = true; s->value = 26; uint8_t packed[RCW_SIZE_BYTES]; rcw_error_t err = rcw_bits_pack(&syms, &vars, packed); assert_int_equal(err, RCW_OK); /* Extract and verify */ uint64_t v; v = rcw_bits_extract(packed, 6, 2, true); assert_int_equal(v, 14); v = rcw_bits_extract(packed, 15, 10, true); assert_int_equal(v, 21); v = rcw_bits_extract(packed, 264, 260, true); assert_int_equal(v, 26); } /* Test field with no value set (should leave bits as zero) */ static void test_pack_no_value(void **state) { (void)state; rcw_symtab_t syms = {0}; rcw_vars_t vars = { .size = 1024, .pbiformat = 2, .classicbitnumbers = false, }; rcw_symbol_t *sym = &syms.entries[syms.count++]; snprintf(sym->name, RCW_FIELD_NAME_MAX, "TEST"); sym->begin = 7; sym->end = 0; sym->has_value = false; uint8_t out[RCW_SIZE_BYTES]; rcw_error_t err = rcw_bits_pack(&syms, &vars, out); assert_int_equal(err, RCW_OK); /* All zeros */ for (int i = 0; i < RCW_SIZE_BYTES; i++) assert_int_equal(out[i], 0); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_pack_single_field), cmocka_unit_test(test_pack_classicbitnumbers), cmocka_unit_test(test_extract_roundtrip), cmocka_unit_test(test_pack_no_value), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_cond.c000066400000000000000000000267201526740601600214270ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * PBI flow control: * - Load Condition (0x14) * - Jump (0x84) * - Conditional Jump (0x85) * aliased as loadc / jump / jumpc too * * Both jumps are FORWARD ONLY. The RM specifies two error checks for each * (4-byte alignment, and OFFSET strictly greater than 8) and both are * asserted below, because a misaligned offset lands mid-command and an * offset of 8 targets the word immediately after the command. */ #include #include #include #include #include #include #include #include #include "rcw_internal.h" /* Minimal preamble: enough field definitions for a valid image. */ #define HDR \ "%size=1024\n" \ "%pbiformat=2\n" \ "%classicbitnumbers=1\n" \ "%littleendian=1\n" \ "%nocrc=1\n" \ "SYS_PLL_RAT[6:2]\n" \ "PBI_LENGTH[287:276]\n" \ "SYS_PLL_RAT=7\n" static uint8_t *compile_ok(const char *src, size_t *len) { rcw_ctx_t *c = rcw_ctx_new(); assert_non_null(c); uint8_t *bin = NULL; assert_int_equal(rcw_compile_buffer(c, src, strlen(src), &bin, len), RCW_OK); assert_non_null(bin); rcw_ctx_free(c); return bin; } static void compile_fails(const char *src, const char *why) { rcw_ctx_t *c = rcw_ctx_new(); assert_non_null(c); uint8_t *bin = NULL; size_t len = 0; assert_int_equal(rcw_compile_buffer(c, src, strlen(src), &bin, &len), RCW_ERR_PBI_SYNTAX); if (why) assert_non_null(strstr(rcw_ctx_last_error_detail(c), why)); free(bin); rcw_ctx_free(c); } static void compile_same(const char *a, const char *b) { size_t la = 0, lb = 0; uint8_t *ba = compile_ok(a, &la); uint8_t *bb = compile_ok(b, &lb); assert_int_equal(la, lb); assert_memory_equal(ba, bb, la); free(ba); free(bb); } /* Little-endian word fetch, matching %littleendian=1 above. */ static uint32_t word_at(const uint8_t *b, size_t off) { return (uint32_t)b[off] | ((uint32_t)b[off + 1] << 8) | ((uint32_t)b[off + 2] << 16) | ((uint32_t)b[off + 3] << 24); } /* Find a 32-bit word in the image; -1 if absent. */ static long find_word(const uint8_t *b, size_t len, uint32_t want) { for (size_t i = 0; i + 4 <= len; i += 4) if (word_at(b, i) == want) return (long)i; return -1; } static void test_loadc_encoding(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok( HDR ".pbi\nloadc 0x01E00100,0x000000FF\n.end\n", &len); long at = find_word(bin, len, 0x80140000u); /* RM Table 40 header */ assert_true(at >= 0); assert_int_equal(word_at(bin, (size_t)at + 4), 0x01E00100u); /* ADDR */ assert_int_equal(word_at(bin, (size_t)at + 8), 0x000000FFu); /* MASK */ free(bin); compile_same(HDR ".pbi\nloadc 0x01E00100,0x000000FF\n.end\n", HDR ".pbi\nloadcondition 0x01E00100,0x000000FF\n.end\n"); } static void test_jump_encoding(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR ".pbi\njump 0x0000000C\n.end\n", &len); long at = find_word(bin, len, 0x80840000u); /* RM Table 46 header */ assert_true(at >= 0); assert_int_equal(word_at(bin, (size_t)at + 4), 0x0000000Cu); /* OFFSET */ free(bin); } static void test_jumpc_encoding(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR ".pbi\njumpc 0x00000010,0x00000005\n.end\n", &len); long at = find_word(bin, len, 0x80850000u); /* RM Table 47 header */ assert_true(at >= 0); assert_int_equal(word_at(bin, (size_t)at + 4), 0x00000010u); /* OFFSET */ assert_int_equal(word_at(bin, (size_t)at + 8), 0x00000005u); /* CONDITION */ free(bin); compile_same(HDR ".pbi\njumpc 0x10,0x5\n.end\n", HDR ".pbi\njumpif 0x10,0x5\n.end\n"); } /* * Sizes are load-bearing: the SP walks the PBI linearly, so a command that * consumes the wrong number of words desynchronises everything after it. * Compile one of each in isolation and check the image grows by exactly the * RM-specified amount over an empty PBI. */ static void test_command_sizes(void **state) { (void)state; size_t base = 0, l_cond = 0, l_jump = 0, l_jumpc = 0; uint8_t *b0 = compile_ok(HDR ".pbi\n.end\n", &base); uint8_t *b1 = compile_ok(HDR ".pbi\nloadc 0x1,0x2\n.end\n", &l_cond); uint8_t *b2 = compile_ok(HDR ".pbi\njump 0x0C\n.end\n", &l_jump); uint8_t *b3 = compile_ok(HDR ".pbi\njumpc 0x0C,0x1\n.end\n", &l_jumpc); assert_int_equal(l_cond - base, 12); /* RM Table 40 */ assert_int_equal(l_jump - base, 8); /* RM Table 46 */ assert_int_equal(l_jumpc - base, 12); /* RM Table 47 */ free(b0); free(b1); free(b2); free(b3); } /* RM Table 46/47 error check: OFFSET must be 4-byte aligned. */ static void test_offset_must_be_aligned(void **state) { (void)state; compile_fails(HDR ".pbi\njump 0x0000000A\n.end\n", "aligned"); compile_fails(HDR ".pbi\njumpc 0x0000000A,0x1\n.end\n", "aligned"); compile_fails(HDR ".pbi\njumpif 0x0000000A,0x1\n.end\n", "aligned"); } /* * RM Table 46/47 error check: OFFSET must be greater than 8. Exactly 8 is * the boundary case and is rejected: it would target the word right after * the command, which the SP treats as an error rather than a no-op. */ static void test_offset_must_exceed_eight(void **state) { (void)state; compile_fails(HDR ".pbi\njump 0x00000008\n.end\n", "greater than 8"); compile_fails(HDR ".pbi\njump 0x00000004\n.end\n", "greater than 8"); compile_fails(HDR ".pbi\njumpc 0x00000008,0x1\n.end\n", "greater than 8"); compile_fails(HDR ".pbi\njumpc 0x00000004,0x1\n.end\n", "greater than 8"); } static void test_missing_operands_rejected(void **state) { (void)state; compile_fails(HDR ".pbi\nloadc 0x01E00100\n.end\n", "requires 2"); compile_fails(HDR ".pbi\njump\n.end\n", "requires 1"); compile_fails(HDR ".pbi\njumpc 0x0C\n.end\n", "requires 2"); compile_fails(HDR ".pbi\njumpc done\n.end\n", "requires 2"); } /* * lx2160asi/bootlocptr_auto.rcw in the upstream rcw tree, SolidRun's * auto-boot: loadc PORSR1[RCW_SRC], then per boot source a jumpc over a * jump that skips its block copy. */ #define AUTO_BOOT \ ".pbi\n" \ "loadc 0x01e00000, (0xf << 23)\n" \ "jumpc sdhc1_boot, (0x8 << 23)\n" \ "jump sdhc1_done\n" \ "sdhc1_boot:\n" \ "blockcopy 0x08, 0x0000a000, 0x1800d000, 0x00020000\n" \ "write 0x01e00400, 0x1800d000\n" \ "sdhc1_done:\n" \ "jumpc sdhc2_boot, (0x9 << 23)\n" \ "jump sdhc2_done\n" \ "sdhc2_boot:\n" \ "blockcopy 0x09, 0x0000a000, 0x1800d000, 0x00020000\n" \ "write 0x01e00400, 0x1800d000\n" \ "sdhc2_done:\n" \ "jumpc xspi_boot, (0xf << 23)\n" \ "jump xspi_done\n" \ "xspi_boot:\n" \ "blockcopy 0x0f, 0x20009000, 0x1800d000, 0x00020000\n" \ "write 0x01e00400, 0x1800d000\n" \ "xspi_done:\n" \ ".end\n" static const uint32_t auto_boot_words[] = { 0x80140000, 0x01e00000, 0x07800000, 0x80850000, 0x00000014, 0x04000000, 0x80840000, 0x00000020, 0x80000008, 0x0000a000, 0x1800d000, 0x00020000, 0x31e00400, 0x1800d000, 0x80850000, 0x00000014, 0x04800000, 0x80840000, 0x00000020, 0x80000009, 0x0000a000, 0x1800d000, 0x00020000, 0x31e00400, 0x1800d000, 0x80850000, 0x00000014, 0x07800000, 0x80840000, 0x00000020, 0x8000000f, 0x20009000, 0x1800d000, 0x00020000, 0x31e00400, 0x1800d000, 0x80ff0000, 0x00000000, /* Stop */ }; static void test_labels_resolve_as_rcwpy(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR AUTO_BOOT, &len); size_t n = sizeof(auto_boot_words) / sizeof(auto_boot_words[0]); assert_int_equal(len, RCW_PBL_PBI_OFFSET + 4 * n); for (size_t i = 0; i < n; i++) assert_int_equal(word_at(bin, RCW_PBL_PBI_OFFSET + 4 * i), auto_boot_words[i]); free(bin); /* the same block with the offsets written out */ compile_same(HDR AUTO_BOOT, HDR ".pbi\n" "loadc 0x01e00000,0x07800000\n" "jumpc 0x14,0x04000000\n" "jump 0x20\n" "blockcopy 0x08,0x0000a000,0x1800d000,0x00020000\n" "write 0x01e00400,0x1800d000\n" "jumpc 0x14,0x04800000\n" "jump 0x20\n" "blockcopy 0x09,0x0000a000,0x1800d000,0x00020000\n" "write 0x01e00400,0x1800d000\n" "jumpc 0x14,0x07800000\n" "jump 0x20\n" "blockcopy 0x0f,0x20009000,0x1800d000,0x00020000\n" "write 0x01e00400,0x1800d000\n" ".end\n"); } /* A label right after an 8-byte jump is the offset 8 we refuse. */ static void test_label_too_close(void **state) { (void)state; compile_fails(HDR ".pbi\njump next\nnext:\nwait 1\n.end\n", "more than 8"); /* the conditional jump is 12 bytes, so the same layout is fine */ size_t len = 0; uint8_t *bin = compile_ok(HDR ".pbi\njumpc next,1\nnext:\nwait 1\n.end\n", &len); long at = find_word(bin, len, 0x80850000u); assert_true(at >= 0); assert_int_equal(word_at(bin, (size_t)at + 4), 12); free(bin); } static void test_label_errors(void **state) { (void)state; compile_fails(HDR ".pbi\njump out\nwait 1\nout:\nwait 1\nout:\n.end\n", "Duplicate label \"out\""); compile_fails(HDR ".pbi\njump nowhere\nwait 1\n.end\n", "\"jump nowhere\": label \"nowhere\" is not defined"); compile_fails(HDR ".pbi\njumpc nowhere,1\nwait 1\n.end\n", "\"jumpc nowhere\": label \"nowhere\" is not defined"); /* jump is forward only */ compile_fails(HDR ".pbi\nback:\nwait 1\njump back\n.end\n", "behind the jump"); compile_fails(HDR ".pbi\njump nowhere\nwait 1\n", "is not defined"); char name[RCW_LABEL_NAME_MAX + 8]; memset(name, 'a', sizeof(name) - 1); name[sizeof(name) - 1] = '\0'; char src[1024]; snprintf(src, sizeof(src), "%s.pbi\njump %s\nwait 1\n%s:\n.end\n", HDR, name, name); compile_fails(src, "too long"); } static void test_labels_are_per_block(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok( HDR ".pbi\njump out\nwait 1\nout:\n.end\n" ".pbi\njump out\nwait 2\nout:\n.end\n", &len); /* each jump skips its own wait: 12 bytes */ assert_int_equal(word_at(bin, RCW_PBL_PBI_OFFSET + 4), 12); assert_int_equal(word_at(bin, RCW_PBL_PBI_OFFSET + 12 + 4), 12); free(bin); compile_fails(HDR ".pbi\nwait 1\n.end\n.pbi\njump later\n.end\n.pbi\nlater:\n.end\n", "is not defined in this .pbi block"); } static void test_label_names_and_block_end(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok( HDR ".pbi\njump write\nwait 1\nwait 1\nwrite:\nwrite 0x01e00400,0x1\n.end\n", &len); assert_int_equal(word_at(bin, RCW_PBL_PBI_OFFSET + 4), 16); free(bin); bin = compile_ok(HDR ".pbi\njump done\nwait 1\ndone:\n.end\n", &len); assert_int_equal(word_at(bin, RCW_PBL_PBI_OFFSET + 4), 12); /* the jump lands on the Stop command */ assert_int_equal(word_at(bin, RCW_PBL_PBI_OFFSET + 12), 0x80ff0000u); free(bin); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_loadc_encoding), cmocka_unit_test(test_jump_encoding), cmocka_unit_test(test_jumpc_encoding), cmocka_unit_test(test_command_sizes), cmocka_unit_test(test_offset_must_be_aligned), cmocka_unit_test(test_offset_must_exceed_eight), cmocka_unit_test(test_missing_operands_rejected), cmocka_unit_test(test_labels_resolve_as_rcwpy), cmocka_unit_test(test_label_too_close), cmocka_unit_test(test_label_errors), cmocka_unit_test(test_labels_are_per_block), cmocka_unit_test(test_label_names_and_block_end), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_context.c000066400000000000000000000104511526740601600221620ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * The command-line forms of the context configuration: * rcw_ctx_set_bitfield_text() ("NAME=VALUE" à la rcw.py -D) and * rcw_ctx_add_define() with "NAME=VALUE" as a C compiler's -D */ #include #include #include #include #include #include #include #include "rcw_internal.h" #define HDR \ "%size=1024\n" \ "%pbiformat=2\n" \ "%classicbitnumbers=1\n" \ "%littleendian=1\n" \ "%nocrc=1\n" \ "SYS_PLL_RAT[6:2]\n" \ "PBI_LENGTH[287:276]\n" \ "SYS_PLL_RAT=7\n" static void test_set_bitfield_text(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "SYS_PLL_RAT=0b1110"), RCW_OK); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "MEM_PLL_RAT=0x1F"), RCW_OK); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "A_9=0o17"), RCW_OK); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "B=10"), RCW_OK); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "C=0"), RCW_OK); assert_int_equal(ctx->override_count, 5); assert_string_equal(ctx->overrides[0].name, "SYS_PLL_RAT"); assert_int_equal(ctx->overrides[0].value, 14); assert_string_equal(ctx->overrides[1].name, "MEM_PLL_RAT"); assert_int_equal(ctx->overrides[1].value, 31); assert_int_equal(ctx->overrides[2].value, 15); assert_int_equal(ctx->overrides[3].value, 10); assert_int_equal(ctx->overrides[4].value, 0); /* Python refuses a leading zero; rcw.py's regex passes no sign or space. */ assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "B=010"), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "B=-1"), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "B= 1"), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "B=1x"), RCW_ERR_PARSE); assert_non_null(strstr(rcw_ctx_last_error_detail(ctx), "1x")); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "B="), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "=1"), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "FOO"), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "FO O=1"), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, ""), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, NULL), RCW_ERR_PARSE); assert_int_equal(ctx->override_count, 5); rcw_ctx_free(ctx); /* The override reaches the image: SYS_PLL_RAT[6:2]=14 is 0x38 in RCW byte 0. */ ctx = rcw_ctx_new(); assert_non_null(ctx); assert_int_equal(rcw_ctx_set_bitfield_text(ctx, "SYS_PLL_RAT=0b1110"), RCW_OK); uint8_t *bin = NULL; size_t len = 0; assert_int_equal(rcw_compile_buffer(ctx, HDR, strlen(HDR), &bin, &len), RCW_OK); assert_int_equal(bin[RCW_PBL_RCW_OFFSET], 0x38); free(bin); rcw_ctx_free(ctx); } static void test_add_define_text(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); assert_int_equal(rcw_ctx_add_define(ctx, "FOO=1", NULL), RCW_OK); assert_int_equal(rcw_ctx_add_define(ctx, "BAR", NULL), RCW_OK); assert_int_equal(rcw_ctx_add_define(ctx, "_X9", "2"), RCW_OK); assert_int_equal(rcw_ctx_add_define(ctx, "EMPTY=", NULL), RCW_OK); assert_int_equal(ctx->define_count, 4); assert_string_equal(ctx->defines[0], "-DFOO=1"); assert_string_equal(ctx->defines[1], "-DBAR"); assert_string_equal(ctx->defines[2], "-D_X9=2"); assert_string_equal(ctx->defines[3], "-DEMPTY="); assert_int_equal(rcw_ctx_add_define(ctx, "=3", NULL), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_add_define(ctx, "9X", NULL), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_add_define(ctx, "FO-O", NULL), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_add_define(ctx, "FOO=1", "2"), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_add_define(ctx, "", NULL), RCW_ERR_PARSE); assert_int_equal(rcw_ctx_add_define(ctx, NULL, NULL), RCW_ERR_PARSE); assert_int_equal(ctx->define_count, 4); rcw_ctx_free(ctx); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_set_bitfield_text), cmocka_unit_test(test_add_define_text), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_crc32.c000066400000000000000000000032131526740601600214100ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #include #include #include #include #include #include "rcw_internal.h" /* Test CRC of an empty buffer */ static void test_crc32_empty(void **state) { (void)state; uint32_t crc = rcw_crc32(NULL, 0); /* With init=0xFFFFFFFF and no data, CRC should remain 0xFFFFFFFF */ assert_int_equal(crc, 0xFFFFFFFF); } /* Test CRC of a single zero byte */ static void test_crc32_single_zero(void **state) { (void)state; uint8_t data[] = {0x00}; uint32_t crc = rcw_crc32(data, 1); /* Known value for this polynomial with single zero byte */ assert_int_equal(crc, 0x4E08BFB4); } /* Test CRC of a known pattern "123456789" (ASCII) */ static void test_crc32_ascii_check(void **state) { (void)state; const uint8_t data[] = "123456789"; uint32_t crc = rcw_crc32(data, 9); /* * The standard CRC-32 check value for polynomial 0x04C11DB7 * with init 0xFFFFFFFF and no final XOR is 0x0376E6E7. */ assert_int_equal(crc, 0x0376E6E7); } /* Test CRC of all-FF data (8 bytes) */ static void test_crc32_all_ff(void **state) { (void)state; uint8_t data[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; uint32_t crc = rcw_crc32(data, 8); /* Verify it produces a deterministic result */ assert_true(crc != 0); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_crc32_empty), cmocka_unit_test(test_crc32_single_zero), cmocka_unit_test(test_crc32_ascii_check), cmocka_unit_test(test_crc32_all_ff), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_decode.c000066400000000000000000000260041526740601600217220ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * PBI command decoder, rcw_pbi_decode(). */ #include #include #include #include #include #include #include #include #include "rcw_internal.h" static void put_le32(uint8_t *p, uint32_t w) { p[0] = (uint8_t)w; p[1] = (uint8_t)(w >> 8); p[2] = (uint8_t)(w >> 16); p[3] = (uint8_t)(w >> 24); } static void put_be32(uint8_t *p, uint32_t w) { p[0] = (uint8_t)(w >> 24); p[1] = (uint8_t)(w >> 16); p[2] = (uint8_t)(w >> 8); p[3] = (uint8_t)w; } struct layout { const char *name; uint32_t word; rcw_pbi_op_t op; size_t nargs; uint32_t param; uint32_t width; }; static const struct layout layouts[] = { { "write", 0x31e00400, RCW_PBI_CCSR_WRITE, 1, 0x01e00400, 4 }, { "write.b1", 0x121c0500, RCW_PBI_CCSR_WRITE, 1, 0x021c0500, 1 }, { "awrite", 0x8c117e60, RCW_PBI_ALTCFG_WRITE, 1, 0x00117e60, 4 }, { "awrite.b4", 0x92508000, RCW_PBI_ALTCFG_WRITE, 2, 0x02508000, 8 }, { "awrite.b5", 0x96518000, RCW_PBI_ALTCFG_WRITE, 4, 0x02518000, 16 }, { "awrite, 1 byte", 0x84000010, RCW_PBI_ALTCFG_WRITE, 1, 0x00000010, 1 }, { "awrite, 512 bytes", 0xa8000000, RCW_PBI_ALTCFG_WRITE, 128, 0x00000000, 512 }, { "blockcopy", 0x8000000f, RCW_PBI_BLOCKCOPY, 3, 0x0f, 0 }, { "ccsrcopy.b4", 0x80020003, RCW_PBI_CCSR_COPY, 3, 0x03, 0 }, { "loadrcw", 0x80100000, RCW_PBI_LOAD_RCW, 33, 0, 0 }, { "loadrcw.nochecksum", 0x80110000, RCW_PBI_LOAD_RCW_NOCS, 33, 0, 0 }, { "loadacwindow", 0x801201c0, RCW_PBI_LOADACWINDOW, 0, 0x1c0, 0 }, { "loadc", 0x80140000, RCW_PBI_LOADCONDITION, 2, 0, 0 }, { "loadsecurityheader", 0x80200000, RCW_PBI_LOAD_SEC_HDR, 20, 0, 0 }, { "loadboot1csfptr", 0x80220000, RCW_PBI_LOAD_CSF_PTR, 1, 0, 0 }, { "ccsrreplace.b4", 0x8042000b, RCW_PBI_CCSR_RMW, 3, 0x0b, 0 }, { "poll.short", 0x80800000, RCW_PBI_POLL_SHORT, 3, 0, 0 }, { "poll.long", 0x80810000, RCW_PBI_POLL_LONG, 3, 0, 0 }, { "wait", 0x80820064, RCW_PBI_WAIT, 0, 0x64, 0 }, { "jump", 0x80840000, RCW_PBI_JUMP, 1, 0, 0 }, { "jumpc", 0x80850000, RCW_PBI_JUMP_COND, 2, 0, 0 }, { "crcstop", 0x808f0000, RCW_PBI_CRC_STOP, 1, 0, 0 }, { "stop", 0x80ff0000, RCW_PBI_STOP, 1, 0, 0 }, }; static void test_layouts(void **state) { (void)state; uint8_t buf[4 + 4 * RCW_PBI_MAX_ARGS + 8]; rcw_pbi_cmd_t cmd; for (size_t i = 0; i < sizeof(layouts) / sizeof(layouts[0]); i++) { const struct layout *l = &layouts[i]; size_t size = 4 + 4 * l->nargs; print_message(" %s\n", l->name); memset(buf, 0xee, sizeof(buf)); put_le32(buf, l->word); for (size_t k = 0; k < l->nargs; k++) put_le32(buf + 4 + 4 * k, 0x1000u + (uint32_t)k); assert_int_equal(rcw_pbi_decode(buf, size, 1, &cmd), RCW_OK); assert_int_equal(cmd.op, l->op); assert_int_equal(cmd.word, l->word); assert_int_equal(cmd.size, size); assert_int_equal(cmd.nargs, l->nargs); assert_int_equal(cmd.param, l->param); assert_int_equal(cmd.width, l->width); for (size_t k = 0; k < l->nargs; k++) assert_int_equal(cmd.args[k], 0x1000u + (uint32_t)k); assert_int_equal(rcw_pbi_decode(buf, size + 8, 1, &cmd), RCW_OK); assert_int_equal(cmd.size, size); if (l->nargs > 0) { assert_int_equal(rcw_pbi_decode(buf, size - 1, 1, &cmd), RCW_ERR_TRUNCATED); assert_int_equal(cmd.op, l->op); assert_int_equal(cmd.word, l->word); assert_int_equal(cmd.size, size); assert_int_equal(cmd.nargs, l->nargs); assert_int_equal(rcw_pbi_decode(buf, 4, 1, &cmd), RCW_ERR_TRUNCATED); assert_int_equal(cmd.size, size); } } } static void test_rejects(void **state) { (void)state; static const uint32_t words[] = { 0x01e00200, /* CCSR Write, B=0: reserved */ 0x21e00200, /* CCSR Write, B=2: 2-byte writes are not legal */ 0x41e00200, /* CCSR Write with RSVD bit 6 set */ 0x71e00200, 0xac000000, /* AltConfig Write, B=11: above 512 bytes */ 0xbc000000, /* AltConfig Write, B=15 */ 0x81000000, /* ACS=1, B=0, RSVD bits [1:0] set */ 0x83ff0000, 0x80010000, /* undefined CMD bytes */ 0x80030000, 0x80300000, 0x80430000, 0x80830000, 0x80860000, 0x80900000, 0x80fe0000, 0xc0000000, /* ACS=1 with RSVD bit 30 set */ 0xffffffff, }; uint8_t buf[16]; rcw_pbi_cmd_t cmd; for (size_t i = 0; i < sizeof(words) / sizeof(words[0]); i++) { print_message(" 0x%08x\n", words[i]); put_le32(buf, words[i]); memset(buf + 4, 0, sizeof(buf) - 4); assert_int_equal(rcw_pbi_decode(buf, sizeof(buf), 1, &cmd), RCW_ERR_BAD_BINARY); assert_int_equal(cmd.op, RCW_PBI_UNKNOWN); assert_int_equal(cmd.word, words[i]); assert_int_equal(cmd.size, 0); } } static void test_too_short(void **state) { (void)state; uint8_t buf[4] = { 0x00, 0x00, 0xff, 0x80 }; rcw_pbi_cmd_t cmd; for (size_t len = 0; len < 4; len++) { assert_int_equal(rcw_pbi_decode(buf, len, 1, &cmd), RCW_ERR_TRUNCATED); assert_int_equal(cmd.op, RCW_PBI_UNKNOWN); assert_int_equal(cmd.size, 0); assert_int_equal(cmd.nargs, 0); } } static void test_big_endian(void **state) { (void)state; uint8_t buf[16]; rcw_pbi_cmd_t cmd; put_be32(buf, 0x8000000f); put_be32(buf + 4, 0x20009000); put_be32(buf + 8, 0x1800d000); put_be32(buf + 12, 0x00001000); assert_int_equal(rcw_pbi_decode(buf, sizeof(buf), 0, &cmd), RCW_OK); assert_int_equal(cmd.op, RCW_PBI_BLOCKCOPY); assert_int_equal(cmd.param, 0x0f); assert_int_equal(cmd.args[0], 0x20009000); assert_int_equal(cmd.args[1], 0x1800d000); assert_int_equal(cmd.args[2], 0x00001000); assert_int_equal(rcw_pbi_decode(buf, sizeof(buf), 1, &cmd), RCW_ERR_BAD_BINARY); } static void test_op_names(void **state) { (void)state; assert_string_equal(rcw_pbi_op_name(RCW_PBI_CCSR_WRITE), "write"); assert_string_equal(rcw_pbi_op_name(RCW_PBI_ALTCFG_WRITE), "awrite"); assert_string_equal(rcw_pbi_op_name(RCW_PBI_BLOCKCOPY), "blockcopy"); assert_string_equal(rcw_pbi_op_name(RCW_PBI_LOAD_SEC_HDR), "loadsecurityheader"); assert_string_equal(rcw_pbi_op_name(RCW_PBI_LOAD_CSF_PTR), "loadboot1csfptr"); assert_string_equal(rcw_pbi_op_name(RCW_PBI_LOADCONDITION), "loadc"); assert_string_equal(rcw_pbi_op_name(RCW_PBI_JUMP), "jump"); assert_string_equal(rcw_pbi_op_name(RCW_PBI_JUMP_COND), "jumpc"); assert_string_equal(rcw_pbi_op_name(RCW_PBI_CRC_STOP), "crcstop"); assert_string_equal(rcw_pbi_op_name(RCW_PBI_STOP), "stop"); assert_string_equal(rcw_pbi_op_name(RCW_PBI_UNKNOWN), "unknown"); assert_string_equal(rcw_pbi_op_name((rcw_pbi_op_t)0x33), "unknown"); for (size_t i = 0; i < sizeof(layouts) / sizeof(layouts[0]); i++) assert_string_not_equal(rcw_pbi_op_name(layouts[i].op), "unknown"); } #define HDR \ "%size=1024\n" \ "%pbiformat=2\n" \ "%classicbitnumbers=1\n" \ "%littleendian=1\n" \ "SYS_PLL_RAT[6:2]\n" \ "PBI_LENGTH[287:276]\n" \ "SYS_PLL_RAT=7\n" #define EVERY_COMMAND \ ".pbi\n" \ "write 0x01e00620,0x80000000\n" \ "write.b1 0x021c0500,0x49\n" \ "awrite 0x117e60,0x80000001\n" \ "awrite.b4 0x2508000,0x64a8150e,0xcfc4885c\n" \ "awrite.b5 0x2518000,0x1,0x2,0x3,0x4\n" \ "blockcopy 0x0f,0x20009000,0x1800d000,0x1000\n" \ "ccsrcopy.b4 0x18000000,0x01e00210,0x4\n" \ "ccsrset.b4 0x01e00620,0x1\n" \ "ccsrreplace.b2 0x01e00622,0xff,0x12\n" \ "loadacwindow 0x1c0\n" \ "loadc 0x01e60130,0xff\n" \ "jump 0x10\n" \ "jumpc 0x14,0x1\n" \ "loadsecurityheader 0x12192001,0x200,0x101,0,0x800,0x100,0x600,0x1," \ "0x1800d000,0,0,0,0,0,0,0,0,0,0,0\n" \ "loadboot1csfptr 0x1800a000\n" \ "poll 0x01e00404,0x1,0x1\n" \ "poll.long 0x01e00404,0x1,0x1\n" \ "wait 0x64\n" \ ".end\n" static const rcw_pbi_op_t every_op[] = { RCW_PBI_CCSR_WRITE, RCW_PBI_CCSR_WRITE, RCW_PBI_ALTCFG_WRITE, RCW_PBI_ALTCFG_WRITE, RCW_PBI_ALTCFG_WRITE, RCW_PBI_BLOCKCOPY, RCW_PBI_CCSR_COPY, RCW_PBI_CCSR_RMW, RCW_PBI_CCSR_RMW, RCW_PBI_LOADACWINDOW, RCW_PBI_LOADCONDITION, RCW_PBI_JUMP, RCW_PBI_JUMP_COND, RCW_PBI_LOAD_SEC_HDR, RCW_PBI_LOAD_CSF_PTR, RCW_PBI_POLL_SHORT, RCW_PBI_POLL_LONG, RCW_PBI_WAIT, }; static uint8_t *compile_ok(const char *src, size_t *len) { rcw_ctx_t *c = rcw_ctx_new(); assert_non_null(c); uint8_t *bin = NULL; assert_int_equal(rcw_compile_buffer(c, src, strlen(src), &bin, len), RCW_OK); assert_non_null(bin); rcw_ctx_free(c); return bin; } static void walk(const char *src, rcw_pbi_op_t terminator) { size_t len = 0; uint8_t *bin = compile_ok(src, &len); size_t off = RCW_PBL_PBI_OFFSET; rcw_pbi_cmd_t cmd; for (size_t i = 0; i < sizeof(every_op) / sizeof(every_op[0]); i++) { print_message(" byte %zu: %s\n", off, rcw_pbi_op_name(every_op[i])); assert_int_equal(rcw_pbi_decode(bin + off, len - off, 1, &cmd), RCW_OK); assert_int_equal(cmd.op, every_op[i]); switch (i) { case 0: assert_int_equal(cmd.param, 0x01e00620); assert_int_equal(cmd.width, 4); assert_int_equal(cmd.args[0], 0x80000000); break; case 1: assert_int_equal(cmd.width, 1); assert_int_equal(cmd.args[0], 0x49); break; case 4: assert_int_equal(cmd.param, 0x2518000); assert_int_equal(cmd.width, 16); assert_int_equal(cmd.args[3], 4); break; case 5: assert_int_equal(cmd.param, 0x0f); assert_int_equal(cmd.args[0], 0x20009000); assert_int_equal(cmd.args[1], 0x1800d000); assert_int_equal(cmd.args[2], 0x1000); break; case 13: assert_int_equal(cmd.nargs, PBI_CSF_HEADER_WORDS); assert_int_equal(cmd.args[0], 0x12192001); assert_int_equal(cmd.args[8], 0x1800d000); break; case 14: assert_int_equal(cmd.args[0], 0x1800a000); break; case 17: assert_int_equal(cmd.param, 0x64); break; default: break; } off += cmd.size; } assert_int_equal(rcw_pbi_decode(bin + off, len - off, 1, &cmd), RCW_OK); assert_int_equal(cmd.op, terminator); assert_int_equal(off + cmd.size, len); free(bin); } static void test_walk_stop(void **state) { (void)state; walk(HDR "%nocrc=1\n" EVERY_COMMAND, RCW_PBI_STOP); } static void test_walk_crc_stop(void **state) { (void)state; walk(HDR EVERY_COMMAND, RCW_PBI_CRC_STOP); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_layouts), cmocka_unit_test(test_rejects), cmocka_unit_test(test_too_short), cmocka_unit_test(test_big_endian), cmocka_unit_test(test_op_names), cmocka_unit_test(test_walk_stop), cmocka_unit_test(test_walk_crc_stop), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_decompile.c000066400000000000000000001000561526740601600224400ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "rcw_internal.h" /* * Round-trip test: compile, then decompile, then recompile. * The two binaries must be identical. */ static const char roundtrip_source[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "MEM_PLL_RAT[15:10]\n" "CGA_PLL1_RAT[31:26]\n" "BOOT_LOC[264:260]\n" "PBI_LENGTH[287:276]\n" "SYSCLK_FREQ[301:292]\n" "SYS_PLL_RAT=7\n" "MEM_PLL_RAT=21\n" "CGA_PLL1_RAT=16\n" "BOOT_LOC=26\n" "SYSCLK_FREQ=0x258\n" ".pbi\n" "write 0x01e00620,0x80000000\n" ".end\n"; static void test_decompile_roundtrip(void **state) { (void)state; /* Step 1: Compile */ rcw_ctx_t *ctx1 = rcw_ctx_new(); assert_non_null(ctx1); uint8_t *bin1 = NULL; size_t bin1_len = 0; rcw_error_t err = rcw_compile_buffer(ctx1, roundtrip_source, strlen(roundtrip_source), &bin1, &bin1_len); assert_int_equal(err, RCW_OK); assert_non_null(bin1); /* Step 2: Decompile */ /* Re-parse the symbol definitions only (simulate .rcwi) */ const char rcwi_source[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "MEM_PLL_RAT[15:10]\n" "CGA_PLL1_RAT[31:26]\n" "BOOT_LOC[264:260]\n" "PBI_LENGTH[287:276]\n" "SYSCLK_FREQ[301:292]\n"; rcw_ctx_t *ctx2 = rcw_ctx_new(); assert_non_null(ctx2); err = rcw_parse(ctx2, rcwi_source, strlen(rcwi_source)); assert_int_equal(err, RCW_OK); char *source_out = NULL; size_t source_len = 0; err = rcw_decompile(ctx2, bin1, bin1_len, &source_out, &source_len); assert_int_equal(err, RCW_OK); assert_non_null(source_out); /* Step 3: Recompile the decompiled source */ rcw_ctx_t *ctx3 = rcw_ctx_new(); assert_non_null(ctx3); /* * The decompiled output doesn't include %variable definitions or * bitfield definitions (those come from the .rcwi). We need to * prepend the rcwi definitions. */ size_t combined_len = strlen(rcwi_source) + source_len; char *combined = malloc(combined_len + 1); assert_non_null(combined); memcpy(combined, rcwi_source, strlen(rcwi_source)); memcpy(combined + strlen(rcwi_source), source_out, source_len); combined[combined_len] = '\0'; uint8_t *bin2 = NULL; size_t bin2_len = 0; err = rcw_compile_buffer(ctx3, combined, combined_len, &bin2, &bin2_len); assert_int_equal(err, RCW_OK); assert_non_null(bin2); /* Step 4: Compare binaries */ assert_int_equal(bin1_len, bin2_len); assert_memory_equal(bin1, bin2, bin1_len); free(combined); rcw_free(source_out); rcw_free(bin1); rcw_free(bin2); rcw_ctx_free(ctx1); rcw_ctx_free(ctx2); rcw_ctx_free(ctx3); } /* Test decompile produces expected field output */ static void test_decompile_fields(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n" "SYS_PLL_RAT=7\n"; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); rcw_ctx_set_pbl(ctx, 0); /* no PBL for simplicity */ uint8_t *bin = NULL; size_t bin_len = 0; rcw_error_t err = rcw_compile_buffer(ctx, src, strlen(src), &bin, &bin_len); assert_int_equal(err, RCW_OK); /* Decompile */ rcw_ctx_t *ctx2 = rcw_ctx_new(); const char rcwi[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n"; err = rcw_parse(ctx2, rcwi, strlen(rcwi)); assert_int_equal(err, RCW_OK); char *source = NULL; size_t slen = 0; err = rcw_decompile(ctx2, bin, bin_len, &source, &slen); assert_int_equal(err, RCW_OK); /* Should contain "SYS_PLL_RAT=7" */ assert_non_null(strstr(source, "SYS_PLL_RAT=7")); rcw_free(source); rcw_free(bin); rcw_ctx_free(ctx); rcw_ctx_free(ctx2); } /* * Test rcw_decompile_buffer(): * - With rcwi_name -> output starts with "#include \n\n" * - With rcwi_name=NULL -> output omits the header * - Field values are extracted correctly in both cases */ static void test_decompile_buffer_with_header(void **state) { (void)state; static const char src[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n" "SYS_PLL_RAT=7\n"; /* Step 1: compile to get a binary */ rcw_ctx_t *ctx = rcw_ctx_new(); rcw_ctx_set_pbl(ctx, 0); uint8_t *bin = NULL; size_t bin_len = 0; rcw_error_t err = rcw_compile_buffer(ctx, src, strlen(src), &bin, &bin_len); assert_int_equal(err, RCW_OK); rcw_ctx_free(ctx); /* Step 2: decompile via the buffer API with rcwi_name="my.rcwi" */ static const char rcwi_pp[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n"; rcw_ctx_t *ctx2 = rcw_ctx_new(); char *source = NULL; size_t source_len = 0; err = rcw_decompile_buffer(ctx2, rcwi_pp, strlen(rcwi_pp), bin, bin_len, "my.rcwi", &source, &source_len); assert_int_equal(err, RCW_OK); assert_non_null(source); /* Header must be the very first thing in the output */ assert_int_equal(strncmp(source, "#include \n\n", strlen("#include \n\n")), 0); /* Body must contain the extracted field */ assert_non_null(strstr(source, "SYS_PLL_RAT=7")); rcw_free(source); rcw_free(bin); rcw_ctx_free(ctx2); } static void test_decompile_buffer_no_header(void **state) { (void)state; static const char src[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n" "SYS_PLL_RAT=7\n"; rcw_ctx_t *ctx = rcw_ctx_new(); rcw_ctx_set_pbl(ctx, 0); uint8_t *bin = NULL; size_t bin_len = 0; rcw_error_t err = rcw_compile_buffer(ctx, src, strlen(src), &bin, &bin_len); assert_int_equal(err, RCW_OK); rcw_ctx_free(ctx); static const char rcwi_pp[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n"; rcw_ctx_t *ctx2 = rcw_ctx_new(); char *source = NULL; size_t source_len = 0; err = rcw_decompile_buffer(ctx2, rcwi_pp, strlen(rcwi_pp), bin, bin_len, NULL, /* no header */ &source, &source_len); assert_int_equal(err, RCW_OK); assert_non_null(source); /* No #include header */ assert_null(strstr(source, "#include")); /* Body still contains the field */ assert_non_null(strstr(source, "SYS_PLL_RAT=7")); rcw_free(source); rcw_free(bin); rcw_ctx_free(ctx2); } /* * Test that rcw_compile_buffer + rcw_decompile_buffer round-trip * produces the same binary (no file I/O, no gcc dependency). */ static void test_decompile_buffer_roundtrip(void **state) { (void)state; static const char src[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "MEM_PLL_RAT[15:10]\n" "BOOT_LOC[264:260]\n" "PBI_LENGTH[287:276]\n" "SYS_PLL_RAT=7\n" "MEM_PLL_RAT=21\n" "BOOT_LOC=26\n"; static const char rcwi_pp[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "MEM_PLL_RAT[15:10]\n" "BOOT_LOC[264:260]\n" "PBI_LENGTH[287:276]\n"; /* Step 1: compile original source */ rcw_ctx_t *ctx1 = rcw_ctx_new(); uint8_t *bin1 = NULL; size_t bin1_len = 0; rcw_error_t err = rcw_compile_buffer(ctx1, src, strlen(src), &bin1, &bin1_len); assert_int_equal(err, RCW_OK); rcw_ctx_free(ctx1); /* Step 2: decompile via buffer API (header included) */ rcw_ctx_t *ctx2 = rcw_ctx_new(); char *decompiled = NULL; size_t decompiled_len = 0; err = rcw_decompile_buffer(ctx2, rcwi_pp, strlen(rcwi_pp), bin1, bin1_len, "test.rcwi", &decompiled, &decompiled_len); assert_int_equal(err, RCW_OK); rcw_ctx_free(ctx2); /* * Step 3: recompile the decompiled output. We must drop the * "#include " line since rcw_compile_buffer expects * already-preprocessed text and there is no file to include from. * Concatenate the rcwi definitions with the body after the header. */ const char *body = strstr(decompiled, "\n\n"); assert_non_null(body); body += 2; /* skip the two newlines after the header */ size_t combined_len = strlen(rcwi_pp) + strlen(body); char *combined = malloc(combined_len + 1); assert_non_null(combined); memcpy(combined, rcwi_pp, strlen(rcwi_pp)); memcpy(combined + strlen(rcwi_pp), body, strlen(body)); combined[combined_len] = '\0'; rcw_ctx_t *ctx3 = rcw_ctx_new(); uint8_t *bin2 = NULL; size_t bin2_len = 0; err = rcw_compile_buffer(ctx3, combined, combined_len, &bin2, &bin2_len); assert_int_equal(err, RCW_OK); /* Step 4: compare binaries */ assert_int_equal(bin1_len, bin2_len); assert_memory_equal(bin1, bin2, bin1_len); free(combined); rcw_free(decompiled); rcw_free(bin1); rcw_free(bin2); rcw_ctx_free(ctx3); } /* * Exercise rcw_preprocess_file(): write a small .rcwi to a tempfile * (with a #define that mcpp must expand), preprocess it, feed the * result to rcw_compile_buffer(). End-to-end this proves the new * public preprocess API correctly invokes mcpp and produces text * the rest of the library can consume. */ static void test_preprocess_file_roundtrip(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "#define RAT 14\n" "SYS_PLL_RAT[6:2]\n" "SYS_PLL_RAT=RAT\n" "PBI_LENGTH[287:276]\n"; char tmpl[] = "/tmp/test-pp-XXXXXX"; int fd = mkstemp(tmpl); assert_true(fd >= 0); ssize_t n = write(fd, src, sizeof(src) - 1); assert_int_equal(n, (ssize_t)(sizeof(src) - 1)); close(fd); rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); char *pp = NULL; size_t pp_len = 0; rcw_error_t err = rcw_preprocess_file(ctx, tmpl, &pp, &pp_len); assert_int_equal(err, RCW_OK); assert_non_null(pp); assert_true(pp_len > 0); /* mcpp expanded RAT -> 14 (it preserves a token boundary, so the * actual line reads "SYS_PLL_RAT= 14"; whitespace is irrelevant * since the parser strips it on non-PBI lines). */ assert_non_null(strstr(pp, "14")); assert_null(strstr(pp, "=RAT")); assert_null(strstr(pp, "= RAT")); /* The #define line itself is consumed by the preprocessor. */ assert_null(strstr(pp, "#define")); /* The preprocessed text round-trips through compile_buffer. */ uint8_t *bin = NULL; size_t bin_len = 0; err = rcw_compile_buffer(ctx, pp, pp_len, &bin, &bin_len); assert_int_equal(err, RCW_OK); assert_true(bin_len > 0); rcw_free(bin); rcw_free(pp); rcw_ctx_free(ctx); unlink(tmpl); } /* * NOTE on mcpp re-entrancy: * * libmcpp keeps a substantial amount of static state and does not * fully reset between mcpp_lib_main() calls in the same process. * The CLI is unaffected (one invocation per fork+exec), but cmocka * test suites can only safely exercise rcw_preprocess_file() / * rcw_compile_file() / rcw_decompile_file() once per test binary. * That is why this file has a single round-trip preprocess test and * routes everything else through the buffer APIs. */ /* * A2 - the disassembler used to round pbi_len up to a multiple of 4 * and then read 4 bytes per word, over-reading up to 3 bytes past * the heap allocation when pbi_len was not aligned. We exercise the * unaligned path under the address sanitizer (it traps any OOB read). */ static void test_decompile_unaligned_pbi_no_oob(void **state) { (void)state; /* * Construct a binary with a valid 128-byte RCW preceded by an 8-byte * PBL header and 4-byte checksum, then a PBI section that ends in * a 1-byte tail (so total PBI region is 5 bytes, not a multiple of 4). * * binary layout: * [0..3] preamble (LE) * [4..7] load RCW cmd (LE) * [8..135] RCW (zeroed) * [136..139] checksum (zero) * [140..144] PBI: 1 valid 4-byte word + 1 dangling byte */ uint8_t bin[145] = {0}; /* preamble */ bin[0] = 0x55; bin[1] = 0xAA; bin[2] = 0x55; bin[3] = 0xAA; /* load-RCW cmd 0x80100000 LE */ bin[4] = 0x00; bin[5] = 0x00; bin[6] = 0x10; bin[7] = 0x80; /* PBI word 0 = a valid Stop command 0x80FF0000 LE */ bin[140] = 0x00; bin[141] = 0x00; bin[142] = 0xFF; bin[143] = 0x80; /* trailing dangling byte at [144] */ bin[144] = 0xAA; /* Minimal rcwi - no fields needed, just the meta vars. */ static const char rcwi[] = "%size=1024\n" "%pbiformat=2\n" "%littleendian=1\n"; rcw_ctx_t *ctx = rcw_ctx_new(); char *out = NULL; size_t out_len = 0; /* * Pre-fix: this would over-read 3 bytes past the buffer end. ASan * would trap; without ASan the call would return RCW_OK with * garbage trailing bytes from the heap. * Post-fix: stops at the last full word, returns OK cleanly. */ rcw_error_t err = rcw_decompile_buffer(ctx, rcwi, strlen(rcwi), bin, sizeof(bin), NULL, &out, &out_len); assert_int_equal(err, RCW_OK); assert_non_null(out); /* We should have disassembled the Stop command and stopped there. */ assert_non_null(strstr(out, "Stop command")); rcw_free(out); rcw_ctx_free(ctx); } /* * A3 - a buffer that starts with the PBL preamble but is shorter * than the minimum 140 bytes (preamble+cmd+RCW+checksum) used to * trigger an OOB read of up to 7 bytes past the buffer when * rcw_bits_extract() pulled 128 bytes from binary+8. * * Now: detected as no-preamble, falls into the short-buffer branch * and returns RCW_ERR_BAD_BINARY (no field extraction attempted). */ static void test_decompile_short_preamble_no_oob(void **state) { (void)state; /* 130 bytes: preamble + load-cmd + only 122 bytes of "RCW" -> too short. */ uint8_t bin[130] = {0}; bin[0] = 0x55; bin[1] = 0xAA; bin[2] = 0x55; bin[3] = 0xAA; bin[4] = 0x00; bin[5] = 0x00; bin[6] = 0x10; bin[7] = 0x80; static const char rcwi[] = "%size=1024\n" "%pbiformat=2\n" "%littleendian=1\n"; rcw_ctx_t *ctx = rcw_ctx_new(); char *out = NULL; size_t out_len = 0; rcw_error_t err = rcw_decompile_buffer(ctx, rcwi, strlen(rcwi), bin, sizeof(bin), NULL, &out, &out_len); /* * 130 bytes is below the 140-byte PBL minimum, so we ignore the * preamble bytes and go down the no-preamble branch. That branch * needs >= 128 bytes of RCW; 130 is fine, so decompile succeeds * but treats the buffer as raw RCW. */ assert_int_equal(err, RCW_OK); rcw_free(out); /* And a buffer truly shorter than RCW_SIZE_BYTES is rejected. */ out = NULL; err = rcw_decompile_buffer(ctx, rcwi, strlen(rcwi), bin, 64, /* well under 128 */ NULL, &out, &out_len); assert_int_equal(err, RCW_ERR_BAD_BINARY); rcw_ctx_free(ctx); } /* * PBI mnemonic seam: decompiler emitter vs rcw_pbi.c parser. * * test_pbi.c already covers ENCODING of every awrite width, and the * round-trip test above covers decompile+recompile but only for a * single plain "write". Nothing crossed the two, so a decompiler that * emitted a bare "awrite" for a B=4/B=5 command went unnoticed: the * source still listed every data word, but recompiling parsed it as * B=3 and silently dropped all but the first. * * These tests assert exact byte counts, not just equality, so a * truncation cannot hide behind a coincidentally-matching prefix. */ /* Minimal .rcwi shared by the seam tests below. */ static const char seam_rcwi[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n"; /* * Compile `pbi_body` (a .pbi block), decompile it, recompile the * result, and hand both binaries back to the caller. Any step failing * is a test failure. */ static void seam_roundtrip(const char *pbi_body, uint8_t **bin1, size_t *bin1_len, uint8_t **bin2, size_t *bin2_len, char **decompiled) { char src[4096]; int n = snprintf(src, sizeof(src), "%sSYS_PLL_RAT=7\n.pbi\n%s.end\n", seam_rcwi, pbi_body); assert_true(n > 0 && (size_t)n < sizeof(src)); rcw_ctx_t *c1 = rcw_ctx_new(); assert_non_null(c1); assert_int_equal(rcw_compile_buffer(c1, src, strlen(src), bin1, bin1_len), RCW_OK); rcw_ctx_t *c2 = rcw_ctx_new(); assert_non_null(c2); assert_int_equal(rcw_parse(c2, seam_rcwi, strlen(seam_rcwi)), RCW_OK); char *out = NULL; size_t out_len = 0; assert_int_equal(rcw_decompile(c2, *bin1, *bin1_len, &out, &out_len), RCW_OK); assert_non_null(out); size_t comb_len = strlen(seam_rcwi) + out_len; char *comb = malloc(comb_len + 1); assert_non_null(comb); memcpy(comb, seam_rcwi, strlen(seam_rcwi)); memcpy(comb + strlen(seam_rcwi), out, out_len); comb[comb_len] = '\0'; rcw_ctx_t *c3 = rcw_ctx_new(); assert_non_null(c3); assert_int_equal(rcw_compile_buffer(c3, comb, comb_len, bin2, bin2_len), RCW_OK); free(comb); if (decompiled) *decompiled = out; else rcw_free(out); rcw_ctx_free(c1); rcw_ctx_free(c2); rcw_ctx_free(c3); } /* * The decompiler must spell the width into the mnemonic, and must * still emit every data word. */ static void test_decompile_awrite_emits_width_suffix(void **state) { (void)state; uint8_t *b1 = NULL, *b2 = NULL; size_t l1 = 0, l2 = 0; char *out = NULL; seam_roundtrip("awrite 0x0400098,0x11111111\n" "awrite.b4 0x0100000,0x22222222,0x33333333\n" "awrite.b5 0x2508000,0x44444444,0x55555555," "0x66666666,0x77777777\n", &b1, &l1, &b2, &l2, &out); /* Mnemonics, each at the start of its own line. */ assert_non_null(strstr(out, "\nawrite 0x00400098,0x11111111\n")); assert_non_null(strstr(out, "\nawrite.b4 0x00100000,0x22222222,0x33333333\n")); assert_non_null(strstr(out, "\nawrite.b5 0x02508000,0x44444444,0x55555555," "0x66666666,0x77777777\n")); /* A bare "awrite " must not be used for the wide forms. */ assert_null(strstr(out, "\nawrite 0x00100000")); assert_null(strstr(out, "\nawrite 0x02508000")); rcw_free(out); rcw_free(b1); rcw_free(b2); } /* * Byte-exact regression for the 992-byte truncation. PBI payload: * awrite (B=3) 4 + 4 = 8 * awrite.b4 (B=4) 4 + 8 = 12 * awrite.b5 (B=5) 4 + 16 = 20 * --- 40 * plus 140 header (preamble + Load RCW + RCW + checksum) and an * 8-byte STOP terminator (%nocrc=1) = 188. */ static void test_decompile_roundtrip_awrite_widths(void **state) { (void)state; uint8_t *b1 = NULL, *b2 = NULL; size_t l1 = 0, l2 = 0; seam_roundtrip("awrite 0x0400098,0x11111111\n" "awrite.b4 0x0100000,0x22222222,0x33333333\n" "awrite.b5 0x2508000,0x44444444,0x55555555," "0x66666666,0x77777777\n", &b1, &l1, &b2, &l2, NULL); assert_int_equal(l1, 140 + 40 + 8); assert_int_equal(l2, l1); /* no silent shrink */ assert_memory_equal(b1, b2, l1); rcw_free(b1); rcw_free(b2); } /* * Every other mnemonic the decompiler can emit, in one image, so the * emitter/parser seam is covered as a whole rather than per-command. * * Sizes: loadacwindow 4, write 8, write.b1 8, wait 4, * blockcopy 16 = 40. * poll is excluded, see the dedicated test below. */ static void test_decompile_roundtrip_all_mnemonics(void **state) { (void)state; uint8_t *b1 = NULL, *b2 = NULL; size_t l1 = 0, l2 = 0; char *out = NULL; seam_roundtrip("loadacwindow 0x00000005\n" "write 0x01e00620,0x80000000\n" "write.b1 0x021c0000,0x00000049\n" "wait 0x00000800\n" "blockcopy 0x0f,0x00009000,0x1800d000,0x00020000\n", &b1, &l1, &b2, &l2, &out); assert_int_equal(l1, 140 + 40 + 8); assert_int_equal(l2, l1); assert_memory_equal(b1, b2, l1); assert_non_null(strstr(out, "\nloadacwindow 0x00000005\n")); assert_non_null(strstr(out, "\nwrite 0x01e00620,0x80000000\n")); assert_non_null(strstr(out, "\nwrite.b1 0x021c0000,0x00000049\n")); assert_non_null(strstr(out, "\nwait 0x00000800\n")); assert_non_null(strstr(out, "\nblockcopy 0x0f,0x00009000,0x1800d000,0x00020000\n")); rcw_free(out); rcw_free(b1); rcw_free(b2); } /* * poll round-trips now that rcw_pbi.c emits the four words of * LX2160ARM Section 8.3.11 Table 44 (word0 = HDR|CMD|0x0000, then * ADDR, MASK, CONDITION) and agrees with the decompiler, which * already followed the RM. * * Before that fix the encoder wrote three words with the address * ORed into word0: the decompiler then read one word too many and * printed MASK, CONDITION and the NEXT command's header as the three * arguments, desynchronising the rest of the stream. A wide address * also corrupted the command byte, turning a short poll into a long * one. Both are covered here and in test_pbi.c. * * Sizes: poll 16 + poll.long 16 = 32. */ static void test_decompile_roundtrip_poll(void **state) { (void)state; uint8_t *b1 = NULL, *b2 = NULL; size_t l1 = 0, l2 = 0; char *out = NULL; seam_roundtrip("poll 0x01e00620,0xffffffff,0x80000000\n" "poll.long 0x01e00624,0x0000000f,0x00000001\n", &b1, &l1, &b2, &l2, &out); assert_int_equal(l1, 140 + 32 + 8); assert_int_equal(l2, l1); assert_memory_equal(b1, b2, l1); /* Address preserved, and the two forms stay distinct. */ assert_non_null(strstr(out, "\npoll.short 0x01e00620,0xffffffff,0x80000000\n")); assert_non_null(strstr(out, "\npoll.long 0x01e00624,0x0000000f,0x00000001\n")); assert_null(strstr(out, "Unknown word")); rcw_free(out); rcw_free(b1); rcw_free(b2); } /* Flow control round-trips */ static void test_decompile_roundtrip_cond(void **state) { (void)state; uint8_t *b1 = NULL, *b2 = NULL; size_t l1 = 0, l2 = 0; char *out = NULL; seam_roundtrip("loadc 0x01e00100,0x000000ff\n" "jumpc 0x00000014,0x00000005\n" "jump 0x0000000c\n", &b1, &l1, &b2, &l2, &out); /* 12 (loadc) + 12 (jumpc) + 8 (jump) = 32 bytes of PBI. */ assert_int_equal(l1, 140 + 32 + 8); assert_int_equal(l2, l1); assert_memory_equal(b1, b2, l1); assert_non_null(strstr(out, "\nloadc 0x01e00100,0x000000ff\n")); assert_non_null(strstr(out, "\njumpc L0,0x00000005\n")); assert_non_null(strstr(out, "\njump 0x0000000c\n")); assert_non_null(strstr(out, "\nL0:\n/* Stop command */\n")); assert_null(strstr(out, "Unknown word")); assert_null(strstr(out, "not implemented")); rcw_free(out); rcw_free(b1); rcw_free(b2); } /* The auto-boot block of lx2160asi/bootlocptr_auto.rcw */ static void test_decompile_labels_as_rcwpy(void **state) { (void)state; uint8_t *b1 = NULL, *b2 = NULL; size_t l1 = 0, l2 = 0; char *out = NULL; seam_roundtrip("loadc 0x01e00000,(0xf<<23)\n" "jumpc sdhc1_boot,(0x8<<23)\n" "jump sdhc1_done\n" "sdhc1_boot:\n" "blockcopy 0x08,0x0000a000,0x1800d000,0x00020000\n" "write 0x01e00400,0x1800d000\n" "sdhc1_done:\n" "jumpc xspi_boot,(0xf<<23)\n" "jump xspi_done\n" "xspi_boot:\n" "blockcopy 0x0f,0x20009000,0x1800d000,0x00020000\n" "write 0x01e00400,0x1800d000\n" "xspi_done:\n", &b1, &l1, &b2, &l2, &out); /* 12 + (12 + 8 + 16 + 8) * 2 = 100 bytes of PBI */ assert_int_equal(l1, 140 + 100 + 8); assert_int_equal(l2, l1); assert_memory_equal(b1, b2, l1); static const char want[] = "\n.pbi\n" "loadc 0x01e00000,0x07800000\n" "jumpc L0,0x04000000\n" "jump L1\n" "L0:\n" "blockcopy 0x08,0x0000a000,0x1800d000,0x00020000\n" "write 0x01e00400,0x1800d000\n" "L1:\n" "jumpc L2,0x07800000\n" "jump L3\n" "L2:\n" "blockcopy 0x0f,0x20009000,0x1800d000,0x00020000\n" "write 0x01e00400,0x1800d000\n" "L3:\n" "/* Stop command */\n" ".end\n"; assert_non_null(strstr(out, want)); rcw_free(out); rcw_free(b1); rcw_free(b2); } static void test_decompile_roundtrip_density(void **state) { (void)state; uint8_t *b1 = NULL, *b2 = NULL; size_t l1 = 0, l2 = 0; char *out = NULL; seam_roundtrip("ccsrcopy.b4 0x18000000,0x01e00300,0x00000010\n" "ccsrset.b4 0x01e00400,0x0000000f\n" "ccsrclear.b4 0x01e00404,0x000000f0\n" "ccsrreplace.b4 0x01e00408,0x0000ff00,0x00005500\n", &b1, &l1, &b2, &l2, &out); /* Four 16-byte commands. */ assert_int_equal(l1, 140 + 64 + 8); assert_int_equal(l2, l1); assert_memory_equal(b1, b2, l1); assert_non_null(strstr(out, "\nccsrcopy.b4 0x18000000,0x01e00300,0x00000010\n")); assert_non_null(strstr(out, "\nccsrset.b4 0x01e00400,0x0000000f\n")); assert_non_null(strstr(out, "\nccsrclear.b4 0x01e00404,0x000000f0\n")); assert_non_null(strstr(out, "\nccsrreplace.b4 0x01e00408,0x0000ff00,0x00005500\n")); assert_null(strstr(out, "Unknown word")); assert_null(strstr(out, "not implemented")); rcw_free(out); rcw_free(b1); rcw_free(b2); } static void test_decompile_roundtrip_secure(void **state) { (void)state; uint8_t *b1 = NULL, *b2 = NULL; size_t l1 = 0, l2 = 0; char *out = NULL; seam_roundtrip("loadsecurityheader 0x55550000,0x55550001,0x55550002,0x55550003,0x55550004,0x55550005,0x55550006,0x55550007,0x55550008,0x55550009,0x5555000a,0x5555000b,0x5555000c,0x5555000d,0x5555000e,0x5555000f,0x55550010,0x55550011,0x55550012,0x55550013\n" "loadboot1csfptr 0x18000000\n", &b1, &l1, &b2, &l2, &out); /* 84 (security header) + 8 (csf pointer). */ assert_int_equal(l1, 140 + 92 + 8); assert_int_equal(l2, l1); assert_memory_equal(b1, b2, l1); assert_non_null(strstr(out, "\nloadsecurityheader 0x55550000,")); assert_non_null(strstr(out, "\nloadboot1csfptr 0x18000000\n")); assert_null(strstr(out, "Unknown word")); assert_null(strstr(out, "not implemented")); rcw_free(out); rcw_free(b1); rcw_free(b2); } /* * Every others: .b1 and .b2 (one data word, 1 and 2 bytes), * .b6 (8 words) and .b10 (128 words, the 512-byte maximum). * The printer must spell each one, and recompiling must give the same * bytes. */ static void test_decompile_roundtrip_awrite_narrow_and_wide(void **state) { (void)state; uint8_t *b1 = NULL, *b2 = NULL; size_t l1 = 0, l2 = 0; char *out = NULL; char body[2048]; int n = snprintf(body, sizeof(body), "awrite.b1 0x0000101,0x00000011\n" "awrite.b2 0x0000102,0x00002222\n" "awrite.b6 0x0000200,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8\n" "awrite.b10 0x0001000"); for (int k = 0; k < 128; k++) n += snprintf(body + n, sizeof(body) - (size_t)n, ",0x%08x", 0xa0000000u + (unsigned)k); n += snprintf(body + n, sizeof(body) - (size_t)n, "\n"); assert_true(n > 0 && (size_t)n < sizeof(body)); seam_roundtrip(body, &b1, &l1, &b2, &l2, &out); /* 8 + 8 + 36 + 516 bytes of PBI */ assert_int_equal(l1, 140 + 568 + 8); assert_int_equal(l2, l1); assert_memory_equal(b1, b2, l1); assert_non_null(strstr(out, "\nawrite.b1 0x00000101,0x00000011\n")); assert_non_null(strstr(out, "\nawrite.b2 0x00000102,0x00002222\n")); assert_non_null(strstr(out, "\nawrite.b6 0x00000200,0x00000001,")); assert_non_null(strstr(out, "\nawrite.b10 0x00001000,0xa0000000,")); assert_non_null(strstr(out, ",0xa000007f\n")); assert_null(strstr(out, "Unsupported")); assert_null(strstr(out, "Unknown word")); rcw_free(out); rcw_free(b1); rcw_free(b2); } static void test_decompile_reports_unknown_bits(void **state) { (void)state; static const char src[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "A[7:0]\n" "B[9:8]\n" "C[300]\n" "PBI_LENGTH[287:276]\n" "A=5\n" "B=3\n" "C=1\n"; /* the same image read with a .rcwi that does not know B and C */ static const char partial_rcwi[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "A[7:0]\n" "PBI_LENGTH[287:276]\n"; static const char full_rcwi[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "A[7:0]\n" "B[9:8]\n" "C[300]\n" "PBI_LENGTH[287:276]\n"; rcw_ctx_t *c1 = rcw_ctx_new(); uint8_t *bin = NULL; size_t len = 0; assert_int_equal(rcw_compile_buffer(c1, src, strlen(src), &bin, &len), RCW_OK); char *out = NULL; size_t out_len = 0; rcw_ctx_t *c2 = rcw_ctx_new(); assert_int_equal(rcw_decompile_buffer(c2, partial_rcwi, strlen(partial_rcwi), bin, len, NULL, &out, &out_len), RCW_OK); assert_int_equal(strncmp(out, "A=5\n", 4), 0); /* no header: the body starts here */ assert_non_null(strstr(out, "\n/* Unknown bits in positions: 8 9 300 */\n")); /* the comment does not get in the way of recompiling */ size_t comb_len = strlen(partial_rcwi) + out_len; char *comb = malloc(comb_len + 1); memcpy(comb, partial_rcwi, strlen(partial_rcwi)); memcpy(comb + strlen(partial_rcwi), out, out_len); comb[comb_len] = '\0'; uint8_t *bin2 = NULL; size_t len2 = 0; rcw_ctx_t *c3 = rcw_ctx_new(); assert_int_equal(rcw_compile_buffer(c3, comb, comb_len, &bin2, &len2), RCW_OK); assert_int_equal(len2, len); free(comb); rcw_free(bin2); rcw_free(out); /* with every field known there is nothing to report */ out = NULL; assert_int_equal(rcw_decompile_buffer(c2, full_rcwi, strlen(full_rcwi), bin, len, NULL, &out, &out_len), RCW_OK); assert_null(strstr(out, "Unknown bits")); assert_non_null(strstr(out, "\nB=3\n")); assert_non_null(strstr(out, "\nC=1\n")); rcw_free(out); rcw_free(bin); rcw_ctx_free(c1); rcw_ctx_free(c2); rcw_ctx_free(c3); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_decompile_reports_unknown_bits), cmocka_unit_test(test_decompile_roundtrip), cmocka_unit_test(test_decompile_awrite_emits_width_suffix), cmocka_unit_test(test_decompile_roundtrip_awrite_widths), cmocka_unit_test(test_decompile_roundtrip_awrite_narrow_and_wide), cmocka_unit_test(test_decompile_roundtrip_all_mnemonics), cmocka_unit_test(test_decompile_roundtrip_poll), cmocka_unit_test(test_decompile_roundtrip_cond), cmocka_unit_test(test_decompile_labels_as_rcwpy), cmocka_unit_test(test_decompile_roundtrip_density), cmocka_unit_test(test_decompile_roundtrip_secure), cmocka_unit_test(test_decompile_fields), cmocka_unit_test(test_decompile_buffer_with_header), cmocka_unit_test(test_decompile_buffer_no_header), cmocka_unit_test(test_decompile_buffer_roundtrip), cmocka_unit_test(test_preprocess_file_roundtrip), cmocka_unit_test(test_decompile_unaligned_pbi_no_oob), cmocka_unit_test(test_decompile_short_preamble_no_oob), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_density.c000066400000000000000000000147251526740601600221650ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * PBI density commands: CCSR Write from Address (0x02) and CCSR * Read-Modify-Write (0x42). * * Both exist to express in one command what would otherwise be a run of * blind writes * - 0x02 loads N consecutive CCSR registers from memory, * - 0x42 sets/clears/replaces bits under a mask without the caller having to * know the register's other contents. * * Table 37 CCSR Write from Address 16 B HDR=0x80 CMD=0x02 |wtype * SRC | DEST | COPY LENGTH * Table 43 CCSR Read-Modify-Write 16 B HDR=0x80 CMD=0x42 |ops|mtype * ADDR | MASK | DATA * */ #include #include #include #include #include #include #include #include #include "rcw_internal.h" #define HDR \ "%size=1024\n" \ "%pbiformat=2\n" \ "%classicbitnumbers=1\n" \ "%littleendian=1\n" \ "%nocrc=1\n" \ "SYS_PLL_RAT[6:2]\n" \ "PBI_LENGTH[287:276]\n" \ "SYS_PLL_RAT=7\n" static uint8_t *compile_ok(const char *src, size_t *len) { rcw_ctx_t *c = rcw_ctx_new(); assert_non_null(c); uint8_t *bin = NULL; assert_int_equal(rcw_compile_buffer(c, src, strlen(src), &bin, len), RCW_OK); assert_non_null(bin); rcw_ctx_free(c); return bin; } static void compile_fails(const char *src) { rcw_ctx_t *c = rcw_ctx_new(); assert_non_null(c); uint8_t *bin = NULL; size_t len = 0; assert_int_not_equal(rcw_compile_buffer(c, src, strlen(src), &bin, &len), RCW_OK); free(bin); rcw_ctx_free(c); } static uint32_t word_at(const uint8_t *b, size_t off) { return (uint32_t)b[off] | ((uint32_t)b[off + 1] << 8) | ((uint32_t)b[off + 2] << 16) | ((uint32_t)b[off + 3] << 24); } static long find_word(const uint8_t *b, size_t len, uint32_t want) { for (size_t i = 0; i + 4 <= len; i += 4) if (word_at(b, i) == want) return (long)i; return -1; } static void test_ccsrcopy_encoding(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok( HDR ".pbi\nccsrcopy.b4 0x18000000,0x01E00300,0x00000010\n.end\n", &len); /* CMD 0x02, word type 3 (32-bit destination registers). */ long at = find_word(bin, len, 0x80020003u); assert_true(at >= 0); assert_int_equal(word_at(bin, (size_t)at + 4), 0x18000000u); /* SRC */ assert_int_equal(word_at(bin, (size_t)at + 8), 0x01E00300u); /* DEST */ assert_int_equal(word_at(bin, (size_t)at + 12), 0x00000010u); /* WORDS */ free(bin); } /* Word type is 1/2/3 for 8/16/32-bit destination registers; 0 is invalid. */ static void test_ccsrcopy_word_types(void **state) { (void)state; size_t len = 0; uint8_t *b1 = compile_ok( HDR ".pbi\nccsrcopy.b1 0x1,0x01E00300,0x1\n.end\n", &len); assert_true(find_word(b1, len, 0x80020001u) >= 0); uint8_t *b2 = compile_ok( HDR ".pbi\nccsrcopy.b2 0x1,0x01E00300,0x1\n.end\n", &len); assert_true(find_word(b2, len, 0x80020002u) >= 0); free(b1); free(b2); } /* * ops occupies bits [3:2] and mask type bits [1:0], so the header low byte * is (ops << 2) | mtype: set=0x03, clear=0x07, replace=0x0B at .b4. */ static void test_rmw_ops_encoding(void **state) { (void)state; size_t len = 0; uint8_t *bs = compile_ok( HDR ".pbi\nccsrset.b4 0x01E00400,0x0000000F\n.end\n", &len); long at = find_word(bs, len, 0x80420003u); assert_true(at >= 0); assert_int_equal(word_at(bs, (size_t)at + 4), 0x01E00400u); assert_int_equal(word_at(bs, (size_t)at + 8), 0x0000000Fu); /* Data word is emitted but ignored by the SP for set/clear. */ assert_int_equal(word_at(bs, (size_t)at + 12), 0u); uint8_t *bc = compile_ok( HDR ".pbi\nccsrclear.b4 0x01E00404,0x000000F0\n.end\n", &len); assert_true(find_word(bc, len, 0x80420007u) >= 0); uint8_t *br = compile_ok( HDR ".pbi\nccsrreplace.b4 0x01E00408,0x0000FF00,0x00005500\n.end\n", &len); at = find_word(br, len, 0x8042000Bu); assert_true(at >= 0); assert_int_equal(word_at(br, (size_t)at + 12), 0x00005500u); /* DATA */ free(bs); free(bc); free(br); } static void test_rmw_mask_types(void **state) { (void)state; size_t len = 0; /* .b1 needs no alignment, .b2 needs 2-byte, .b4 needs 4-byte. */ uint8_t *b1 = compile_ok( HDR ".pbi\nccsrset.b1 0x01E00401,0x0F\n.end\n", &len); assert_true(find_word(b1, len, 0x80420001u) >= 0); uint8_t *b2 = compile_ok( HDR ".pbi\nccsrset.b2 0x01E00402,0x0FFF\n.end\n", &len); assert_true(find_word(b2, len, 0x80420002u) >= 0); free(b1); free(b2); } static void test_sizes(void **state) { (void)state; size_t base = 0, l_copy = 0, l_rmw = 0; uint8_t *b0 = compile_ok(HDR ".pbi\n.end\n", &base); uint8_t *b1 = compile_ok( HDR ".pbi\nccsrcopy.b4 0x1,0x01E00300,0x1\n.end\n", &l_copy); uint8_t *b2 = compile_ok( HDR ".pbi\nccsrset.b4 0x01E00400,0x1\n.end\n", &l_rmw); assert_int_equal(l_copy - base, 16); /* RM Table 37 */ assert_int_equal(l_rmw - base, 16); /* RM Table 43 */ free(b0); free(b1); free(b2); } /* Every error check the RM lists for Table 43. */ static void test_rmw_guards(void **state) { (void)state; /* mask type 0 is invalid -> a width suffix is mandatory */ compile_fails(HDR ".pbi\nccsrset 0x01E00400,0x1\n.end\n"); /* destination must be above 0x100_0000 */ compile_fails(HDR ".pbi\nccsrset.b4 0x00FF0000,0x1\n.end\n"); /* 4-byte mask needs a 4-byte aligned address */ compile_fails(HDR ".pbi\nccsrset.b4 0x01E00402,0x1\n.end\n"); /* 2-byte mask needs a 2-byte aligned address */ compile_fails(HDR ".pbi\nccsrset.b2 0x01E00401,0x1\n.end\n"); /* replace needs its data operand */ compile_fails(HDR ".pbi\nccsrreplace.b4 0x01E00400,0xF\n.end\n"); } static void test_ccsrcopy_guards(void **state) { (void)state; /* word type 0 is invalid -> suffix mandatory */ compile_fails(HDR ".pbi\nccsrcopy 0x1,0x01E00300,0x4\n.end\n"); compile_fails(HDR ".pbi\nccsrcopy.b4 0x1,0x01E00300\n.end\n"); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_ccsrcopy_encoding), cmocka_unit_test(test_ccsrcopy_word_types), cmocka_unit_test(test_rmw_ops_encoding), cmocka_unit_test(test_rmw_mask_types), cmocka_unit_test(test_sizes), cmocka_unit_test(test_rmw_guards), cmocka_unit_test(test_ccsrcopy_guards), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_parse.c000066400000000000000000000410621526740601600216120ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include "rcw_internal.h" /* * Redirect stderr to a tmpfile, run `body`, then read what was written * and copy it into `out` (NUL-terminated, truncated to out_size-1). * Restores stderr afterwards. */ #define CAPTURE_STDERR(out, out_size, body) \ do { \ fflush(stderr); \ int _saved = dup(fileno(stderr)); \ FILE *_tmp = tmpfile(); \ assert_non_null(_tmp); \ dup2(fileno(_tmp), fileno(stderr)); \ body; \ fflush(stderr); \ rewind(_tmp); \ size_t _n = fread((out), 1, (out_size) - 1, _tmp); \ (out)[_n] = '\0'; \ dup2(_saved, fileno(stderr)); \ close(_saved); \ fclose(_tmp); \ } while (0) static const char minimal_source[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "MEM_PLL_RAT[15:10]\n" "BOOT_LOC[264:260]\n" "PBI_LENGTH[287:276]\n" "SYS_PLL_RAT=14\n" "MEM_PLL_RAT=21\n" "BOOT_LOC=26\n"; /* Test that variables are parsed correctly */ static void test_parse_variables(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); rcw_error_t err = rcw_parse(ctx, minimal_source, strlen(minimal_source)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->vars.size, 1024); assert_int_equal(ctx->vars.pbiformat, 2); assert_true(ctx->vars.classicbitnumbers); assert_true(ctx->vars.littleendian); assert_true(ctx->vars.nocrc); rcw_ctx_free(ctx); } /* Test that symbol definitions are parsed correctly */ static void test_parse_symbols(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); rcw_error_t err = rcw_parse(ctx, minimal_source, strlen(minimal_source)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->symbols.count, 4); assert_string_equal(ctx->symbols.entries[0].name, "SYS_PLL_RAT"); assert_int_equal(ctx->symbols.entries[0].begin, 6); assert_int_equal(ctx->symbols.entries[0].end, 2); assert_string_equal(ctx->symbols.entries[1].name, "MEM_PLL_RAT"); assert_int_equal(ctx->symbols.entries[1].begin, 15); assert_int_equal(ctx->symbols.entries[1].end, 10); assert_string_equal(ctx->symbols.entries[2].name, "BOOT_LOC"); assert_int_equal(ctx->symbols.entries[2].begin, 264); assert_int_equal(ctx->symbols.entries[2].end, 260); rcw_ctx_free(ctx); } /* Test that assignments are parsed correctly */ static void test_parse_assignments(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); rcw_error_t err = rcw_parse(ctx, minimal_source, strlen(minimal_source)); assert_int_equal(err, RCW_OK); assert_true(ctx->symbols.entries[0].has_value); assert_int_equal(ctx->symbols.entries[0].value, 14); assert_true(ctx->symbols.entries[1].has_value); assert_int_equal(ctx->symbols.entries[1].value, 21); assert_true(ctx->symbols.entries[2].has_value); assert_int_equal(ctx->symbols.entries[2].value, 26); /* PBI_LENGTH not assigned */ assert_false(ctx->symbols.entries[3].has_value); rcw_ctx_free(ctx); } /* Test hex value parsing */ static void test_parse_hex_values(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "SYSCLK_FREQ[301:292]\n" "SYSCLK_FREQ=0x258\n"; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); rcw_error_t err = rcw_parse(ctx, src, strlen(src)); assert_int_equal(err, RCW_OK); assert_true(ctx->symbols.entries[0].has_value); assert_int_equal(ctx->symbols.entries[0].value, 0x258); rcw_ctx_free(ctx); } /* Test single-bit field definition: NAME[pos] */ static void test_parse_single_bit(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "SYS_PLL_SPD[128]\n" "SYS_PLL_SPD=1\n"; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); rcw_error_t err = rcw_parse(ctx, src, strlen(src)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->symbols.count, 1); assert_int_equal(ctx->symbols.entries[0].begin, 128); assert_int_equal(ctx->symbols.entries[0].end, 128); assert_true(ctx->symbols.entries[0].has_value); assert_int_equal(ctx->symbols.entries[0].value, 1); rcw_ctx_free(ctx); } /* Test PBI block parsing */ static void test_parse_pbi_block(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "%littleendian=1\n" "PBI_LENGTH[287:276]\n" ".pbi\n" "write 0x01e00200,0x12345678\n" ".end\n"; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); rcw_error_t err = rcw_parse(ctx, src, strlen(src)); assert_int_equal(err, RCW_OK); /* PBI should have 8 bytes (1 write = 2 words) */ assert_int_equal(ctx->pbi.len, 8); rcw_ctx_free(ctx); } /* Test blank lines and whitespace handling */ static void test_parse_whitespace(void **state) { (void)state; const char src[] = " %size=1024 \n" "\n" " %pbiformat=2\n" " SYS_PLL_RAT [ 6 : 2 ] \n" " SYS_PLL_RAT = 14 \n"; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); rcw_error_t err = rcw_parse(ctx, src, strlen(src)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->vars.size, 1024); assert_int_equal(ctx->symbols.count, 1); assert_true(ctx->symbols.entries[0].has_value); assert_int_equal(ctx->symbols.entries[0].value, 14); rcw_ctx_free(ctx); } /* * Test -w (warnings) for duplicate bitfield assignments. * Mirrors rcw.py's behavior at lines 515-516: * if options.warnings and (name in assignments): * print('Warning: Duplicate assignment for bitfield', name) * * Wording must match exactly (only the output stream differs: * rcw.py prints to stdout, libqoriq-rcw to stderr). */ static const char dup_assign_source[] = "%size=1024\n" "%pbiformat=2\n" "SYS_PLL_RAT[6:2]\n" "SYS_PLL_RAT=14\n" "SYS_PLL_RAT=10\n"; static void test_parse_dup_assign_warns_when_enabled(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); rcw_ctx_set_warnings(ctx, 1); char buf[256]; rcw_error_t err = RCW_OK; CAPTURE_STDERR(buf, sizeof(buf), { err = rcw_parse(ctx, dup_assign_source, strlen(dup_assign_source)); }); assert_int_equal(err, RCW_OK); assert_non_null(strstr(buf, "Warning: Duplicate assignment for bitfield SYS_PLL_RAT")); /* Last assignment wins */ assert_true(ctx->symbols.entries[0].has_value); assert_int_equal(ctx->symbols.entries[0].value, 10); rcw_ctx_free(ctx); } static void test_parse_dup_assign_silent_when_disabled(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); /* warnings disabled (default) */ char buf[256]; rcw_error_t err = RCW_OK; CAPTURE_STDERR(buf, sizeof(buf), { err = rcw_parse(ctx, dup_assign_source, strlen(dup_assign_source)); }); assert_int_equal(err, RCW_OK); assert_null(strstr(buf, "Duplicate assignment")); /* Last assignment still wins regardless of -w */ assert_true(ctx->symbols.entries[0].has_value); assert_int_equal(ctx->symbols.entries[0].value, 10); rcw_ctx_free(ctx); } /* * A1 - bitfield position must fit in the 1024-bit RCW. * * Without the bound check, FOO[2000:2000]=1 reaches set_bits() with * bytepos=250 and writes byte 250 of the 128-byte stack buffer in * rcw_bits_pack(). Confirmed under ASan to corrupt the saved return * address. Parser must drop the definition (no symbol added) and the * downstream assignment must fall through as "Unknown bitfield". */ static void test_parse_bitpos_oob_range(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "FOO[2000:2000]\n" "FOO=1\n"; rcw_ctx_t *ctx = rcw_ctx_new(); char buf[256]; rcw_error_t err = RCW_OK; CAPTURE_STDERR(buf, sizeof(buf), { err = rcw_parse(ctx, src, strlen(src)); }); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->symbols.count, 0); /* Assignment to the dropped symbol must report unknown */ assert_non_null(strstr(buf, "Unknown bitfield")); rcw_ctx_free(ctx); } /* A1 - upper edge: 1024 is one past the last legal position. */ static void test_parse_bitpos_just_over_max(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "FOO[1024]\n"; rcw_ctx_t *ctx = rcw_ctx_new(); rcw_error_t err = rcw_parse(ctx, src, strlen(src)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->symbols.count, 0); rcw_ctx_free(ctx); } /* A1 - exact maximum (1023) is accepted. */ static void test_parse_bitpos_max_accepted(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "TOPBIT[1023]\n" "TOPBIT=1\n"; rcw_ctx_t *ctx = rcw_ctx_new(); rcw_error_t err = rcw_parse(ctx, src, strlen(src)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->symbols.count, 1); assert_true(ctx->symbols.entries[0].has_value); rcw_ctx_free(ctx); } /* * A1 - negative literal. strtoul("-1") wraps to ULONG_MAX which casts * to int=-1. set_bits would then write buf[-1]. The parser must * reject the leading '-'. */ static void test_parse_bitpos_negative(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "FOO[-1:0]\n" "FOO=1\n"; rcw_ctx_t *ctx = rcw_ctx_new(); rcw_error_t err = rcw_parse(ctx, src, strlen(src)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->symbols.count, 0); rcw_ctx_free(ctx); } /* * B3 - assignment value with strtoull overflow. The value field is * uint64_t and must be rejected when the literal does not fit (or * has trailing garbage / leading minus). */ static void test_parse_value_overflow(void **state) { (void)state; /* ULLONG_MAX + 1 forces ERANGE on strtoull. */ const char src[] = "%size=1024\n" "%pbiformat=2\n" "FOO[7:0]\n" "FOO=99999999999999999999999\n"; rcw_ctx_t *ctx = rcw_ctx_new(); rcw_error_t err = rcw_parse(ctx, src, strlen(src)); assert_int_equal(err, RCW_OK); /* Symbol exists but assignment was rejected */ assert_int_equal(ctx->symbols.count, 1); assert_false(ctx->symbols.entries[0].has_value); rcw_ctx_free(ctx); } /* B3 - negative assignment must be rejected (bitfield is unsigned). */ static void test_parse_value_negative(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "FOO[7:0]\n" "FOO=-1\n"; rcw_ctx_t *ctx = rcw_ctx_new(); rcw_error_t err = rcw_parse(ctx, src, strlen(src)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->symbols.count, 1); assert_false(ctx->symbols.entries[0].has_value); rcw_ctx_free(ctx); } /* B3 - trailing garbage in value (e.g. "1abc") must be rejected. */ static void test_parse_value_trailing_garbage(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "FOO[7:0]\n" "FOO=42xyz\n"; rcw_ctx_t *ctx = rcw_ctx_new(); rcw_error_t err = rcw_parse(ctx, src, strlen(src)); assert_int_equal(err, RCW_OK); assert_false(ctx->symbols.entries[0].has_value); rcw_ctx_free(ctx); } /* * B5 - bitfield names that would otherwise truncate inside sym->name * (RCW_FIELD_NAME_MAX = 64) must be rejected. Otherwise two distinct * 70-char names sharing a 63-char prefix become aliases of each other, * defeating duplicate detection. */ static void test_parse_name_too_long(void **state) { (void)state; /* 80-character name */ char src[256]; snprintf(src, sizeof(src), "%%size=1024\n" "%%pbiformat=2\n" "%-80s[7:0]\n", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); rcw_ctx_t *ctx = rcw_ctx_new(); rcw_error_t err = rcw_parse(ctx, src, strlen(src)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->symbols.count, 0); rcw_ctx_free(ctx); } static void test_parse_python_literals(void **state) { (void)state; const char src[] = "%size=0x400\n" "%pbiformat=0b10\n" "A[7:0]\n" "B[15:8]\n" "C[23:16]\n" "D[31:24]\n" "E[39:32]\n" "F[0b10000000]\n" "A=0b1010\n" "B=0o17\n" "C=0X1f\n" "D=1_0\n" "E=00\n" "F=1\n"; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); assert_int_equal(rcw_parse(ctx, src, strlen(src)), RCW_OK); assert_int_equal(ctx->vars.size, 1024); assert_int_equal(ctx->vars.pbiformat, 2); assert_int_equal(ctx->symbols.count, 6); assert_int_equal(ctx->symbols.entries[0].value, 10); assert_int_equal(ctx->symbols.entries[1].value, 15); assert_int_equal(ctx->symbols.entries[2].value, 31); assert_int_equal(ctx->symbols.entries[3].value, 10); assert_true(ctx->symbols.entries[4].has_value); assert_int_equal(ctx->symbols.entries[4].value, 0); assert_int_equal(ctx->symbols.entries[5].begin, 128); assert_int_equal(ctx->symbols.entries[5].end, 128); assert_int_equal(ctx->symbols.entries[5].value, 1); rcw_ctx_free(ctx); } static void test_parse_value_leading_zero_rejected(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "FOO[7:0]\n" "FOO=010\n" "BAR[15:8]\n" "BAR=0b\n" "BAZ[23:16]\n" "BAZ=1__0\n"; rcw_ctx_t *ctx = rcw_ctx_new(); assert_int_equal(rcw_parse(ctx, src, strlen(src)), RCW_OK); assert_int_equal(ctx->symbols.count, 3); assert_false(ctx->symbols.entries[0].has_value); assert_false(ctx->symbols.entries[1].has_value); assert_false(ctx->symbols.entries[2].has_value); rcw_ctx_free(ctx); } /* A comment alone on a top-level line is skipped quietly, like in a block. */ static void test_parse_toplevel_comment_is_quiet(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "/* Unknown bits in positions: 8 9 */\n" "SYS_PLL_RAT[6:2]\n" " /* indented too */ \n" "SYS_PLL_RAT=14\n"; rcw_ctx_t *ctx = rcw_ctx_new(); char buf[256]; rcw_error_t err = RCW_OK; CAPTURE_STDERR(buf, sizeof(buf), { err = rcw_parse(ctx, src, strlen(src)); }); assert_int_equal(err, RCW_OK); assert_string_equal(buf, ""); assert_int_equal(ctx->symbols.count, 1); assert_int_equal(ctx->symbols.entries[0].value, 14); rcw_ctx_free(ctx); } static void test_sysaddr_refused_with_pbiformat_2(void **state) { (void)state; const char src[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%sysaddr=ee0100\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n" "SYS_PLL_RAT=14\n"; rcw_ctx_t *ctx = rcw_ctx_new(); uint8_t *bin = NULL; size_t len = 0; assert_int_equal(rcw_compile_buffer(ctx, src, strlen(src), &bin, &len), RCW_ERR_PARSE); assert_string_equal(rcw_ctx_last_error_detail(ctx), "PBL format does not use %sysaddr"); assert_null(bin); rcw_ctx_set_pbl(ctx, 0); assert_int_equal(rcw_compile_buffer(ctx, src, strlen(src), &bin, &len), RCW_OK); assert_int_equal(len, RCW_SIZE_BYTES); rcw_free(bin); rcw_ctx_free(ctx); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_sysaddr_refused_with_pbiformat_2), cmocka_unit_test(test_parse_toplevel_comment_is_quiet), cmocka_unit_test(test_parse_variables), cmocka_unit_test(test_parse_symbols), cmocka_unit_test(test_parse_assignments), cmocka_unit_test(test_parse_hex_values), cmocka_unit_test(test_parse_single_bit), cmocka_unit_test(test_parse_pbi_block), cmocka_unit_test(test_parse_whitespace), cmocka_unit_test(test_parse_dup_assign_warns_when_enabled), cmocka_unit_test(test_parse_dup_assign_silent_when_disabled), cmocka_unit_test(test_parse_bitpos_oob_range), cmocka_unit_test(test_parse_bitpos_just_over_max), cmocka_unit_test(test_parse_bitpos_max_accepted), cmocka_unit_test(test_parse_bitpos_negative), cmocka_unit_test(test_parse_value_overflow), cmocka_unit_test(test_parse_value_negative), cmocka_unit_test(test_parse_value_trailing_garbage), cmocka_unit_test(test_parse_name_too_long), cmocka_unit_test(test_parse_python_literals), cmocka_unit_test(test_parse_value_leading_zero_rejected), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_pbi.c000066400000000000000000000616411526740601600212570ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #include #include #include #include #include #include #include "rcw_internal.h" static rcw_ctx_t * setup_ctx(void) { rcw_ctx_t *ctx = rcw_ctx_new(); ctx->vars.pbiformat = 2; ctx->vars.littleendian = true; return ctx; } /* Helper: read a LE32 word from PBI buffer at offset */ static uint32_t pbi_word(const rcw_ctx_t *ctx, size_t offset) { const uint8_t *p = ctx->pbi.data + offset; return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24) ; } /* Test expression evaluator */ static void test_eval_simple(void **state) { (void)state; uint32_t result; assert_int_equal(rcw_eval_expr("0x1234", &result), RCW_OK); assert_int_equal(result, 0x1234); assert_int_equal(rcw_eval_expr("42", &result), RCW_OK); assert_int_equal(result, 42); assert_int_equal(rcw_eval_expr("0x100 + 0x200", &result), RCW_OK); assert_int_equal(result, 0x300); } static void test_eval_complex(void **state) { (void)state; uint32_t result; /* Typical macro expansion from serdes_28g.rcw */ assert_int_equal(rcw_eval_expr("(0x1ea0000 + (0x100 * (0)) + 0x0800)", &result), RCW_OK); assert_int_equal(result, 0x1ea0800); assert_int_equal(rcw_eval_expr("(0x1ea0000 + (0x40 * (2)) + 0x1200)", &result), RCW_OK); assert_int_equal(result, 0x1ea1280); /* Bitwise operations */ assert_int_equal(rcw_eval_expr("((0x10) << 3) & 0x000000F8", &result), RCW_OK); assert_int_equal(result, 0x80); } /* Operands are Python literals: 0b and 0o exist, C octal does not. */ static void test_eval_python_literals(void **state) { (void)state; uint32_t r; assert_int_equal(rcw_eval_expr("0b1010", &r), RCW_OK); assert_int_equal(r, 10); assert_int_equal(rcw_eval_expr("0o17", &r), RCW_OK); assert_int_equal(r, 15); assert_int_equal(rcw_eval_expr("0x10 + 0B1", &r), RCW_OK); assert_int_equal(r, 17); assert_int_equal(rcw_eval_expr("1_000", &r), RCW_OK); assert_int_equal(r, 1000); assert_int_equal(rcw_eval_expr("0", &r), RCW_OK); assert_int_equal(r, 0); assert_int_equal(rcw_eval_expr("010", &r), RCW_ERR_PBI_SYNTAX); } /* Test write command */ static void test_pbi_write(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "write 0x01e00200,0x12345678"); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 8); /* write: (3 << 28) | addr */ assert_int_equal(pbi_word(ctx, 0), (3u << 28) | 0x01e00200); assert_int_equal(pbi_word(ctx, 4), 0x12345678); rcw_ctx_free(ctx); } /* Test write.b1 command */ static void test_pbi_write_b1(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "write.b1 0x1e60060,0xff"); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 8); /* write.b1: (1 << 28) | addr */ assert_int_equal(pbi_word(ctx, 0), (1u << 28) | 0x01e60060); assert_int_equal(pbi_word(ctx, 4), 0xff); rcw_ctx_free(ctx); } /* Test awrite command */ static void test_pbi_awrite(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "awrite 0x400098,0x00000000"); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 8); /* awrite: 0x80000000 | (3 << 26) | addr */ assert_int_equal(pbi_word(ctx, 0), 0x80000000 | (3u << 26) | 0x400098); assert_int_equal(pbi_word(ctx, 4), 0x00000000); rcw_ctx_free(ctx); } /* Test awrite.b4 command */ static void test_pbi_awrite_b4(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "awrite.b4 0x100000,0xAAAAAAAA,0xBBBBBBBB"); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 12); assert_int_equal(pbi_word(ctx, 0), 0x80000000 | (4u << 26) | 0x100000); assert_int_equal(pbi_word(ctx, 4), 0xAAAAAAAA); assert_int_equal(pbi_word(ctx, 8), 0xBBBBBBBB); rcw_ctx_free(ctx); } /* Test awrite.b5 command */ static void test_pbi_awrite_b5(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "awrite.b5 0x2508000,0x11111111,0x22222222,0x33333333,0x44444444"); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 20); assert_int_equal(pbi_word(ctx, 0), 0x80000000 | (5u << 26) | 0x2508000); assert_int_equal(pbi_word(ctx, 4), 0x11111111); assert_int_equal(pbi_word(ctx, 8), 0x22222222); assert_int_equal(pbi_word(ctx, 12), 0x33333333); assert_int_equal(pbi_word(ctx, 16), 0x44444444); rcw_ctx_free(ctx); } /* Test wait command */ static void test_pbi_wait(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "wait 2500"); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 4); assert_int_equal(pbi_word(ctx, 0), 0x80820000 | 2500); rcw_ctx_free(ctx); } /* Test wait command at the field maximum: 0xFFFF must encode cleanly. */ static void test_pbi_wait_max(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "wait 0xFFFF"); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 4); assert_int_equal(pbi_word(ctx, 0), 0x80820000 | 0xFFFF); rcw_ctx_free(ctx); } /* * Test wait command overflow: anything past 0xFFFF would silently * corrupt the CMD field. The encoder must reject it instead. */ static void test_pbi_wait_overflow(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); /* 0x10000 is the first value that overflows. */ rcw_error_t err = rcw_pbi_encode_line(ctx, "wait 0x10000"); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, 0); /* The historical nbxv3 footgun: wait 1000000 (0xF4240). */ err = rcw_pbi_encode_line(ctx, "wait 1000000"); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, 0); rcw_ctx_free(ctx); } /* Test blockcopy command */ static void test_pbi_blockcopy(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "blockcopy 0x0,0x01e00210,0x18000000,0x4"); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 16); assert_int_equal(pbi_word(ctx, 0), 0x80000000); assert_int_equal(pbi_word(ctx, 4), 0x01e00210); assert_int_equal(pbi_word(ctx, 8), 0x18000000); assert_int_equal(pbi_word(ctx, 12), 0x4); rcw_ctx_free(ctx); } /* Test loadacwindow command */ static void test_pbi_loadacwindow(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "loadacwindow 0x1C0"); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 4); assert_int_equal(pbi_word(ctx, 0), 0x80120000 | 0x1C0); rcw_ctx_free(ctx); } /* Test poll command */ static void test_pbi_poll(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "poll 0x01e00404,0x00000001,0x00000001"); assert_int_equal(err, RCW_OK); /* * Four words per LX2160ARM Section 8.3.11 Table 44: the address has * a word of its own and word0's low half is reserved-zero. Note the * address here is exactly the case that used to break: ORed into * word0 it produced 0x81e00404, whose command byte 0x81 is * poll.long, a short poll silently became a long one. */ assert_int_equal(ctx->pbi.len, 16); assert_int_equal(pbi_word(ctx, 0), 0x80800000); assert_int_equal(pbi_word(ctx, 4), 0x01e00404); assert_int_equal(pbi_word(ctx, 8), 0x00000001); assert_int_equal(pbi_word(ctx, 12), 0x00000001); rcw_ctx_free(ctx); } /* poll.long differs only in the command byte; the layout is the same. */ static void test_pbi_poll_long(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "poll.long 0x01e00404,0x0000000f,0x00000002"); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 16); assert_int_equal(pbi_word(ctx, 0), 0x80810000); assert_int_equal(pbi_word(ctx, 4), 0x01e00404); assert_int_equal(pbi_word(ctx, 8), 0x0000000f); assert_int_equal(pbi_word(ctx, 12), 0x00000002); rcw_ctx_free(ctx); } /* * A full 28-bit address must survive intact -- it no longer shares a * word with the command byte. */ static void test_pbi_poll_wide_address(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "poll 0x0fffffff,0xffffffff,0xffffffff"); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 16); assert_int_equal(pbi_word(ctx, 0), 0x80800000); /* command intact */ assert_int_equal(pbi_word(ctx, 4), 0x0fffffff); /* address intact */ rcw_ctx_free(ctx); } /* * A4 - the parens-only "((((...))))" pattern would blow the C stack * via unbounded recursion in eval_primary. The evaluator must hit * the depth cap and return RCW_ERR_PBI_SYNTAX cleanly. */ static void test_eval_recursion_capped(void **state) { (void)state; /* 200 nested '(' - well past the 64-deep cap. */ char expr[512]; for (int i = 0; i < 200; i++) expr[i] = '('; expr[200] = '1'; for (int i = 201; i < 401; i++) expr[i] = ')'; expr[401] = '\0'; uint32_t r; rcw_error_t err = rcw_eval_expr(expr, &r); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); } /* * Shifts are Python's: "1 << 32" is 4294967296, which struct.pack('L') * refuses. The intermediate is wider * than the word, so a shift back brings it into range again. */ static void test_eval_shift_count_overflow(void **state) { (void)state; uint32_t r; assert_int_equal(rcw_eval_expr("1 << 32", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("(1 << 32) >> 1", &r), RCW_OK); assert_int_equal(r, 0x80000000u); assert_int_equal(rcw_eval_expr("0xFFFFFFFF >> 32", &r), RCW_OK); assert_int_equal(r, 0); assert_int_equal(rcw_eval_expr("1 << -1", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("1 << 63", &r), RCW_ERR_PBI_SYNTAX); /* Sanity: legal shifts still work */ assert_int_equal(rcw_eval_expr("1 << 31", &r), RCW_OK); assert_int_equal(r, 0x80000000u); assert_int_equal(rcw_eval_expr("0xFFFFFFFF >> 31", &r), RCW_OK); assert_int_equal(r, 1); } /* * The whole string must be one Python expression. * Text the grammar does not consume used to be * ignored here, so "0x5 junk" compiled as 5 and "0x10%3" as 0x10. */ static void test_eval_rejects_trailing_text(void **state) { (void)state; uint32_t r; assert_int_equal(rcw_eval_expr("0x5 junk", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("0x10 0x20", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("0x10 %", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("(1", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("1)", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("1 ? 2 : 3", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("1 == 1", &r), RCW_ERR_PBI_SYNTAX); } /* Python precedence and the operators eval() offers on integers. */ static void test_eval_python_operators(void **state) { (void)state; uint32_t r; assert_int_equal(rcw_eval_expr("7 % 3", &r), RCW_OK); assert_int_equal(r, 1); assert_int_equal(rcw_eval_expr("0x10 % 3", &r), RCW_OK); assert_int_equal(r, 1); /* floor division: -7 // 2 is -4 in Python, not -3 */ assert_int_equal(rcw_eval_expr("-7 // 2 + 10", &r), RCW_OK); assert_int_equal(r, 6); assert_int_equal(rcw_eval_expr("-7 % 2", &r), RCW_OK); assert_int_equal(r, 1); assert_int_equal(rcw_eval_expr("2 ** 10", &r), RCW_OK); assert_int_equal(r, 1024); assert_int_equal(rcw_eval_expr("2 ** 31", &r), RCW_OK); assert_int_equal(r, 0x80000000u); /* ** binds tighter than *, shifts bind looser than + */ assert_int_equal(rcw_eval_expr("2 * 3 ** 2", &r), RCW_OK); assert_int_equal(r, 18); assert_int_equal(rcw_eval_expr("4 - 2 ** 2", &r), RCW_OK); assert_int_equal(r, 0); assert_int_equal(rcw_eval_expr("1 + 2 << 3", &r), RCW_OK); assert_int_equal(r, 24); /* | ^ & from loosest to tightest */ assert_int_equal(rcw_eval_expr("1 | 2 ^ 3 & 4", &r), RCW_OK); assert_int_equal(r, 3); assert_int_equal(rcw_eval_expr("+5", &r), RCW_OK); assert_int_equal(r, 5); assert_int_equal(rcw_eval_expr("-1 + 2", &r), RCW_OK); assert_int_equal(r, 1); assert_int_equal(rcw_eval_expr("~0 & 0xff", &r), RCW_OK); assert_int_equal(r, 0xff); assert_int_equal(rcw_eval_expr("(0xf << 23)", &r), RCW_OK); assert_int_equal(r, 0x07800000u); } /* * struct.pack('L') takes 0..0xFFFFFFFF and nothing else, and Python has * no 32-bit wrap. */ static void test_eval_rejects_what_struct_pack_rejects(void **state) { (void)state; uint32_t r; assert_int_equal(rcw_eval_expr("0xffffffff + 1", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("2 ** 32", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("~0", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("-1", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("0x10 / 4", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("2 ** -1", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("1 % 0", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("1 // 0", &r), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_eval_expr("0xffffffff * 0xffffffff * 0xffffffff", &r), RCW_ERR_PBI_SYNTAX); /* a 64-bit detour is fine as long as the result fits */ assert_int_equal(rcw_eval_expr("0xffffffff + 1 - 1", &r), RCW_OK); assert_int_equal(r, 0xffffffffu); assert_int_equal(rcw_eval_expr("0xffffffff * 0x10 // 0x10", &r), RCW_OK); assert_int_equal(r, 0xffffffffu); } /* The encoder names the operand and the reason. */ static void test_pbi_operand_error_is_explained(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); char why[RCW_EVAL_WHY_MAX]; uint32_t r; assert_int_equal(rcw_pbi_encode_line(ctx, "write 0x1e00400,0x5 junk"), RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, 0); assert_non_null(strstr(rcw_ctx_last_error_detail(ctx), "'0x5 junk'")); assert_non_null(strstr(rcw_ctx_last_error_detail(ctx), "unexpected 'junk'")); assert_int_equal(rcw_eval_expr_why("0xffffffff + 1", &r, why, sizeof(why)), RCW_ERR_PBI_SYNTAX); assert_non_null(strstr(why, "4294967296 does not fit")); assert_int_equal(rcw_eval_expr_why("1 // 0", &r, why, sizeof(why)), RCW_ERR_PBI_SYNTAX); assert_string_equal(why, "division by zero"); rcw_ctx_free(ctx); } /* * B2 - integer literal that does not fit in uint32_t (e.g. * 0x100000000) must be rejected, not silently truncated. */ static void test_eval_numeric_literal_overflow(void **state) { (void)state; uint32_t r; rcw_error_t err = rcw_eval_expr("0x100000000", &r); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); /* UINT32_MAX still works */ assert_int_equal(rcw_eval_expr("0xFFFFFFFF", &r), RCW_OK); assert_int_equal(r, 0xFFFFFFFFu); } /* * B1 - addresses that exceed the per-command field width OR-overflow * into the command-type bits and silently corrupt the encoded * command. Each opcode must reject upfront. */ static void test_pbi_write_addr_overflow(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); /* CCSR address field is [27:0]; 0x10000000 sets bit 28 and * silently changes the byte-count field from B=3 to B=2 (RESERVED). */ rcw_error_t err = rcw_pbi_encode_line(ctx, "write 0x10000000,0xCAFE"); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, 0); /* And the upper-bound address is still accepted. */ err = rcw_pbi_encode_line(ctx, "write 0x0FFFFFFF,0xCAFE"); assert_int_equal(err, RCW_OK); rcw_ctx_free(ctx); } static void test_pbi_write_b1_addr_overflow(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_pbi_encode_line(ctx, "write.b1 0x10000000,0xff"); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, 0); rcw_ctx_free(ctx); } static void test_pbi_awrite_addr_overflow(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); /* AltConfig offset field is [25:0]; 0x4000000 corrupts B-field. */ rcw_error_t err = rcw_pbi_encode_line(ctx, "awrite 0x4000000,0"); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); err = rcw_pbi_encode_line(ctx, "awrite.b4 0x4000000,0,0"); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); err = rcw_pbi_encode_line(ctx, "awrite.b5 0x4000000,0,0,0,0"); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, 0); /* Upper-bound offsets still encode. */ err = rcw_pbi_encode_line(ctx, "awrite 0x3FFFFFC,0"); assert_int_equal(err, RCW_OK); err = rcw_pbi_encode_line(ctx, "awrite.b1 0x3FFFFFF,0"); assert_int_equal(err, RCW_OK); rcw_ctx_free(ctx); } static void test_pbi_loadacwindow_overflow(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); /* 14-bit field; 0x4000 sets bit 14 and corrupts the CMD byte. */ rcw_error_t err = rcw_pbi_encode_line(ctx, "loadacwindow 0x4000"); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, 0); err = rcw_pbi_encode_line(ctx, "loadacwindow 0x3FFF"); assert_int_equal(err, RCW_OK); rcw_ctx_free(ctx); } static void test_pbi_blockcopy_src_overflow(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); /* SRC interface byte is 8 bits; 0x100 spills into the CMD byte. */ rcw_error_t err = rcw_pbi_encode_line(ctx, "blockcopy 0x100,0x01e00210,0x18000000,0x4"); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, 0); rcw_ctx_free(ctx); } /* * B2 - extra parameters past PBI_MAX_PARAMS used to be silently * dropped; the encoder must now report it. */ static void test_pbi_too_many_params(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); /* awrite.b5 takes 5 params; supply 6 */ rcw_error_t err = rcw_pbi_encode_line(ctx, "awrite.b5 0x100,0x1,0x2,0x3,0x4,0x5"); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, 0); rcw_ctx_free(ctx); } /* * AltConfig Write widths: B in 1..10 selects N = 2^(B-1) bytes * in ceil(N/4) data words */ static void test_pbi_awrite_every_width(void **state) { (void)state; static const struct { unsigned b; int nwords; } widths[] = { { 1, 1 }, { 2, 1 }, { 3, 1 }, { 4, 2 }, { 5, 4 }, { 6, 8 }, { 7, 16 }, { 8, 32 }, { 9, 64 }, { 10, 128 }, }; char line[2048]; for (size_t t = 0; t < sizeof(widths) / sizeof(widths[0]); t++) { rcw_ctx_t *ctx = setup_ctx(); int n = snprintf(line, sizeof(line), "awrite.b%u 0x100", widths[t].b); for (int k = 0; k < widths[t].nwords; k++) n += snprintf(line + n, sizeof(line) - (size_t)n, ",0x%08x", 0x11110000u + (unsigned)k); assert_int_equal(rcw_pbi_encode_line(ctx, line), RCW_OK); assert_int_equal(ctx->pbi.len, 4u * (1u + (unsigned)widths[t].nwords)); assert_int_equal(pbi_word(ctx, 0), 0x80000000u | (widths[t].b << 26) | 0x100u); for (int k = 0; k < widths[t].nwords; k++) assert_int_equal(pbi_word(ctx, 4u * (1u + (unsigned)k)), 0x11110000u + (unsigned)k); rcw_ctx_free(ctx); } rcw_ctx_t *ctx = setup_ctx(); assert_int_equal(rcw_pbi_encode_line(ctx, "awrite.b1 0x100,0x5a"), RCW_OK); assert_int_equal(pbi_word(ctx, 0), 0x84000100u); assert_int_equal(pbi_word(ctx, 4), 0x0000005au); rcw_ctx_free(ctx); } /* The suffix is the B field: nothing outside .b1 to .b10 encodes. */ static void test_pbi_awrite_bad_width(void **state) { (void)state; static const char *lines[] = { "awrite.b0 0x100,0x1", "awrite.b11 0x100,0x1", "awrite.b01 0x100,0x1", "awrite.bx 0x100,0x1", "awrite.short 0x100,0x1", "awrite.b 0x100,0x1", }; for (size_t t = 0; t < sizeof(lines) / sizeof(lines[0]); t++) { rcw_ctx_t *ctx = setup_ctx(); assert_int_equal(rcw_pbi_encode_line(ctx, lines[t]), RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, 0); rcw_ctx_free(ctx); } } /* The operand count follows the width: one too few or too many is an error. */ static void test_pbi_awrite_operand_count(void **state) { (void)state; static const char *lines[] = { "awrite 0x100", "awrite 0x100,0x1,0x2", "awrite.b1 0x100,0x1,0x2", "awrite.b4 0x100,0x1", "awrite.b4 0x100,0x1,0x2,0x3", "awrite.b6 0x100,0x1,0x2,0x3,0x4,0x5,0x6,0x7", }; for (size_t t = 0; t < sizeof(lines) / sizeof(lines[0]); t++) { rcw_ctx_t *ctx = setup_ctx(); assert_int_equal(rcw_pbi_encode_line(ctx, lines[t]), RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, 0); rcw_ctx_free(ctx); } } /* For B above 2, ALTCFG_OFFSET must be word aligned. */ static void test_pbi_awrite_alignment(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); assert_int_equal(rcw_pbi_encode_line(ctx, "awrite.b1 0x101,0x5a"), RCW_OK); assert_int_equal(rcw_pbi_encode_line(ctx, "awrite.b2 0x102,0x5a5a"), RCW_OK); assert_int_equal(rcw_pbi_encode_line(ctx, "awrite 0x104,0x1"), RCW_OK); size_t len = ctx->pbi.len; assert_int_equal(rcw_pbi_encode_line(ctx, "awrite 0x102,0x1"), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_pbi_encode_line(ctx, "awrite.b4 0x101,0x1,0x2"), RCW_ERR_PBI_SYNTAX); assert_int_equal(rcw_pbi_encode_line(ctx, "awrite.b5 0x103,0x1,0x2,0x3,0x4"), RCW_ERR_PBI_SYNTAX); assert_int_equal(ctx->pbi.len, len); assert_non_null(strstr(rcw_ctx_last_error_detail(ctx), "word aligned")); rcw_ctx_free(ctx); } /* * flush is a legacy construct (pbiformat 0 and 1): an 8-byte CCSR write * into the PBL address space that forces a read-back of the previous * write, PBL_CMD_FLUSH | (pbladdr & RCW_PBLADDR_MASK) then a zero word. * Under pbiformat=2 that word has ACS=0 and B=0, illegal, * so the SP would halt on it: the encoder refuses it and names the doc's * idiom instead, a Poll with mask 0. */ static void test_pbi_flush_refused_under_pbiformat_2(void **state) { (void)state; static const char src[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "PBI_LENGTH[287:276]\n" "SYS_PLL_RAT=7\n" ".pbi\n" "write 0x01e00400,0x1\n" "flush\n" ".end\n"; rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); uint8_t *bin = NULL; size_t len = 0; assert_int_equal(rcw_compile_buffer(ctx, src, strlen(src), &bin, &len), RCW_ERR_PBI_SYNTAX); assert_null(bin); assert_non_null(strstr(rcw_ctx_last_error_detail(ctx), "poll ,0,0")); rcw_ctx_free(ctx); /* the RM's flush, a poll with mask 0, encodes as any poll */ ctx = setup_ctx(); assert_int_equal(rcw_pbi_encode_line(ctx, "poll 0x01e00400,0,0"), RCW_OK); assert_int_equal(ctx->pbi.len, 16); assert_int_equal(pbi_word(ctx, 0), 0x80800000u); assert_int_equal(pbi_word(ctx, 4), 0x01e00400u); assert_int_equal(pbi_word(ctx, 8), 0); assert_int_equal(pbi_word(ctx, 12), 0); rcw_ctx_free(ctx); } /* The legacy encoding stays for the legacy formats, pinned for their day. */ static void test_pbi_flush_legacy_encoding(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); ctx->vars.pbiformat = 1; ctx->vars.pbladdr = 0x610000; /* the LS1 family's PBL window */ assert_int_equal(rcw_pbi_encode_line(ctx, "flush"), RCW_OK); assert_int_equal(ctx->pbi.len, 8); assert_int_equal(pbi_word(ctx, 0), PBL_CMD_FLUSH | 0x610000u); assert_int_equal(pbi_word(ctx, 4), 0); rcw_ctx_free(ctx); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_pbi_flush_refused_under_pbiformat_2), cmocka_unit_test(test_pbi_flush_legacy_encoding), cmocka_unit_test(test_eval_simple), cmocka_unit_test(test_eval_complex), cmocka_unit_test(test_eval_python_literals), cmocka_unit_test(test_pbi_write), cmocka_unit_test(test_pbi_write_b1), cmocka_unit_test(test_pbi_awrite), cmocka_unit_test(test_pbi_awrite_b4), cmocka_unit_test(test_pbi_awrite_b5), cmocka_unit_test(test_pbi_wait), cmocka_unit_test(test_pbi_wait_max), cmocka_unit_test(test_pbi_wait_overflow), cmocka_unit_test(test_pbi_blockcopy), cmocka_unit_test(test_pbi_loadacwindow), cmocka_unit_test(test_pbi_poll), cmocka_unit_test(test_pbi_poll_long), cmocka_unit_test(test_pbi_poll_wide_address), cmocka_unit_test(test_eval_recursion_capped), cmocka_unit_test(test_eval_shift_count_overflow), cmocka_unit_test(test_eval_rejects_trailing_text), cmocka_unit_test(test_eval_python_operators), cmocka_unit_test(test_eval_rejects_what_struct_pack_rejects), cmocka_unit_test(test_pbi_operand_error_is_explained), cmocka_unit_test(test_eval_numeric_literal_overflow), cmocka_unit_test(test_pbi_write_addr_overflow), cmocka_unit_test(test_pbi_write_b1_addr_overflow), cmocka_unit_test(test_pbi_awrite_addr_overflow), cmocka_unit_test(test_pbi_loadacwindow_overflow), cmocka_unit_test(test_pbi_blockcopy_src_overflow), cmocka_unit_test(test_pbi_too_many_params), cmocka_unit_test(test_pbi_awrite_every_width), cmocka_unit_test(test_pbi_awrite_bad_width), cmocka_unit_test(test_pbi_awrite_operand_count), cmocka_unit_test(test_pbi_awrite_alignment), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_pbl.c000066400000000000000000000740651526740601600212660ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * PBL image envelope and PBI check */ #include #include #include #include #include #include #include #include #include "rcw_internal.h" #define HDR \ "%size=1024\n" \ "%pbiformat=2\n" \ "%classicbitnumbers=1\n" \ "%littleendian=1\n" \ "SYS_PLL_RAT[6:2]\n" \ "SB_EN[266]\n" \ "PBI_LENGTH[287:276]\n" \ "SYS_PLL_RAT=7\n" #define PBI \ ".pbi\n" \ "write 0x01e00620,0x80000000\n" \ "blockcopy 0x0f,0x20009000,0x1800d000,0x1000\n" \ "wait 0x64\n" \ ".end\n" #define OFF_WRITE 0 #define OFF_BLOCKCOPY 8 #define OFF_WAIT 24 #define OFF_STOP 28 #define PBI_BYTES 36 #define PBI_WORDS (PBI_BYTES / 4) #define RCW_WORD8_AT (RCW_PBL_RCW_OFFSET + 8 * PBI_WORD_BYTES) #define PBI_LENGTH_SHIFT 20 #define PBI_LENGTH_MASK (0xFFFu << PBI_LENGTH_SHIFT) #define SB_EN_BIT (1u << 10) #define LOAD_RCW_CMD_BYTE (PBI_WORD_BYTES + 2) static uint8_t *compile_ok(const char *src, size_t *len) { rcw_ctx_t *c = rcw_ctx_new(); assert_non_null(c); uint8_t *bin = NULL; assert_int_equal(rcw_compile_buffer(c, src, strlen(src), &bin, len), RCW_OK); assert_non_null(bin); rcw_ctx_free(c); return bin; } static uint32_t word_at(const uint8_t *b, size_t off) { return (uint32_t)b[off] | ((uint32_t)b[off + 1] << 8) | ((uint32_t)b[off + 2] << 16) | ((uint32_t)b[off + 3] << 24); } static void put_word(uint8_t *b, size_t off, uint32_t w) { b[off] = (uint8_t)w; b[off + 1] = (uint8_t)(w >> 8); b[off + 2] = (uint8_t)(w >> 16); b[off + 3] = (uint8_t)(w >> 24); } static void set_pbi_length(uint8_t *bin, uint32_t words) { put_word(bin, RCW_WORD8_AT, (word_at(bin, RCW_WORD8_AT) & ~PBI_LENGTH_MASK) | (words << PBI_LENGTH_SHIFT)); } /* The Load RCW checksum again, after a patch of the RCW. */ static void fix_checksum(uint8_t *bin) { uint32_t sum = 0; for (size_t off = 0; off < RCW_PBL_CHECKSUM_OFFSET; off += PBI_WORD_BYTES) sum += word_at(bin, off); put_word(bin, RCW_PBL_CHECKSUM_OFFSET, sum); } static rcw_pbi_check_t parse_check_ok(const uint8_t *bin, size_t len, rcw_pbl_t *pbl) { rcw_pbi_check_t chk; assert_int_equal(rcw_pbl_parse(bin, len, 1, pbl), RCW_OK); assert_int_equal(rcw_pbl_check(pbl, 1, NULL, NULL, &chk), RCW_OK); assert_int_equal(chk.len, PBI_WORD_BYTES * (size_t)pbl->pbi_length); assert_int_equal(chk.end, chk.len); assert_ptr_equal(pbl->pbi + chk.end, bin + len); return chk; } struct visit_log { size_t n; rcw_pbi_op_t op[8]; size_t off[8]; }; static void log_visit(const rcw_pbi_cmd_t *cmd, size_t off, void *arg) { struct visit_log *l = arg; assert_true(l->n < 8); l->op[l->n] = cmd->op; l->off[l->n] = off; l->n++; } static void test_parse_stop(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" "SB_EN=1\n" PBI, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk = parse_check_ok(bin, len, &pbl); assert_int_equal(pbl.load_rcw_cmd, PBL_CMD_LOAD_RCW); assert_ptr_equal(pbl.rcw, bin + RCW_PBL_RCW_OFFSET); assert_int_equal(pbl.checksum, pbl.checksum_computed); assert_int_equal(pbl.checksum, word_at(bin, RCW_PBL_CHECKSUM_OFFSET)); assert_ptr_equal(pbl.pbi, bin + RCW_PBL_PBI_OFFSET); assert_int_equal(pbl.pbi_avail, len - RCW_PBL_PBI_OFFSET); assert_int_equal(pbl.pbi_length, PBI_WORDS); assert_int_equal(pbl.sb_en, 1); assert_int_equal(word_at(bin, RCW_WORD8_AT) >> PBI_LENGTH_SHIFT, pbl.pbi_length); assert_int_equal(word_at(bin, RCW_WORD8_AT) & SB_EN_BIT, SB_EN_BIT); assert_int_equal(chk.stop_op, RCW_PBI_STOP); assert_int_equal(chk.cmd.op, RCW_PBI_STOP); assert_int_equal(chk.ncmd, 4); assert_int_equal(chk.crc, 0); assert_int_equal(chk.crc_computed, 0); free(bin); } static void test_parse_crc_stop(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR PBI, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk = parse_check_ok(bin, len, &pbl); assert_int_equal(pbl.sb_en, 0); assert_int_equal(word_at(bin, RCW_WORD8_AT) & SB_EN_BIT, 0); assert_int_equal(chk.stop_op, RCW_PBI_CRC_STOP); assert_int_equal(chk.cmd.op, RCW_PBI_CRC_STOP); assert_int_equal(chk.crc, word_at(bin, len - PBI_WORD_BYTES)); assert_int_equal(chk.crc_computed, chk.crc); assert_int_equal(rcw_pbi_crc(pbl.pbi, PBI_WORD_BYTES * ((size_t)pbl.pbi_length - 1)), chk.crc); assert_int_not_equal(rcw_pbi_crc(pbl.pbi, PBI_WORD_BYTES * ((size_t)pbl.pbi_length - 2)), chk.crc); free(bin); } static void test_parse_without_checksum(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%loadwochecksum=1\n" PBI, &len); rcw_pbl_t pbl; assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(pbl.load_rcw_cmd, PBL_CMD_LOAD_RCW_NOCS); assert_int_equal(pbl.checksum, 0); assert_int_not_equal(pbl.checksum_computed, 0); free(bin); } static void test_pbi_length_field(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "PBI_LENGTH=0xabc\n" PBI, &len); rcw_pbl_t pbl; assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(pbl.pbi_length, 0xabc); assert_int_equal(word_at(bin, RCW_WORD8_AT) >> PBI_LENGTH_SHIFT, 0xabc); free(bin); } static void test_checksum_mismatch(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR PBI, &len); rcw_pbl_t pbl; bin[RCW_PBL_RCW_OFFSET] ^= 0x01; assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_not_equal(pbl.checksum, pbl.checksum_computed); assert_int_equal(pbl.checksum_computed - pbl.checksum, (bin[RCW_PBL_RCW_OFFSET] & 1) ? 1 : (uint32_t)-1); free(bin); } static void test_errors(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR PBI, &len); rcw_pbl_t pbl; for (size_t cut = 0; cut < RCW_PBL_PBI_OFFSET; cut += 7) assert_int_equal(rcw_pbl_parse(bin, cut, 1, &pbl), RCW_ERR_TRUNCATED); assert_int_equal(rcw_pbl_parse(bin, RCW_PBL_PBI_OFFSET - 1, 1, &pbl), RCW_ERR_TRUNCATED); assert_int_equal(rcw_pbl_parse(bin, RCW_PBL_PBI_OFFSET, 1, &pbl), RCW_OK); assert_int_equal(pbl.pbi_avail, 0); assert_int_not_equal(rcw_pbl_parse(bin + RCW_PBL_RCW_OFFSET, RCW_SIZE_BYTES, 1, &pbl), RCW_OK); bin[0] ^= 0x01; assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_ERR_BAD_BINARY); bin[0] ^= 0x01; bin[LOAD_RCW_CMD_BYTE] = PBI_GENCMD_STOP; assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_ERR_BAD_BINARY); bin[LOAD_RCW_CMD_BYTE] = PBI_GENCMD_LOAD_RCW_CS; assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); free(bin); } static void test_crc_check_value(void **state) { (void)state; static const uint8_t check[] = "123456789"; assert_int_equal(rcw_pbi_crc(check, 9), 0xCBF43926); assert_int_equal(rcw_pbi_crc(check, 0), 0); } /* Every executed command is visited in order, at the offset its layout gives it. */ static void test_check_visit(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk; struct visit_log l = { 0 }; assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, log_visit, &l, &chk), RCW_OK); assert_int_equal(chk.ncmd, 4); assert_int_equal(l.n, 4); assert_int_equal(l.op[0], RCW_PBI_CCSR_WRITE); assert_int_equal(l.off[0], OFF_WRITE); assert_int_equal(l.op[1], RCW_PBI_BLOCKCOPY); assert_int_equal(l.off[1], OFF_BLOCKCOPY); assert_int_equal(l.op[2], RCW_PBI_WAIT); assert_int_equal(l.off[2], OFF_WAIT); assert_int_equal(l.op[3], RCW_PBI_STOP); assert_int_equal(l.off[3], OFF_STOP); assert_int_equal(chk.len, PBI_BYTES); assert_int_equal(chk.end, PBI_BYTES); free(bin); } static void test_check_no_pbi(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "PBI_LENGTH=0\n" PBI, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk; struct visit_log l = { 0 }; assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(pbl.pbi_length, 0); assert_int_equal(pbl.pbi_avail, PBI_BYTES); assert_int_equal(rcw_pbl_check(&pbl, 1, log_visit, &l, &chk), RCW_OK); assert_int_equal(chk.len, 0); assert_int_equal(chk.end, 0); assert_int_equal(chk.ncmd, 0); assert_int_equal(chk.stop_op, RCW_PBI_UNKNOWN); assert_int_equal(l.n, 0); free(bin); } static void test_check_crc_mismatch(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR PBI, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk; bin[RCW_PBL_PBI_OFFSET + OFF_WRITE + PBI_WORD_BYTES] ^= 0x01; /* the data word of the write */ assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_OK); assert_int_equal(chk.stop_op, RCW_PBI_CRC_STOP); assert_int_equal(chk.crc, word_at(bin, len - PBI_WORD_BYTES)); assert_int_not_equal(chk.crc, chk.crc_computed); bin[RCW_PBL_PBI_OFFSET + OFF_WRITE + PBI_WORD_BYTES] ^= 0x01; bin[len - 1] ^= 0x80; /* the CRC word itself */ assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_OK); assert_int_equal(chk.crc, word_at(bin, len - PBI_WORD_BYTES)); assert_int_equal(chk.crc_computed, rcw_pbi_crc(pbl.pbi, PBI_BYTES - PBI_WORD_BYTES)); assert_int_not_equal(chk.crc, chk.crc_computed); free(bin); } static void test_check_pbi_length(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk; set_pbi_length(bin, 1); assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_PBI_OVERRUN); assert_string_not_equal(rcw_strerror(RCW_ERR_PBI_OVERRUN), "Unknown error"); assert_string_not_equal(rcw_strerror(RCW_ERR_PBI_LENGTH), "Unknown error"); assert_int_equal(chk.len, PBI_WORD_BYTES); assert_int_equal(chk.end, OFF_WRITE); assert_int_equal(chk.ncmd, 0); assert_int_equal(chk.cmd.op, RCW_PBI_CCSR_WRITE); assert_int_equal(chk.cmd.size, 8); assert_int_equal(chk.stop_op, RCW_PBI_UNKNOWN); set_pbi_length(bin, 5); assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_PBI_OVERRUN); assert_int_equal(chk.end, OFF_BLOCKCOPY); assert_int_equal(chk.ncmd, 1); assert_int_equal(chk.cmd.op, RCW_PBI_BLOCKCOPY); set_pbi_length(bin, 7); assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_PBI_LENGTH); assert_int_equal(chk.end, OFF_STOP); assert_int_equal(chk.ncmd, 3); assert_int_equal(chk.cmd.op, RCW_PBI_WAIT); assert_int_equal(chk.stop_op, RCW_PBI_UNKNOWN); set_pbi_length(bin, PBI_WORDS); assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_OK); free(bin); } static void test_check_development_buffer(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &len); size_t img_len = len + PBI_WORD_BYTES + PBL_TERMINATOR_BYTES; /* a wait word, then the final terminator */ uint8_t *img = malloc(img_len); rcw_pbl_t pbl; rcw_pbi_check_t chk; struct visit_log l = { 0 }; assert_non_null(img); memcpy(img, bin, len); free(bin); put_word(img, len, PBL_CMD_WAIT | 1); /* wait 1: never executed */ put_word(img, len + PBI_WORD_BYTES, PBL_CMD_STOP); /* the final Stop */ put_word(img, len + 2 * PBI_WORD_BYTES, 0); set_pbi_length(img, PBI_WORDS + 1 + PBL_TERMINATOR_WORDS); assert_int_equal(rcw_pbl_parse(img, img_len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, log_visit, &l, &chk), RCW_OK); assert_int_equal(chk.len, img_len - RCW_PBL_PBI_OFFSET); assert_int_equal(chk.end, PBI_BYTES); assert_int_equal(chk.ncmd, 4); assert_int_equal(l.n, 4); assert_int_equal(l.op[3], RCW_PBI_STOP); assert_int_equal(chk.cmd.op, RCW_PBI_STOP); assert_int_equal(chk.stop_op, RCW_PBI_STOP); put_word(img, len + PBI_WORD_BYTES, PBL_CMD_CRC_STOP); put_word(img, len + 2 * PBI_WORD_BYTES, rcw_pbi_crc(img + RCW_PBL_PBI_OFFSET, PBI_BYTES + 2 * PBI_WORD_BYTES)); assert_int_equal(rcw_pbl_parse(img, img_len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_OK); assert_int_equal(chk.end, PBI_BYTES); assert_int_equal(chk.stop_op, RCW_PBI_CRC_STOP); assert_int_equal(chk.crc, chk.crc_computed); assert_int_equal(chk.crc, word_at(img, len + 2 * PBI_WORD_BYTES)); put_word(img, len + PBI_WORD_BYTES, PBL_CMD_WAIT | 2); put_word(img, len + 2 * PBI_WORD_BYTES, PBL_CMD_WAIT | 3); assert_int_equal(rcw_pbl_parse(img, img_len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_PBI_LENGTH); assert_int_equal(chk.end, PBI_BYTES); assert_int_equal(chk.cmd.op, RCW_PBI_STOP); assert_int_equal(chk.stop_op, RCW_PBI_UNKNOWN); free(img); } static void test_check_unknown_word(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk; struct visit_log l = { 0 }; put_word(bin, RCW_PBL_PBI_OFFSET + OFF_WAIT, PBL_CMD_GENERAL | (0x30u << PBI_CMD_SHIFT)); /* CMD 0x30: not in Tables */ assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, log_visit, &l, &chk), RCW_ERR_BAD_BINARY); assert_int_equal(chk.end, OFF_WAIT); assert_int_equal(chk.ncmd, 2); assert_int_equal(l.n, 2); assert_int_equal(chk.cmd.op, RCW_PBI_UNKNOWN); assert_int_equal(chk.cmd.word, 0x80300000); assert_int_equal(chk.stop_op, RCW_PBI_UNKNOWN); free(bin); } static void test_check_truncated(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk; assert_int_equal(rcw_pbl_parse(bin, len - 4, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_TRUNCATED); assert_int_equal(chk.len, PBI_BYTES); assert_int_equal(chk.end, OFF_STOP); assert_int_equal(chk.ncmd, 3); assert_int_equal(chk.cmd.op, RCW_PBI_STOP); assert_int_equal(chk.cmd.size, 8); assert_int_equal(chk.stop_op, RCW_PBI_UNKNOWN); /* Nothing after the RCW. */ assert_int_equal(rcw_pbl_parse(bin, RCW_PBL_PBI_OFFSET, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_TRUNCATED); assert_int_equal(chk.end, 0); assert_int_equal(chk.ncmd, 0); set_pbi_length(bin, PBI_WORDS + 2); assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_TRUNCATED); assert_int_equal(chk.len, PBI_BYTES + 2 * PBI_WORD_BYTES); assert_int_equal(chk.end, PBI_BYTES); assert_int_equal(chk.ncmd, 4); assert_int_equal(chk.cmd.op, RCW_PBI_STOP); assert_int_equal(chk.stop_op, RCW_PBI_UNKNOWN); free(bin); } /* rcw_pbl_from_rcw(): the RCW alone, as the RCWSR registers hold it. */ static void test_from_rcw(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" "SB_EN=1\n" PBI, &len); rcw_pbl_t pbl; rcw_pbl_from_rcw(bin + RCW_PBL_RCW_OFFSET, &pbl); assert_ptr_equal(pbl.rcw, bin + RCW_PBL_RCW_OFFSET); assert_int_equal(pbl.pbi_length, PBI_WORDS); assert_int_equal(pbl.sb_en, 1); assert_int_equal(pbl.load_rcw_cmd, 0); assert_int_equal(pbl.checksum, 0); assert_int_equal(pbl.checksum_computed, 0); assert_null(pbl.pbi); assert_int_equal(pbl.pbi_avail, 0); free(bin); } /* rcw_pbl_verify(): the three terminators and Load RCW forms that boot. */ static void test_verify_ok(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &len); rcw_pbl_report_t rep; struct visit_log l = { 0 }; assert_int_equal(rcw_pbl_verify(bin, len, 1, log_visit, &l, &rep), RCW_OK); assert_true(rep.ok); assert_int_equal(rep.envelope, RCW_OK); assert_int_equal(rep.word0, PBL_PREAMBLE); assert_int_equal(rep.word1, PBL_CMD_LOAD_RCW); assert_true(rep.preamble_ok); assert_true(rep.load_rcw_ok); assert_true(rep.has_checksum); assert_true(rep.checksum_ok); assert_int_equal(rep.pbi, RCW_OK); assert_int_equal(rep.chk.ncmd, 4); assert_int_equal(rep.chk.stop_op, RCW_PBI_STOP); assert_true(rep.terminated); assert_true(rep.crc_ok); assert_int_equal(rep.image_end, len); assert_int_equal(l.n, 4); assert_int_equal(l.op[3], RCW_PBI_STOP); free(bin); bin = compile_ok(HDR PBI, &len); assert_int_equal(rcw_pbl_verify(bin, len, 1, NULL, NULL, &rep), RCW_OK); assert_true(rep.ok); assert_int_equal(rep.chk.stop_op, RCW_PBI_CRC_STOP); assert_int_equal(rep.chk.crc, rep.chk.crc_computed); assert_true(rep.crc_ok); free(bin); bin = compile_ok(HDR "%nocrc=1\n" "%loadwochecksum=1\n" PBI, &len); assert_int_equal(rcw_pbl_verify(bin, len, 1, NULL, NULL, &rep), RCW_OK); assert_true(rep.ok); assert_int_equal(rep.word1, PBL_CMD_LOAD_RCW_NOCS); assert_true(rep.load_rcw_ok); assert_false(rep.has_checksum); assert_true(rep.checksum_ok); free(bin); } /* image_end stops at the terminator value: what follows is flash padding. */ static void test_verify_padding(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &len); uint8_t *img = malloc(len + 64); rcw_pbl_report_t rep; assert_non_null(img); memcpy(img, bin, len); memset(img + len, 0xFF, 64); free(bin); assert_int_equal(rcw_pbl_verify(img, len + 64, 1, NULL, NULL, &rep), RCW_OK); assert_true(rep.ok); assert_int_equal(rep.image_end, len); /* RCW[PBI_LENGTH] 0: the envelope alone. */ set_pbi_length(img, 0); fix_checksum(img); assert_int_equal(rcw_pbl_verify(img, len + 64, 1, NULL, NULL, &rep), RCW_OK); assert_true(rep.ok); assert_false(rep.terminated); assert_int_equal(rep.chk.len, 0); assert_int_equal(rep.image_end, RCW_PBL_PBI_OFFSET); free(img); } static void test_verify_checksum(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &len); rcw_pbl_report_t rep; bin[RCW_PBL_RCW_OFFSET] ^= 0x01; assert_int_equal(rcw_pbl_verify(bin, len, 1, NULL, NULL, &rep), RCW_ERR_CHECKSUM); assert_string_not_equal(rcw_strerror(RCW_ERR_CHECKSUM), "Unknown error"); assert_false(rep.ok); assert_true(rep.has_checksum); assert_false(rep.checksum_ok); assert_int_equal(rep.pbi, RCW_OK); assert_true(rep.crc_ok); assert_int_equal(rep.image_end, len); free(bin); } static void test_verify_crc(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR PBI, &len); rcw_pbl_report_t rep; bin[RCW_PBL_PBI_OFFSET + OFF_WRITE + PBI_WORD_BYTES] ^= 0x01; assert_int_equal(rcw_pbl_verify(bin, len, 1, NULL, NULL, &rep), RCW_ERR_CRC); assert_string_not_equal(rcw_strerror(RCW_ERR_CRC), "Unknown error"); assert_false(rep.ok); assert_true(rep.checksum_ok); assert_int_equal(rep.pbi, RCW_OK); assert_int_equal(rep.chk.stop_op, RCW_PBI_CRC_STOP); assert_false(rep.crc_ok); assert_true(rep.terminated); assert_int_equal(rep.image_end, len); free(bin); } static void test_verify_pbi_length(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &len); uint8_t *img = malloc(len + PBL_TERMINATOR_BYTES); rcw_pbl_report_t rep; /* Points into the wait: no terminator read, the whole buffer stays. */ set_pbi_length(bin, PBI_WORDS - 2); fix_checksum(bin); assert_int_equal(rcw_pbl_verify(bin, len, 1, NULL, NULL, &rep), RCW_ERR_PBI_LENGTH); assert_false(rep.ok); assert_true(rep.checksum_ok); assert_int_equal(rep.pbi, RCW_ERR_PBI_LENGTH); assert_int_equal(rep.chk.ncmd, 3); assert_int_equal(rep.chk.cmd.op, RCW_PBI_WAIT); assert_false(rep.terminated); assert_int_equal(rep.image_end, len); /* Points past the Stop, at two words of padding: the image ends at the Stop. */ assert_non_null(img); memcpy(img, bin, len); memset(img + len, 0xFF, PBL_TERMINATOR_BYTES); set_pbi_length(img, PBI_WORDS + PBL_TERMINATOR_WORDS); fix_checksum(img); assert_int_equal(rcw_pbl_verify(img, len + PBL_TERMINATOR_BYTES, 1, NULL, NULL, &rep), RCW_ERR_PBI_LENGTH); assert_int_equal(rep.chk.ncmd, 4); assert_int_equal(rep.chk.cmd.op, RCW_PBI_STOP); assert_true(rep.terminated); assert_int_equal(rep.image_end, len); free(img); free(bin); } static void test_verify_truncated(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &len); rcw_pbl_report_t rep; /* Cut inside the Stop: the decode names it, the walk did not read it. */ assert_int_equal(rcw_pbl_verify(bin, len - 4, 1, NULL, NULL, &rep), RCW_ERR_TRUNCATED); assert_false(rep.ok); assert_int_equal(rep.pbi, RCW_ERR_TRUNCATED); assert_int_equal(rep.chk.ncmd, 3); assert_int_equal(rep.chk.cmd.op, RCW_PBI_STOP); assert_false(rep.terminated); assert_int_equal(rep.image_end, len - 4); /* A Stop read whole before a RCW[PBI_LENGTH] the buffer cannot hold. */ set_pbi_length(bin, PBI_WORDS + 2); fix_checksum(bin); assert_int_equal(rcw_pbl_verify(bin, len, 1, NULL, NULL, &rep), RCW_ERR_TRUNCATED); assert_int_equal(rep.chk.ncmd, 4); assert_true(rep.terminated); assert_int_equal(rep.image_end, len); free(bin); } static void test_verify_not_pbl(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &len); rcw_pbl_report_t rep; assert_int_equal(rcw_pbl_verify(bin, RCW_PBL_PBI_OFFSET - 1, 1, NULL, NULL, &rep), RCW_ERR_TRUNCATED); assert_int_equal(rep.envelope, RCW_ERR_TRUNCATED); assert_int_equal(rep.pbi, RCW_ERR_TRUNCATED); assert_true(rep.preamble_ok); assert_true(rep.load_rcw_ok); assert_false(rep.ok); assert_int_equal(rep.image_end, RCW_PBL_PBI_OFFSET - 1); assert_int_equal(rcw_pbl_verify(bin, 4, 1, NULL, NULL, &rep), RCW_ERR_TRUNCATED); assert_int_equal(rep.word0, 0); assert_false(rep.preamble_ok); bin[0] ^= 0x01; assert_int_equal(rcw_pbl_verify(bin, len, 1, NULL, NULL, &rep), RCW_ERR_BAD_BINARY); assert_int_equal(rep.envelope, RCW_ERR_BAD_BINARY); assert_false(rep.preamble_ok); assert_true(rep.load_rcw_ok); assert_false(rep.has_checksum); assert_false(rep.ok); assert_int_equal(rep.image_end, len); bin[0] ^= 0x01; bin[LOAD_RCW_CMD_BYTE] = PBI_GENCMD_STOP; assert_int_equal(rcw_pbl_verify(bin, len, 1, NULL, NULL, &rep), RCW_ERR_BAD_BINARY); assert_true(rep.preamble_ok); assert_false(rep.load_rcw_ok); assert_int_equal(rep.word1, PBL_CMD_STOP); free(bin); } #define CMD(cmd) (PBL_CMD_GENERAL | ((uint32_t)(cmd) << PBI_CMD_SHIFT)) static uint8_t *image_of(const uint32_t *words, size_t n, size_t *len) { size_t blen = 0; uint8_t *bin = compile_ok(HDR "%nocrc=1\n" PBI, &blen); uint8_t *img = malloc(RCW_PBL_PBI_OFFSET + PBI_WORD_BYTES * n); assert_non_null(img); memcpy(img, bin, RCW_PBL_PBI_OFFSET); free(bin); for (size_t i = 0; i < n; i++) put_word(img, RCW_PBL_PBI_OFFSET + PBI_WORD_BYTES * i, words[i]); set_pbi_length(img, (uint32_t)n); fix_checksum(img); *len = RCW_PBL_PBI_OFFSET + PBI_WORD_BYTES * n; return img; } static void test_check_jump(void **state) { (void)state; uint32_t words[] = { 0x31e00620, 0x00000001, /* write @0 */ CMD(PBI_GENCMD_JUMP), 24, /* jump 24 @8, lands at 32 */ PBL_CMD_BLOCKCOPY | 0x0f, 0x20009000, 0x1800d000, 0x1000, /* skipped @16 */ PBL_CMD_WAIT | 0x64, /* wait @32 */ PBL_CMD_STOP, 0, /* stop @36 */ }; size_t len = 0; uint8_t *img = image_of(words, 11, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk; struct visit_log l = { 0 }; assert_int_equal(rcw_pbl_parse(img, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, log_visit, &l, &chk), RCW_OK); assert_int_equal(chk.ncmd, 4); assert_int_equal(l.n, 4); assert_int_equal(l.op[0], RCW_PBI_CCSR_WRITE); assert_int_equal(l.op[1], RCW_PBI_JUMP); assert_int_equal(l.off[1], 8); assert_int_equal(l.op[2], RCW_PBI_WAIT); assert_int_equal(l.off[2], 32); assert_int_equal(l.op[3], RCW_PBI_STOP); assert_int_equal(chk.end, 44); assert_int_equal(chk.stop_op, RCW_PBI_STOP); put_word(img, RCW_PBL_PBI_OFFSET + 12, 28); assert_int_equal(rcw_pbl_parse(img, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_OK); assert_int_equal(chk.ncmd, 3); assert_int_equal(chk.end, 44); put_word(img, RCW_PBL_PBI_OFFSET + 12, 20); assert_int_equal(rcw_pbl_parse(img, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_BAD_BINARY); assert_int_equal(chk.ncmd, 2); assert_int_equal(chk.end, 28); assert_int_equal(chk.cmd.op, RCW_PBI_UNKNOWN); assert_int_equal(chk.cmd.word, 0x1000); free(img); } static void test_check_jump_offset(void **state) { (void)state; uint32_t words[] = { 0x31e00620, 0x00000001, CMD(PBI_GENCMD_JUMP), 24, PBL_CMD_BLOCKCOPY | 0x0f, 0x20009000, 0x1800d000, 0x1000, PBL_CMD_WAIT | 0x64, PBL_CMD_STOP, 0, }; static const uint32_t bad[] = { 0, 4, 8, 10, 22, 36, 40, 0x1000, 0xfffffffc }; size_t len = 0; uint8_t *img = image_of(words, 11, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk; assert_string_not_equal(rcw_strerror(RCW_ERR_PBI_JUMP), "Unknown error"); for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) { put_word(img, RCW_PBL_PBI_OFFSET + 12, bad[i]); assert_int_equal(rcw_pbl_parse(img, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_PBI_JUMP); assert_int_equal(chk.ncmd, 2); assert_int_equal(chk.end, 8); assert_int_equal(chk.cmd.op, RCW_PBI_JUMP); assert_int_equal(chk.cmd.args[0], bad[i]); assert_int_equal(chk.stop_op, RCW_PBI_UNKNOWN); } free(img); } static void test_check_conditional(void **state) { (void)state; uint32_t words[] = { CMD(PBI_GENCMD_LOADCONDITION), 0x01e60130, 0xff, /* loadc @0 */ CMD(PBI_GENCMD_JUMP_COND), 20, 0x1, /* jumpc @12, lands at 32 */ 0x31e00620, 0x00000001, /* write @24 */ PBL_CMD_STOP, 0, /* stop @32 */ }; size_t len = 0; uint8_t *img = image_of(words, 10, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk; rcw_pbl_report_t rep; struct visit_log l = { 0 }; assert_string_not_equal(rcw_strerror(RCW_ERR_PBI_CONDITIONAL), "Unknown error"); assert_int_equal(rcw_pbl_parse(img, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, log_visit, &l, &chk), RCW_ERR_PBI_CONDITIONAL); assert_int_equal(chk.ncmd, 2); assert_int_equal(l.n, 2); assert_int_equal(l.op[1], RCW_PBI_JUMP_COND); assert_int_equal(chk.end, 12); assert_int_equal(chk.cmd.op, RCW_PBI_JUMP_COND); assert_int_equal(chk.stop_op, RCW_PBI_STOP); assert_int_equal(rcw_pbl_verify(img, len, 1, NULL, NULL, &rep), RCW_ERR_PBI_CONDITIONAL); assert_false(rep.ok); assert_true(rep.checksum_ok); assert_true(rep.crc_ok); assert_false(rep.terminated); assert_int_equal(rep.pbi, RCW_ERR_PBI_CONDITIONAL); assert_int_equal(rep.image_end, len); put_word(img, RCW_PBL_PBI_OFFSET + 32, PBL_CMD_CRC_STOP); put_word(img, RCW_PBL_PBI_OFFSET + 36, rcw_pbi_crc(img + RCW_PBL_PBI_OFFSET, 36)); assert_int_equal(rcw_pbl_verify(img, len, 1, NULL, NULL, &rep), RCW_ERR_PBI_CONDITIONAL); assert_true(rep.crc_ok); assert_int_equal(rep.chk.stop_op, RCW_PBI_CRC_STOP); img[len - 1] ^= 0x80; assert_int_equal(rcw_pbl_verify(img, len, 1, NULL, NULL, &rep), RCW_ERR_CRC); assert_false(rep.crc_ok); assert_int_equal(rep.pbi, RCW_ERR_PBI_CONDITIONAL); put_word(img, RCW_PBL_PBI_OFFSET + 32, PBL_CMD_WAIT | 1); assert_int_equal(rcw_pbl_parse(img, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_PBI_LENGTH); assert_int_equal(chk.end, 12); free(img); } static void test_check_no_condition(void **state) { (void)state; uint32_t words[] = { CMD(PBI_GENCMD_JUMP_COND), 20, 0x1, /* jumpc @0, lands at 20 */ 0x31e00620, 0x00000001, /* write @12 */ PBL_CMD_STOP, 0, /* stop @20 */ }; size_t len = 0; uint8_t *img = image_of(words, 7, &len); rcw_pbl_t pbl; rcw_pbi_check_t chk; assert_string_not_equal(rcw_strerror(RCW_ERR_PBI_NO_CONDITION), "Unknown error"); assert_int_equal(rcw_pbl_parse(img, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_PBI_NO_CONDITION); assert_int_equal(chk.ncmd, 1); assert_int_equal(chk.end, 0); assert_int_equal(chk.cmd.op, RCW_PBI_JUMP_COND); put_word(img, RCW_PBL_PBI_OFFSET + 4, 6); assert_int_equal(rcw_pbl_parse(img, len, 1, &pbl), RCW_OK); assert_int_equal(rcw_pbl_check(&pbl, 1, NULL, NULL, &chk), RCW_ERR_PBI_JUMP); free(img); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_parse_stop), cmocka_unit_test(test_parse_crc_stop), cmocka_unit_test(test_parse_without_checksum), cmocka_unit_test(test_pbi_length_field), cmocka_unit_test(test_checksum_mismatch), cmocka_unit_test(test_errors), cmocka_unit_test(test_crc_check_value), cmocka_unit_test(test_check_visit), cmocka_unit_test(test_check_no_pbi), cmocka_unit_test(test_check_crc_mismatch), cmocka_unit_test(test_check_pbi_length), cmocka_unit_test(test_check_development_buffer), cmocka_unit_test(test_check_unknown_word), cmocka_unit_test(test_check_truncated), cmocka_unit_test(test_check_jump), cmocka_unit_test(test_check_jump_offset), cmocka_unit_test(test_check_conditional), cmocka_unit_test(test_check_no_condition), cmocka_unit_test(test_from_rcw), cmocka_unit_test(test_verify_ok), cmocka_unit_test(test_verify_padding), cmocka_unit_test(test_verify_checksum), cmocka_unit_test(test_verify_crc), cmocka_unit_test(test_verify_pbi_length), cmocka_unit_test(test_verify_truncated), cmocka_unit_test(test_verify_not_pbl), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_preprocess.c000066400000000000000000000064331526740601600226700ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * rcw_ctx_add_define(): macros handed to mcpp * * libmcpp keeps static state between mcpp_lib_main() calls, so one * preprocessor run per test binary: a single source exercises a valued * define, a bare one and an absent one at once. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "rcw_internal.h" static void test_defines_reach_mcpp(void **state) { (void)state; static const char src[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "SYS_PLL_RAT[6:2]\n" "MEM_PLL_RAT[15:10]\n" "PBI_LENGTH[287:276]\n" "#if SRDS_PRTCL_S1_VAL == 19\n" "SYS_PLL_RAT=19\n" "#else\n" "SYS_PLL_RAT=1\n" "#endif\n" "#ifdef RCW_ERRATA_NO_TRIM\n" "MEM_PLL_RAT=RCW_ERRATA_NO_TRIM\n" "#endif\n" "#ifdef NOT_DEFINED_ANYWHERE\n" "MEM_PLL_RAT=63\n" "#endif\n"; char tmpl[] = "/tmp/test-define-XXXXXX"; int fd = mkstemp(tmpl); assert_true(fd >= 0); assert_int_equal(write(fd, src, sizeof(src) - 1), (ssize_t)(sizeof(src) - 1)); close(fd); rcw_ctx_t *ctx = rcw_ctx_new(); assert_non_null(ctx); assert_int_equal(rcw_ctx_add_define(ctx, "SRDS_PRTCL_S1_VAL", "19"), RCW_OK); assert_int_equal(rcw_ctx_add_define(ctx, "RCW_ERRATA_NO_TRIM", NULL), RCW_OK); assert_int_equal(rcw_ctx_add_define(ctx, "", "1"), RCW_ERR_PARSE); assert_string_equal(ctx->defines[0], "-DSRDS_PRTCL_S1_VAL=19"); assert_string_equal(ctx->defines[1], "-DRCW_ERRATA_NO_TRIM"); char *pp = NULL; size_t pp_len = 0; assert_int_equal(rcw_preprocess_file(ctx, tmpl, &pp, &pp_len), RCW_OK); unlink(tmpl); assert_non_null(pp); char *bare = malloc(pp_len + 1); assert_non_null(bare); size_t n = 0; for (size_t i = 0; i < pp_len; i++) if (pp[i] != ' ' && pp[i] != '\t') bare[n++] = pp[i]; bare[n] = '\0'; assert_non_null(strstr(bare, "\nSYS_PLL_RAT=19\n")); assert_null(strstr(bare, "\nSYS_PLL_RAT=1\n")); assert_non_null(strstr(bare, "\nMEM_PLL_RAT=1\n")); assert_null(strstr(bare, "MEM_PLL_RAT=63")); assert_null(strstr(bare, "#if")); free(bare); uint8_t *bin = NULL; size_t bin_len = 0; assert_int_equal(rcw_compile_buffer(ctx, pp, pp_len, &bin, &bin_len), RCW_OK); assert_int_equal(rcw_bits_extract(bin + RCW_PBL_RCW_OFFSET, 6, 2, true), 19); assert_int_equal(rcw_bits_extract(bin + RCW_PBL_RCW_OFFSET, 15, 10, true), 1); rcw_free(bin); rcw_free(pp); rcw_ctx_free(ctx); } static void test_define_table_is_bounded(void **state) { (void)state; rcw_ctx_t *ctx = rcw_ctx_new(); char name[16]; for (int i = 0; i < RCW_MAX_DEFINES; i++) { snprintf(name, sizeof(name), "D%d", i); assert_int_equal(rcw_ctx_add_define(ctx, name, NULL), RCW_OK); } assert_int_equal(rcw_ctx_add_define(ctx, "ONE_TOO_MANY", NULL), RCW_ERR_NOMEM); assert_int_equal(ctx->define_count, RCW_MAX_DEFINES); rcw_ctx_free(ctx); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_defines_reach_mcpp), cmocka_unit_test(test_define_table_is_bounded), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_secure.c000066400000000000000000000205031526740601600217630ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * PBI secure-boot commands: Load Security Header (0x20) and Load Boot 1 * CSF Header Pointer (0x22). * * Table 41 Load Security Header 84 B HDR=0x80 CMD=0x20 * | CSF_WORD_0 .. CSF_WORD_19 * Table 42 Load Boot 1 CSF Hdr Ptr 8 B HDR=0x80 CMD=0x22 | CSF_PTR * * NEITHER HAS EVER RUN ON THIS BOARD. nbxv3 boots non-secure -- ITS is * unburned and DBG_LVL is 000 -- so unlike every other PBI command there is * no bench evidence behind these, only the RM tables. Treat a first secure * boot as the real validation; these tests pin the encoding so it cannot * drift in the meantime. * * The RM specifies exactly two checks between them (4-byte alignment on * CSF_PTR, and on the Load Security Header address) and defines nothing * about the CSF word contents -- that layout lives in the QorIQ Trust * Architecture UG, which is not in doc/. So the 20 words are carried * verbatim and deliberately NOT interpreted; a structural check invented * here would risk rejecting valid CST output. */ #include #include #include #include #include #include #include #include #include #include "rcw_internal.h" #define HDR \ "%size=1024\n" \ "%pbiformat=2\n" \ "%classicbitnumbers=1\n" \ "%littleendian=1\n" \ "%nocrc=1\n" \ "SYS_PLL_RAT[6:2]\n" \ "PBI_LENGTH[287:276]\n" \ "SYS_PLL_RAT=7\n" static uint8_t *compile_ok(const char *src, size_t *len) { rcw_ctx_t *c = rcw_ctx_new(); assert_non_null(c); uint8_t *bin = NULL; assert_int_equal(rcw_compile_buffer(c, src, strlen(src), &bin, len), RCW_OK); assert_non_null(bin); rcw_ctx_free(c); return bin; } static void compile_fails(const char *src) { rcw_ctx_t *c = rcw_ctx_new(); assert_non_null(c); uint8_t *bin = NULL; size_t len = 0; assert_int_not_equal(rcw_compile_buffer(c, src, strlen(src), &bin, &len), RCW_OK); free(bin); rcw_ctx_free(c); } static uint32_t word_at(const uint8_t *b, size_t off) { return (uint32_t)b[off] | ((uint32_t)b[off + 1] << 8) | ((uint32_t)b[off + 2] << 16) | ((uint32_t)b[off + 3] << 24); } static long find_word(const uint8_t *b, size_t len, uint32_t want) { for (size_t i = 0; i + 4 <= len; i += 4) if (word_at(b, i) == want) return (long)i; return -1; } /* Build "loadsecurityheader 0x...,0x..." with 20 predictable words. */ static void inline_src(char *buf, size_t sz, uint32_t base) { /* HDR holds %size=, %pbiformat= ... -- must NOT be a format string. */ size_t n = (size_t)snprintf(buf, sz, "%s", HDR ".pbi\nloadsecurityheader "); for (unsigned i = 0; i < PBI_CSF_HEADER_WORDS; i++) n += (size_t)snprintf(buf + n, sz - n, "%s0x%08x", i ? "," : "", base + i); snprintf(buf + n, sz - n, "\n.end\n"); } static void test_loadsecurityheader_inline(void **state) { (void)state; char src[2048]; inline_src(src, sizeof(src), 0x11110000u); size_t len = 0; uint8_t *bin = compile_ok(src, &len); long at = find_word(bin, len, 0x80200000u); /* RM Table 41 header */ assert_true(at >= 0); for (unsigned i = 0; i < PBI_CSF_HEADER_WORDS; i++) assert_int_equal(word_at(bin, (size_t)at + 4 + 4 * i), 0x11110000u + i); free(bin); } static void test_loadboot1csfptr_encoding(void **state) { (void)state; size_t len = 0; uint8_t *bin = compile_ok( HDR ".pbi\nloadboot1csfptr 0x18000000\n.end\n", &len); long at = find_word(bin, len, 0x80220000u); /* RM Table 42 header */ assert_true(at >= 0); assert_int_equal(word_at(bin, (size_t)at + 4), 0x18000000u); free(bin); } /* 84 B and 8 B exactly -- a wrong length desynchronises the SP's walk. */ static void test_sizes(void **state) { (void)state; char src[2048]; inline_src(src, sizeof(src), 0x1u); size_t base = 0, l_hdr = 0, l_ptr = 0; uint8_t *b0 = compile_ok(HDR ".pbi\n.end\n", &base); uint8_t *b1 = compile_ok(src, &l_hdr); uint8_t *b2 = compile_ok( HDR ".pbi\nloadboot1csfptr 0x18000000\n.end\n", &l_ptr); assert_int_equal(l_hdr - base, 84); /* RM Table 41 */ assert_int_equal(l_ptr - base, 8); /* RM Table 42 */ free(b0); free(b1); free(b2); } /* RM Table 42's only stated check. */ static void test_csf_ptr_alignment(void **state) { (void)state; compile_fails(HDR ".pbi\nloadboot1csfptr 0x18000001\n.end\n"); compile_fails(HDR ".pbi\nloadboot1csfptr 0x18000002\n.end\n"); compile_fails(HDR ".pbi\nloadboot1csfptr\n.end\n"); } /* Exactly 20 words, or a quoted path -- nothing else. */ static void test_word_count_enforced(void **state) { (void)state; compile_fails(HDR ".pbi\nloadsecurityheader 0x1,0x2\n.end\n"); compile_fails(HDR ".pbi\nloadsecurityheader\n.end\n"); } /* * File form. Written as hex TEXT rather than raw binary so a CSF header * stays diffable in git -- it authenticates the boot chain, so being able * to review a change to it matters. Tokens may be separated by whitespace * or commas, "0x" is optional, and # / // comments are allowed. */ static void write_file(const char *path, const char *body) { FILE *f = fopen(path, "w"); assert_non_null(f); fputs(body, f); fclose(f); } static void test_loadsecurityheader_from_file(void **state) { (void)state; const char *path = "test_csf_hdr.txt"; char body[1024]; size_t n = (size_t)snprintf(body, sizeof(body), "# CSF header, hex text\n"); for (unsigned i = 0; i < PBI_CSF_HEADER_WORDS; i++) n += (size_t)snprintf(body + n, sizeof(body) - n, "0x%08x%s", 0x33330000u + i, ((i % 4) == 3) ? " // row\n" : ", "); write_file(path, body); size_t len = 0; uint8_t *bin = compile_ok( HDR ".pbi\nloadsecurityheader \"test_csf_hdr.txt\"\n.end\n", &len); long at = find_word(bin, len, 0x80200000u); assert_true(at >= 0); for (unsigned i = 0; i < PBI_CSF_HEADER_WORDS; i++) assert_int_equal(word_at(bin, (size_t)at + 4 + 4 * i), 0x33330000u + i); free(bin); unlink(path); } /* The file form must be strict in both directions and about content. */ static void test_file_errors(void **state) { (void)state; char body[1024]; size_t n; compile_fails(HDR ".pbi\nloadsecurityheader \"no_such_csf.txt\"\n.end\n"); n = 0; for (unsigned i = 0; i < PBI_CSF_HEADER_WORDS - 1; i++) /* 19 */ n += (size_t)snprintf(body + n, sizeof(body) - n, "0x%x ", i); write_file("test_csf_short.txt", body); compile_fails(HDR ".pbi\nloadsecurityheader \"test_csf_short.txt\"\n.end\n"); unlink("test_csf_short.txt"); n = 0; for (unsigned i = 0; i < PBI_CSF_HEADER_WORDS + 1; i++) /* 21 */ n += (size_t)snprintf(body + n, sizeof(body) - n, "0x%x ", i); write_file("test_csf_long.txt", body); compile_fails(HDR ".pbi\nloadsecurityheader \"test_csf_long.txt\"\n.end\n"); unlink("test_csf_long.txt"); write_file("test_csf_bad.txt", "0x1 0x2 zz 0x4\n"); compile_fails(HDR ".pbi\nloadsecurityheader \"test_csf_bad.txt\"\n.end\n"); unlink("test_csf_bad.txt"); } /* Both forms must produce identical bytes for identical words. */ static void test_file_and_inline_agree(void **state) { (void)state; char body[1024], src[2048]; size_t n = 0; for (unsigned i = 0; i < PBI_CSF_HEADER_WORDS; i++) n += (size_t)snprintf(body + n, sizeof(body) - n, "0x%08x\n", 0x44440000u + i); write_file("test_csf_same.txt", body); inline_src(src, sizeof(src), 0x44440000u); size_t l1 = 0, l2 = 0; uint8_t *b1 = compile_ok(src, &l1); uint8_t *b2 = compile_ok( HDR ".pbi\nloadsecurityheader \"test_csf_same.txt\"\n.end\n", &l2); assert_int_equal(l1, l2); assert_memory_equal(b1, b2, l1); free(b1); free(b2); unlink("test_csf_same.txt"); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_loadsecurityheader_inline), cmocka_unit_test(test_loadboot1csfptr_encoding), cmocka_unit_test(test_sizes), cmocka_unit_test(test_csf_ptr_alignment), cmocka_unit_test(test_word_count_enforced), cmocka_unit_test(test_loadsecurityheader_from_file), cmocka_unit_test(test_file_errors), cmocka_unit_test(test_file_and_inline_agree), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_source.c000066400000000000000000000131461526740601600220020ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Each source line is compiled, decoded one command after the other, the * jumps not followed so that every line is handled. */ #include #include #include #include #include #include #include #include #include "rcw_internal.h" #define HDR \ "%size=1024\n" \ "%pbiformat=2\n" \ "%classicbitnumbers=1\n" \ "%littleendian=1\n" \ "SYS_PLL_RAT[6:2]\n" \ "SB_EN[266]\n" \ "PBI_LENGTH[287:276]\n" \ "SYS_PLL_RAT=7\n" #define MAX_LINES 16 struct lines { size_t n; char text[MAX_LINES][RCW_PBI_SOURCE_MAX]; }; static void collect(const rcw_pbi_cmd_t *cmd, struct lines *l) { assert_true(l->n < MAX_LINES); int n = rcw_pbi_to_source(cmd, l->text[l->n], sizeof(l->text[l->n])); assert_true(n > 0 && (size_t)n < sizeof(l->text[l->n])); assert_int_equal((size_t)n, strlen(l->text[l->n])); l->n++; } /* Compile pbi (the lines between .pbi and .end) and print every command. */ static void run(const char *pbi, int crc, struct lines *l) { size_t srclen = strlen(HDR) + strlen(pbi) + 32; char *src = malloc(srclen); rcw_ctx_t *c = rcw_ctx_new(); uint8_t *bin = NULL; size_t len = 0; rcw_pbl_t pbl; rcw_pbi_cmd_t cmd; size_t off = 0; assert_non_null(src); assert_non_null(c); snprintf(src, srclen, "%s%s.pbi\n%s.end\n", HDR, crc ? "" : "%nocrc=1\n", pbi); assert_int_equal(rcw_compile_buffer(c, src, strlen(src), &bin, &len), RCW_OK); memset(l, 0, sizeof(*l)); assert_int_equal(rcw_pbl_parse(bin, len, 1, &pbl), RCW_OK); while (off < PBI_WORD_BYTES * (size_t)pbl.pbi_length) { assert_int_equal(rcw_pbi_decode(pbl.pbi + off, pbl.pbi_avail - off, 1, &cmd), RCW_OK); collect(&cmd, l); off += cmd.size; } assert_int_equal(off, PBI_WORD_BYTES * (size_t)pbl.pbi_length); free(bin); free(src); rcw_ctx_free(c); } static void test_general_commands(void **state) { (void)state; struct lines l; run("write 0x01e00620,0x80000000\n" "write.b1 0x01e00621,0x5a\n" "blockcopy 0x0f,0x20009000,0x1800d000,0x1000\n" "wait 0x64\n" "poll 0x01e00100,0xff,0x1\n" "poll.long 0x01e00100,0xff,0x1\n" "loadacwindow 0x100\n", 0, &l); assert_int_equal(l.n, 8); assert_string_equal(l.text[0], "write 0x01e00620,0x80000000"); assert_string_equal(l.text[1], "write.b1 0x01e00621,0x0000005a"); assert_string_equal(l.text[2], "blockcopy 0x0f,0x20009000,0x1800d000,0x00001000"); assert_string_equal(l.text[3], "wait 0x00000064"); assert_string_equal(l.text[4], "poll.short 0x01e00100,0x000000ff,0x00000001"); assert_string_equal(l.text[5], "poll.long 0x01e00100,0x000000ff,0x00000001"); assert_string_equal(l.text[6], "loadacwindow 0x00000100"); assert_string_equal(l.text[7], "/* Stop command */"); } static void test_awrite_and_flow(void **state) { (void)state; struct lines l; run("awrite 0x100,0x1\n" "awrite.b4 0x100000,0xaaaaaaaa,0xbbbbbbbb\n" "jump skip\n" "wait 1\n" "skip:\n" "loadc 0x01e00000,0x07800000\n" "jumpc end,0x04000000\n" "wait 2\n" "end:\n" "wait 3\n", 0, &l); assert_int_equal(l.n, 9); assert_string_equal(l.text[0], "awrite 0x00000100,0x00000001"); assert_string_equal(l.text[1], "awrite.b4 0x00100000,0xaaaaaaaa,0xbbbbbbbb"); /* The jump is 8 bytes, the wait after it 4: skip is 12 bytes on. */ assert_string_equal(l.text[2], "jump 0x0000000c"); assert_string_equal(l.text[3], "wait 0x00000001"); assert_string_equal(l.text[4], "loadc 0x01e00000,0x07800000"); /* jumpc is 12 bytes, the wait 4: end is 16 bytes on. */ assert_string_equal(l.text[5], "jumpc 0x00000010,0x04000000"); assert_string_equal(l.text[6], "wait 0x00000002"); assert_string_equal(l.text[7], "wait 0x00000003"); assert_string_equal(l.text[8], "/* Stop command */"); } static void test_density_and_secure(void **state) { (void)state; struct lines l; run("ccsrcopy.b4 0x18000000,0x01E00300,0x00000010\n" "ccsrset.b4 0x01E00400,0x0000000F\n" "ccsrclear.b1 0x01E00401,0x0F\n" "ccsrreplace.b4 0x01E00408,0x0000FF00,0x00005500\n" "loadboot1csfptr 0x20000000\n", 1, &l); assert_int_equal(l.n, 6); assert_string_equal(l.text[0], "ccsrcopy.b4 0x18000000,0x01e00300,0x00000010"); assert_string_equal(l.text[1], "ccsrset.b4 0x01e00400,0x0000000f"); assert_string_equal(l.text[2], "ccsrclear.b1 0x01e00401,0x0000000f"); assert_string_equal(l.text[3], "ccsrreplace.b4 0x01e00408,0x0000ff00,0x00005500"); assert_string_equal(l.text[4], "loadboot1csfptr 0x20000000"); assert_memory_equal(l.text[5], "/* CRC and Stop command (CRC 0x", 31); } static void test_truncation(void **state) { (void)state; rcw_pbi_cmd_t cmd; static const uint8_t write[8] = { 0x20, 0x06, 0xe0, 0x31, 0x00, 0x00, 0x00, 0x80 }; /* write 0x01e00620,0x80000000 */ char buf[12]; assert_int_equal(rcw_pbi_decode(write, sizeof(write), 1, &cmd), RCW_OK); assert_int_equal(cmd.op, RCW_PBI_CCSR_WRITE); assert_int_equal(rcw_pbi_to_source(&cmd, NULL, 0), (int)strlen("write 0x01e00620,0x80000000")); assert_int_equal(rcw_pbi_to_source(&cmd, buf, sizeof(buf)), (int)strlen("write 0x01e00620,0x80000000")); assert_string_equal(buf, "write 0x01e"); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_general_commands), cmocka_unit_test(test_awrite_and_flow), cmocka_unit_test(test_density_and_secure), cmocka_unit_test(test_truncation), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-libqoriq-rcw-175bf56/tests/test_uboot.c000066400000000000000000000263721526740601600216370ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Tests for the .uboot subsection encoder (rcw_uboot.c). * * The .uboot input is an xxd hex dump (8 16-bit words per line, 16 bytes * per line). Grouping rules (mirroring rcw.py build_pbi_uboot): * - The last 4 lines are emitted as 2 pairs (32 bytes each), prefixed * by header word 0xC1F80000 + (line2_addr - 0x10). * - Every preceding 4-line group is emitted as a quad (64 bytes), * prefixed by header word 0x81F80000 + (line4_addr - 0x30). * All output is big-endian regardless of %littleendian. */ #include #include #include #include #include #include #include "rcw_internal.h" static rcw_ctx_t * setup_ctx(void) { rcw_ctx_t *ctx = rcw_ctx_new(); ctx->vars.pbiformat = 2; ctx->vars.littleendian = true; /* irrelevant for .uboot - output is BE */ return ctx; } /* Read a 32-bit big-endian word from the PBI buffer. */ static uint32_t pbi_be32(const rcw_ctx_t *ctx, size_t offset) { const uint8_t *p = ctx->pbi.data + offset; return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3] ; } /* Read a 16-bit big-endian word from the PBI buffer. */ static uint16_t pbi_be16(const rcw_ctx_t *ctx, size_t offset) { const uint8_t *p = ctx->pbi.data + offset; return (uint16_t)(((uint16_t)p[0] << 8) | (uint16_t)p[1]); } /* * Test 1: a 4-line input. All 4 lines fall in the "last 4 lines" * tail, so the encoder emits 2 pairs (no quad). * * Layout: addresses 0x00, 0x10, 0x20, 0x30; data words are i*0x100+j. * Expected: * pair 1: header=0xC1F80000+(0x10-0x10)=0xC1F80000, then 16 BE16 words * from lines 1+2. * pair 2: header=0xC1F80000+(0x30-0x10)=0xC1F80020, then 16 BE16 words * from lines 3+4. * Total bytes: 4+32 + 4+32 = 72. */ static void test_uboot_four_lines_two_pairs(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); static const char body[] = "00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f\n" "00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f\n" "00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f\n" "00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f\n"; rcw_error_t err = rcw_uboot_encode(ctx, body, strlen(body)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 72); /* Pair 1 header */ assert_int_equal(pbi_be32(ctx, 0), 0xC1F80000u); /* First few words of pair 1 (line 1 starts at offset 4) */ assert_int_equal(pbi_be16(ctx, 4), 0x0001); assert_int_equal(pbi_be16(ctx, 6), 0x0203); assert_int_equal(pbi_be16(ctx, 8), 0x0405); /* Last word of line 1 (offset 4 + 7*2 = 18) */ assert_int_equal(pbi_be16(ctx, 18), 0x0e0f); /* First word of line 2 */ assert_int_equal(pbi_be16(ctx, 20), 0x1011); /* Last word of pair 1 = last word of line 2 (offset 4 + 15*2 = 34) */ assert_int_equal(pbi_be16(ctx, 34), 0x1e1f); /* Pair 2 header at offset 36 */ assert_int_equal(pbi_be32(ctx, 36), 0xC1F80020u); /* First word of line 3 */ assert_int_equal(pbi_be16(ctx, 40), 0x2021); /* Last word of line 4 (offset 36 + 4 + 15*2 = 70) */ assert_int_equal(pbi_be16(ctx, 70), 0x3e3f); rcw_ctx_free(ctx); } /* * Test 2: an 8-line input. Lines 1-4 form a quad (the first 4 are not * in the last-4 tail). Lines 5-8 are the tail and emit 2 pairs. * * Expected: * quad: header=0x81F80000+(0x30-0x30)=0x81F80000, 32 BE16 words. * pair 1: header=0xC1F80000+(0x50-0x10)=0xC1F80040, 16 BE16 words. * pair 2: header=0xC1F80000+(0x70-0x10)=0xC1F80060, 16 BE16 words. * Total bytes: 4+64 + 4+32 + 4+32 = 140. */ static void test_uboot_eight_lines_quad_plus_pairs(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); static const char body[] = "00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f\n" "00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f\n" "00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f\n" "00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f\n" "00000040: 4041 4243 4445 4647 4849 4a4b 4c4d 4e4f\n" "00000050: 5051 5253 5455 5657 5859 5a5b 5c5d 5e5f\n" "00000060: 6061 6263 6465 6667 6869 6a6b 6c6d 6e6f\n" "00000070: 7071 7273 7475 7677 7879 7a7b 7c7d 7e7f\n"; rcw_error_t err = rcw_uboot_encode(ctx, body, strlen(body)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 140); /* --- quad block --- */ assert_int_equal(pbi_be32(ctx, 0), 0x81F80000u); /* First word of line 1 (offset 4) */ assert_int_equal(pbi_be16(ctx, 4), 0x0001); /* First word of line 2 (offset 4 + 8*2) */ assert_int_equal(pbi_be16(ctx, 20), 0x1011); /* First word of line 3 (offset 4 + 16*2) */ assert_int_equal(pbi_be16(ctx, 36), 0x2021); /* Last word of quad = last word of line 4 (offset 4 + 31*2 = 66) */ assert_int_equal(pbi_be16(ctx, 66), 0x3e3f); /* --- pair 1 (lines 5-6) starts at offset 68 --- */ assert_int_equal(pbi_be32(ctx, 68), 0xC1F80040u); assert_int_equal(pbi_be16(ctx, 72), 0x4041); /* Last word of pair 1 = last word of line 6 (offset 68 + 4 + 15*2 = 102) */ assert_int_equal(pbi_be16(ctx, 102), 0x5e5f); /* --- pair 2 (lines 7-8) starts at offset 104 --- */ assert_int_equal(pbi_be32(ctx, 104), 0xC1F80060u); assert_int_equal(pbi_be16(ctx, 108), 0x6061); /* Last word = last word of line 8 (offset 104 + 4 + 15*2 = 138) */ assert_int_equal(pbi_be16(ctx, 138), 0x7e7f); rcw_ctx_free(ctx); } /* * Test 3: blank lines inside the .uboot block must be skipped (matches * rcw.py which silently ignores them). Same expected output as test 1. */ static void test_uboot_skips_blank_lines(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); static const char body[] = "\n" "00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f\n" " \n" "00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f\n" "00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f\n" "00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f\n"; rcw_error_t err = rcw_uboot_encode(ctx, body, strlen(body)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 72); assert_int_equal(pbi_be32(ctx, 0), 0xC1F80000u); assert_int_equal(pbi_be32(ctx, 36), 0xC1F80020u); rcw_ctx_free(ctx); } /* * Test 4: end-to-end via the parser. Verifies that the parser * recognizes the .uboot/.end markers and dispatches to the encoder. */ static void test_uboot_via_parser(void **state) { (void)state; static const char source[] = "%size=1024\n" "%pbiformat=2\n" "%classicbitnumbers=1\n" "%littleendian=1\n" "%nocrc=1\n" "PBI_LENGTH[287:276]\n" ".uboot\n" "00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f\n" "00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f\n" "00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f\n" "00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f\n" ".end\n"; rcw_ctx_t *ctx = rcw_ctx_new(); rcw_error_t err = rcw_parse(ctx, source, strlen(source)); assert_int_equal(err, RCW_OK); /* The parser should have routed the .uboot body through the encoder */ assert_int_equal(ctx->pbi.len, 72); assert_int_equal(pbi_be32(ctx, 0), 0xC1F80000u); assert_int_equal(pbi_be32(ctx, 36), 0xC1F80020u); rcw_ctx_free(ctx); } /* * Test 5: empty .uboot block produces no PBI output and no error. */ static void test_uboot_empty(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); rcw_error_t err = rcw_uboot_encode(ctx, "", 0); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 0); static const char blanks[] = "\n\n \n"; err = rcw_uboot_encode(ctx, blanks, strlen(blanks)); assert_int_equal(err, RCW_OK); assert_int_equal(ctx->pbi.len, 0); rcw_ctx_free(ctx); } /* * Test 6: malformed line (missing ':' after address) -> RCW_ERR_PBI_SYNTAX. */ static void test_uboot_bad_address(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); static const char body[] = "notavalidline\n"; rcw_error_t err = rcw_uboot_encode(ctx, body, strlen(body)); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); rcw_ctx_free(ctx); } /* * Test 7: malformed line (truncated - fewer than 8 hex words) -> * RCW_ERR_PBI_SYNTAX. */ static void test_uboot_truncated_line(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); static const char body[] = "00000000: 0001 0203\n"; rcw_error_t err = rcw_uboot_encode(ctx, body, strlen(body)); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); rcw_ctx_free(ctx); } /* * Test 8: line longer than RCW_UBOOT_LINE_MAX -> RCW_ERR_PBI_SYNTAX. */ static void test_uboot_line_too_long(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); /* Build a line with > RCW_UBOOT_LINE_MAX chars */ char body[RCW_UBOOT_LINE_MAX + 64]; memset(body, 'X', sizeof(body) - 1); body[sizeof(body) - 1] = '\n'; rcw_error_t err = rcw_uboot_encode(ctx, body, sizeof(body)); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); rcw_ctx_free(ctx); } /* * B6 - the pair header base is RCW_UBOOT_PAIR_BASE + (addr - 0x10). * If the second line of a pair has addr < 0x10, the subtraction wraps * on uint32_t and produces a nonsense header. Reject it explicitly. */ static void test_uboot_pair_addr_underflow(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); /* 4-line input -> all 4 are tail; pair 1 closes on line 2 (addr=0x05 < 0x10). */ static const char body[] = "00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f\n" "00000005: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f\n" "00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f\n" "00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f\n"; rcw_error_t err = rcw_uboot_encode(ctx, body, strlen(body)); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); rcw_ctx_free(ctx); } /* * B6 - same, for the quad header base RCW_UBOOT_QUAD_BASE + (addr - 0x30). * The 4th line of an 8-line input forms the quad; if its addr < 0x30, * the subtraction wraps. */ static void test_uboot_quad_addr_underflow(void **state) { (void)state; rcw_ctx_t *ctx = setup_ctx(); /* * 8 lines: lines 1-4 form a quad; line 4 has addr=0x20 (< 0x30). * Lines 5-8 are tail (would emit 2 pairs) but the quad fails first. */ static const char body[] = "00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f\n" "00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f\n" "00000018: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f\n" "00000020: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f\n" "00000040: 4041 4243 4445 4647 4849 4a4b 4c4d 4e4f\n" "00000050: 5051 5253 5455 5657 5859 5a5b 5c5d 5e5f\n" "00000060: 6061 6263 6465 6667 6869 6a6b 6c6d 6e6f\n" "00000070: 7071 7273 7475 7677 7879 7a7b 7c7d 7e7f\n"; rcw_error_t err = rcw_uboot_encode(ctx, body, strlen(body)); assert_int_equal(err, RCW_ERR_PBI_SYNTAX); rcw_ctx_free(ctx); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_uboot_four_lines_two_pairs), cmocka_unit_test(test_uboot_eight_lines_quad_plus_pairs), cmocka_unit_test(test_uboot_skips_blank_lines), cmocka_unit_test(test_uboot_via_parser), cmocka_unit_test(test_uboot_empty), cmocka_unit_test(test_uboot_bad_address), cmocka_unit_test(test_uboot_truncated_line), cmocka_unit_test(test_uboot_line_too_long), cmocka_unit_test(test_uboot_pair_addr_underflow), cmocka_unit_test(test_uboot_quad_addr_underflow), }; return cmocka_run_group_tests(tests, NULL, NULL); }