pax_global_header00006660000000000000000000000064152674113570014525gustar00rootroot0000000000000052 comment=b09f8e0360884a43f15b6a30dbb25381c374b4ee vjardin-qoriq-rcw-f74d8cb/000077500000000000000000000000001526741135700156075ustar00rootroot00000000000000vjardin-qoriq-rcw-f74d8cb/.github/000077500000000000000000000000001526741135700171475ustar00rootroot00000000000000vjardin-qoriq-rcw-f74d8cb/.github/workflows/000077500000000000000000000000001526741135700212045ustar00rootroot00000000000000vjardin-qoriq-rcw-f74d8cb/.github/workflows/ci.yml000066400000000000000000000214441526741135700223270ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # Copyright 2026 Free Mobile -- Vincent Jardin # # CI for qoriq-rcw. # # Builds the CLI tool against a freshly cloned libqoriq-rcw on a matrix # of {x86_64, ARM64} x {gcc, clang}. The cross-validation test is run # in a separate job that additionally clones the upstream nxp-qoriq/rcw # repository and verifies that qoriq-rcw produces byte-identical output # to rcw.py for every pbiformat=2 board configuration. name: CI on: push: pull_request: permissions: contents: read env: # libqoriq-rcw source repo + ref to clone for builds. # Override per workflow dispatch or via repo variables if needed. LIBQORIQ_RCW_REPO: https://github.com/${{ github.repository_owner }}/libqoriq-rcw.git LIBQORIQ_RCW_REF: main # Upstream NXP RCW repo (rcw.py + per-board .rcw configs). RCW_UPSTREAM_REPO: https://github.com/nxp-qoriq/rcw.git jobs: build: strategy: fail-fast: false matrix: os: [ubuntu-24.04, ubuntu-24.04-arm] cc: [gcc, clang] runs-on: ${{ matrix.os }} name: build (${{ matrix.os }}, ${{ matrix.cc }}) env: CC: ${{ matrix.cc }} steps: - name: Checkout qoriq-rcw uses: actions/checkout@v5 with: path: qoriq-rcw - name: Clone libqoriq-rcw run: | git clone --depth 1 --branch "${LIBQORIQ_RCW_REF}" \ "${LIBQORIQ_RCW_REPO}" libqoriq-rcw - 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: Build libqoriq-rcw working-directory: libqoriq-rcw run: | meson setup build -Db_sanitize=address,undefined -Db_lundef=false meson compile -C build - name: Build qoriq-rcw against sibling libqoriq-rcw working-directory: qoriq-rcw run: | meson setup build \ -Dlibqoriq-rcw-path=../libqoriq-rcw \ -Db_sanitize=address,undefined \ -Db_lundef=false meson compile -C build - name: Upload meson logs on failure if: failure() uses: actions/upload-artifact@v7 with: name: meson-logs-build-${{ matrix.os }}-${{ matrix.cc }} path: | libqoriq-rcw/build/meson-logs/ qoriq-rcw/build/meson-logs/ cross-validate: # Required: this job must pass. It is kept separate from `build` # so it can be skipped/disabled if the upstream nxp-qoriq/rcw # repository is unreachable, without touching the build matrix. strategy: fail-fast: false matrix: os: [ubuntu-24.04, ubuntu-24.04-arm] cc: [gcc, clang] runs-on: ${{ matrix.os }} name: cross-validate (${{ matrix.os }}, ${{ matrix.cc }}) env: CC: ${{ matrix.cc }} steps: - name: Checkout qoriq-rcw uses: actions/checkout@v5 with: path: qoriq-rcw - name: Clone libqoriq-rcw run: | git clone --depth 1 --branch "${LIBQORIQ_RCW_REF}" \ "${LIBQORIQ_RCW_REPO}" libqoriq-rcw - name: Clone upstream nxp-qoriq/rcw (rcw.py + board configs) run: | git clone --depth 1 "${RCW_UPSTREAM_REPO}" rcw - name: Install build/test 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 python3 - name: Build libqoriq-rcw working-directory: libqoriq-rcw run: | meson setup build -Db_sanitize=address,undefined -Db_lundef=false meson compile -C build - name: Build qoriq-rcw with -Drcw-ref-dir working-directory: qoriq-rcw run: | meson setup build \ -Dlibqoriq-rcw-path=../libqoriq-rcw \ -Drcw-ref-dir=../rcw \ -Db_sanitize=address,undefined \ -Db_lundef=false meson compile -C build - name: Run cross-validation test working-directory: qoriq-rcw run: meson test -C build --print-errorlogs - name: Upload meson logs on failure if: failure() uses: actions/upload-artifact@v7 with: name: meson-logs-cross-${{ matrix.os }}-${{ matrix.cc }} path: qoriq-rcw/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 qoriq-rcw (full history) uses: actions/checkout@v5 with: fetch-depth: 0 path: qoriq-rcw - name: Clone libqoriq-rcw run: | git clone --depth 1 --branch "${LIBQORIQ_RCW_REF}" \ "${LIBQORIQ_RCW_REPO}" libqoriq-rcw - 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: Build libqoriq-rcw (so qoriq-rcw can find it) working-directory: libqoriq-rcw run: | meson setup build meson compile -C build - name: meson setup qoriq-rcw working-directory: qoriq-rcw run: meson setup build -Dlibqoriq-rcw-path=../libqoriq-rcw - name: meson dist working-directory: qoriq-rcw run: meson dist -C build --no-tests - name: Upload dist tarball uses: actions/upload-artifact@v7 with: name: dist-tarball path: qoriq-rcw/build/meson-dist/*.tar.xz package: # Build the qoriq-rcw .deb package 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. The CLI deb depends on # libqoriq-rcw-dev at build time, so each cell first builds and installs # libqoriq-rcw1 and libqoriq-rcw-dev from a sibling clone, then builds # the qoriq-rcw deb against the installed library. 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 qoriq-rcw uses: actions/checkout@v5 with: path: qoriq-rcw - name: Clone libqoriq-rcw run: | git clone --depth 1 --branch "${LIBQORIQ_RCW_REF}" \ "${LIBQORIQ_RCW_REPO}" libqoriq-rcw - name: Build libqoriq-rcw .debs working-directory: libqoriq-rcw run: dpkg-buildpackage -us -uc -b - name: Install libqoriq-rcw .debs run: | export DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ ./libqoriq-rcw[0-9]_*.deb ./libqoriq-rcw-dev_*.deb - name: Build qoriq-rcw .deb working-directory: qoriq-rcw run: dpkg-buildpackage -us -uc -b - name: Collect .deb artifacts run: | mkdir -p artifacts mv libqoriq-rcw*_*.deb artifacts/ 2>/dev/null || true # Match both qoriq-rcw_*.deb (CLI, per-arch) and # qoriq-rcw-data_*_all.deb (data, Architecture: all). mv qoriq-rcw*_*.deb artifacts/ ls -la artifacts/ - name: Upload .deb packages uses: actions/upload-artifact@v7 with: name: debs-${{ matrix.distro.slug }}-${{ matrix.arch.name }} path: artifacts/*.deb vjardin-qoriq-rcw-f74d8cb/LICENSE000066400000000000000000000027451526741135700166240ustar00rootroot00000000000000Copyright 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-qoriq-rcw-f74d8cb/README.md000066400000000000000000000144471526741135700171000ustar00rootroot00000000000000# qoriq-rcw [![CI](https://github.com/vjardin/qoriq-rcw/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/vjardin/qoriq-rcw/actions/workflows/ci.yml) Command-line tool 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** is a 1024-bit block that governs the very first stage of hardware initialization. At power-on reset, the SoC's on-chip **Service Processor** (a dedicated Cortex-A5 core running from its own 64 KB boot ROM) samples the `CFG_RCW_SRC` configuration pins, loads 128 bytes of RCW data from the selected boot interface (FlexSPI NOR, SD card, eMMC, or I2C) into the Device Configuration RCWSR registers, then locks the PLLs accordingly. If a Pre-Boot Initialization (PBI) section is present, the Service Processor executes those commands next -- writing CCSR registers and copying data into on-chip SRAM (OCRAM). Only after all of this completes are the application cores released from reset. The RCW binary image produced by this tool is what gets flashed onto the board's boot medium. It encodes PLL ratios, SerDes lane assignments, boot source selection, pin muxing, and errata workarounds. RCW source files (`.rcw`) are human-readable descriptions of these settings; this tool compiles them into binary and can decompile binaries back to source for inspection. `qoriq-rcw` is a C reimplementation of the `rcw.py` Python tool, producing byte-identical output. It is built on top of `libqoriq-rcw`. Supported platforms: LS1028, LS1088, LS2088, and LX2160 families (PBI format 2). ## Dependencies - C17 compiler (gcc or clang) - Meson (>= 0.56) and Ninja - `libqoriq-rcw` - `libmcpp-dev` - `libcmocka-dev` for the unit tests, `pandoc` for the man page (both optional) ## Build ```bash meson setup build # libqoriq-rcw installed system-wide meson setup build -Dlibqoriq-rcw-path=../libqoriq-rcw # for a local copy meson compile -C build ``` The `qoriq-rcw` binary is at `build/qoriq-rcw`. ## Test ```bash meson test -C build ``` The test suite includes a cross-validation script that compiles every `.rcw` configuration for all supported boards with both `qoriq-rcw` and the reference `rcw.py`, compares the outputs byte-for-byte, then runs `qoriq-rcw --check` on every image. This requires Python 3 and a clone of [nxp-qoriq/rcw](https://github.com/nxp-qoriq/rcw) (`-Drcw-ref-dir=PATH`, or `../rcw`). ## Install ```bash meson install -C build ``` ## Releasing The version number shall be bumped for every release: | File | What to change | |--------------------|-----------------------------------------------| | `meson.build` | the `version :` field of the `project()` call | | `debian/changelog` | a new top entry `qoriq-rcw (X.Y.Z-1) ...` | The `QORIQ_RCW_VERSION` macro used by the CLI is derived from the `meson.build` `version :` field at build time, so there is no separate header to edit. Add the changelog entry with the text timestamp: ```bash date -R # e.g. Tue, 30 Jun 2026 16:56:40 +0200 ``` Commit the two files, create an annotated tag, and push both: ```bash git add meson.build debian/changelog git commit -m "Release X.Y.Z" git tag -a vX.Y.Z -m "qoriq-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/qoriq-rcw/archive/refs/tags/vX.Y.Z.tar.gz ``` For example: the 1.0.0 release was commit [`57344b8`](https://github.com/vjardin/qoriq-rcw/commit/57344b80d73aba6b27e07f3510e8a397a714b5ea) ("Release 1.0.0"), tagged `v1.0.0` ([release page](https://github.com/vjardin/qoriq-rcw/releases/tag/v1.0.0)). ## Usage Compile an RCW source file to a binary: ```bash cd ls1088ardb qoriq-rcw -i FCQQQQQQQQ_PPP_H_0x1d_0x0d/rcw_1600_qspi.rcw -o rcw.bin ``` Override a bitfield value (the value reads like Python's `int(x, 0)`: decimal, `0x`, `0o` or `0b`): ```bash qoriq-rcw -i config/rcw_2000.rcw -o rcw.bin -D SYS_PLL_RAT=10 qoriq-rcw -i config/rcw_2000.rcw -o rcw.bin -D SYS_PLL_RAT=0b1010 ``` Define a preprocessor macro, for sources that select variants with `#ifdef` (the LX2160A errata and SerDes protocol files do): ```bash qoriq-rcw -i rcw_2000.rcw -o rcw.bin --define RCW_ERRATA_NO_TRIM qoriq-rcw -i rcw_2000.rcw -o rcw.bin --define SRDS_PRTCL_S1_VAL=0x13 ``` Add an include path: ```bash qoriq-rcw -i rcw_2000.rcw -o rcw.bin -I ../lx2160asi ``` Produce raw RCW data without PBL preamble: ```bash qoriq-rcw -i rcw_2000.rcw -o rcw_raw.bin --no-pbl ``` Check an image before flashing it, the way the BootROM's Service Processor would likely do: ```bash qoriq-rcw --check -i rcw.bin -v ``` Decompile a binary back to source: ```bash qoriq-rcw -r -i rcw.bin --rcwi lx2160a.rcwi -o rcw_decoded.rcw ``` Capture the live RCW from a running QorIQ/Layerscape board (auto-detects SoC from `/proc/device-tree/compatible`, falls back to an SVR probe). Requires root for `/dev/mem`: ```bash sudo qoriq-rcw --dump -o /tmp/live.rcw ``` The output starts with a C-comment header listing the detected SoC, the DCFG base address used, and the result of every sanity check (SVR match, non-zero/non-stuck-high RCW, endianness, ...). PBI commands are not recoverable in this mode - only the 1024-bit RCW register state. If the `qoriq-rcw-data` package is not installed, pass the matching `.rcwi` explicitly: ```bash sudo qoriq-rcw --dump --rcwi ./lx2160a.rcwi -o /tmp/live.rcw ``` Recover the RCW and the PBI commands directly from the boot flash (unlike `--dump`, which only sees the post-execution RCW state because PBI commands run once at boot and aren't preserved). The boot device is auto-detected from `PORSR1.RCW_SRC` and a `/proc/mtd` partition name scan: ```bash sudo qoriq-rcw --dump-flash -o /tmp/flash.rcw ``` Read both bootrom slots (primary at offset 0/0x1000, fallback at +8 MiB) - useful for verifying A/B image consistency: ```bash sudo qoriq-rcw --dump-flash --slot all -o /tmp/dump.rcw # produces /tmp/dump.primary.rcw and /tmp/dump.fallback.rcw ``` See `qoriq-rcw(1)` for the full reference. ## License BSD-3-Clause. Copyright 2026 Free Mobile -- Vincent Jardin. vjardin-qoriq-rcw-f74d8cb/data/000077500000000000000000000000001526741135700165205ustar00rootroot00000000000000vjardin-qoriq-rcw-f74d8cb/data/LICENSE.upstream000066400000000000000000000030631526741135700213660ustar00rootroot00000000000000Copyright 2011-2016 Freescale Semiconductor, Inc Copyright 2017-2020 NXP All rights reserved. All files present in the repo are under BSD License i.e. 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 REGENTS 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-qoriq-rcw-f74d8cb/data/README.md000066400000000000000000000022231526741135700177760ustar00rootroot00000000000000# qoriq-rcw data Canonical `.rcwi` bitfield definitions for the four pbiformat=2 SoC families supported by `qoriq-rcw`: | File | SoC family | Source in `nxp-qoriq/rcw` | |------------------|---------------------|-----------------------------------| | `ls1028a.rcwi` | NXP LS1028A | `ls1028asi/ls1028a.rcwi` | | `ls1088a.rcwi` | NXP LS1088A | `ls1088ardb/ls1088rdb.rcwi` | | `ls2088a.rcwi` | NXP LS2088A | `ls2088asi/ls2088a.rcwi` | | `lx2160a.rcwi` | NXP LX2160A / LX2120A / LX2080A | `lx2160asi/lx2160a.rcwi` | These files are vendored from and are (c) 2011-2016 Freescale Semiconductor (c) 2017-2020 NXP under the 3-clause BSD license (see `LICENSE.upstream`). Each file has a SPDX header prepended; the body is unmodified. They are installed to `$datadir/qoriq-rcw/` so: * `qoriq-rcw --dump -o out.rcw` resolves the matching `.rcwi` automatically (no `--rcwi` needed). * `qoriq-rcw -i out.rcw -o out.bin` recompiles dump output without needing `-I` because the CLI adds `$datadir/qoriq-rcw` to the preprocessor search path automatically. vjardin-qoriq-rcw-f74d8cb/data/ls1028a.rcwi000066400000000000000000000065071526741135700205100ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright 2011-2016 Freescale Semiconductor, Inc. */ /* Copyright 2017-2020 NXP */ /* Vendored from nxp-qoriq/rcw: ls1028asi/ls1028a.rcwi */ %size=1024 %pbiformat=2 %classicbitnumbers=1 %littleendian=1 %nocrc=1 SYS_PLL_CFG[1:0] SYS_PLL_RAT[6:2] RESERVED_7[7] MEM_PLL_CFG[9:8] MEM_PLL_RAT[15:10] CGA_PLL1_CFG[25:24] CGA_PLL1_RAT[31:26] CGA_PLL2_CFG[33:32] CGA_PLL2_RAT[39:34] RESERVED_121_120[121:120] RESERVED_127_122[127:122] SYS_PLL_SPD[128] MEM_PLL_SPD[129] RESERVED_131[131] CGA_PLL1_SPD[132] CGA_PLL2_SPD[133] C1_PLL_SEL[147:144] HWA_CGA_M1_CLK_SEL[178:176] HWA_CGA_M2_CLK_SEL[181:179] HWA_CGA_M3_CLK_SEL[184:182] RESERVED_199_197[199:197] RESERVED_202_200[202:200] HWA_CGA_M4_CLK_SEL[208:206] RESERVED_209[209] RESERVED_210[210] DDR_REFCLK_SEL[212:211] DRAM_LAT[214:213] DDR_RATE[215] RESERVED_216[216] RESERVED_217[217] RESERVED_218[218] RESERVED_220_219[220:219] RESERVED_222_221[222:221] RESERVED_224_223[224:223] RESERVED_226_225[226:225] RESERVED_228_227[228:227] REQD_CUST_CONFIG_230_229[230:229] RESERVED_255_231[255:231] RESERVED_259_256[259:256] BOOT_LOC[264:260] BOOT_HO[265] SB_EN[266] RESERVED_267[267] FLASH_CFG1[269:268] RESERVED_275_270[275:270] PBI_LENGTH[287:276] SDBGEN[288] RESERVED_291_289[291:289] SYSCLK_FREQ[301:292] RESERVED_303_302[303:302] GPIO_LED_NUM[310:304] GPIO_LED_EN[311] RESERVED_318_312[318:312] RESERVED_351_319[351:319] IIC2_PMUX[354:352] IIC3_PMUX[357:355] IIC4_PMUX[360:358] IIC5_PMUX[363:361] IIC6_PMUX[366:364] XSPI1_A_DATA74_PMUX[369:367] XSPI1_A_DATA30_PMUX[372:370] XSPI1_A_BASE_PMUX[375:373] SDHC1_BASE_PMUX[378:376] RESERVED_381_379[381:379] RESERVED_383_382[383:382] SDHC2_DAT74_PMUX[386:384] SDHC2_BASE_PMUX[389:387] UART1_SOUTSIN_PMUX[392:390] RESERVED_395_393[395:393] UART2_SOUTSIN_PMUX[398:396] RESERVED_401_399[401:399] RESERVED_404_402[404:402] RESERVED_407_405[407:405] RESERVED_410_408[410:408] RESERVED_413_411[413:411] RESERVED_415_414[415:414] RESERVED_418_416[418:416] CLK_OUT_PMUX[421:419] ASLEEP_PMUX[424:422] RESET_REQ_PMUX[425] IIC1_PMUX[426] RESERVED_427[427] RESERVED_479_428[479:428] HOST_AGT_PEX1[485] HOST_AGT_PEX2[486] RESERVED_487[487] RESERVED_488[488] RESERVED_511_491[511:491] RESERVED_543_512[543:512] RESERVED_575_544[575:544] RESERVED_607_576[607:576] RESERVED_639_608[639:608] RESERVED_671_640[671:640] RESERVED_703_672[703:672] RESERVED_735_704[735:704] RESERVED_767_736[767:736] GP_INFO[799:768] RESERVED_831_800[831:800] RESERVED_832[832] EC1_SAI4_5_PMUX[835:833] EC1_SAI3_6_PMUX[838:836] RESERVED_839[839] USB_DRVVBUS_PMUX[841:840] USB_PWRFAULT_PMUX[843:842] USB3_CLK_FSEL[849:844] ENETC_RCW[857:850] SPI3_PMUX[859:858] GTX_CLK125_PMUX[861:860] RESERVED_895_860[895:862] SRDS_PLL_PD_PLL1[896] SRDS_PLL_PD_PLL2[897] RESERVED_898[898] RESERVED_899[899] RESERVED_900[900] RESERVED_901[901] RESERVED_902[902] RESERVED_903[903] RESERVED_904[904] RESERVED_905[905] RESERVED_906[906] RESERVED_907[907] RESERVED_908[908] RESERVED_909[909] RESERVED_910[910] RESERVED_911[911] SRDS_PRTCL_S1_L0[915:912] SRDS_PRTCL_S1_L1[919:916] SRDS_PRTCL_S1_L2[923:920] SRDS_PRTCL_S1_L3[927:924] RESERVED_943_928[943:928] SRDS_PLL_REF_CLK_SEL_S1[945:944] RESERVED_947_946[947:946] SRDS_DIV_PEX_S1[949:948] RESERVED_951_950[951:950] SRDS_S1_REFCLK_SRC_SEL[953:952] RESERVED_959_954[959:954] RESERVED_991_960[991:960] RESERVED_1023_992[1023:992] vjardin-qoriq-rcw-f74d8cb/data/ls1088a.rcwi000066400000000000000000000043761526741135700205200ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright 2011-2016 Freescale Semiconductor, Inc. */ /* Copyright 2017-2020 NXP */ /* Vendored from nxp-qoriq/rcw: ls1088ardb/ls1088rdb.rcwi */ %size=1024 %pbiformat=2 %classicbitnumbers=1 %littleendian=1 SYS_PLL_CFG[1:0] SYS_PLL_RAT[6:2] MEM_PLL_CFG[9:8] MEM_PLL_RAT[15:10] CGA_PLL1_CFG[25:24] CGA_PLL1_RAT[31:26] CGA_PLL2_CFG[33:32] CGA_PLL2_RAT[39:34] SYS_PLL_SPD[128] MEM_PLL_SPD[129] CGA_PLL1_SPD[132] CGA_PLL2_SPD[133] C1_PLL_SEL[147:144] C2_PLL_SEL[151:148] HWA_CGA_M1_CLK_SEL[178:176] HWA_CGA_M2_CLK_SEL[181:179] DDR_REFCLK_SEL[212:211] DRAM_LAT[214:213] DDR_RATE[215] BOOT_LOC[264:260] BOOT_HO[265] SB_EN[266] FLASH_MODE[275:267] PBI_LENGTH[287:276] SDBGEN[288] SYSCLK_FREQ[301:292] GPIO_LED_NUM[310:304] GPIO_LED_EN[311] UART_EXT[354:352] IIC2_EXT[357:355] IIC3_EXT[360:358] IIC4_EXT[363:361] SPI_EXT[366:364] SDHC_EXT[369:367] IRQ1_EXT[372:370] IRQ2_EXT[375:373] IRQ_EXT[378:376] UART_BASE[385:384] IIC2_BASE[387:386] IIC3_BASE[389:388] IIC4_BASE[391:390] SPI_BASE_BASE[393:392] SPI_PCS_BASE[395:394] SDHC_BASE[396] IRQ03_BASE[397] IRQ04_BASE[398] IRQ05_BASE[399] IRQ06_BASE[400] IRQ07_BASE[401] IRQ08_BASE[402] IRQ09_BASE[403] IRQ10_BASE[404] IRQ11_BASE[405] EVT_9[406] RTC[407] ASLEEP[408] IFC_GRP_A_EXT[418:416] IFC_GRP_D_EXT[427:425] IFC_GRP_E_EXT[430:428] IFC_GRP_F_EXT[433:431] IFC_GRP_G_EXT[436:434] IFC_GRP_H_EXT[439:437] IFC_GRP_I_EXT[442:440] IFC_GRP_A_BASE[449:448] IFC_GRP_D_BASE[455:454] IFC_GRP_E_BASE[457:456] IFC_GRP_FGHI_BASE[459:458] IFC_A_8_6[460] QSPI_OCT_EN[461] HOST_AGT_PEX1[485] HOST_AGT_PEX2[486] HOST_AGT_PEX3[487] GP_INFO[799:768] EC1[834:832] EC2[837:835] EM1[838] EM2[839] USB[857:856] USB1_REFCLK_SEL[859:858] USB2_REFCLK_SEL[861:860] USB1_CLK_FSEL[869:864] USB2_CLK_FSEL[875:870] SRDS_PLL_PD_PLL1[896] SRDS_PLL_PD_PLL2[897] SRDS_PLL_PD_PLL3[898] SRDS_PLL_PD_PLL4[899] SRDS_PRTCL_S1_LN0[915:912] SRDS_PRTCL_S1_LN1[919:916] SRDS_PRTCL_S1_LN2[923:920] SRDS_PRTCL_S1_LN3[927:924] SRDS_PRTCL_S2_LN0[931:928] SRDS_PRTCL_S2_LN1[935:932] SRDS_PRTCL_S2_LN2[939:936] SRDS_PRTCL_S2_LN3[943:940] SRDS_PLL_REF_CLK_SEL_S1[945:944] SRDS_PLL_REF_CLK_SEL_S2[947:946] SRDS_DIV_PEX_S1[949:948] SRDS_DIV_PEX_S2[951:950] SRDS_S1_REFCLK_SRC_SEL[953:952] SRDS_S2_REFCLK_SRC_SEL[955:954] vjardin-qoriq-rcw-f74d8cb/data/ls2088a.rcwi000066400000000000000000000034521526741135700205130ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright 2011-2016 Freescale Semiconductor, Inc. */ /* Copyright 2017-2020 NXP */ /* Vendored from nxp-qoriq/rcw: ls2088asi/ls2088a.rcwi */ %size=1024 %pbiformat=2 %classicbitnumbers=1 %littleendian=1 %nocrc=1 SYS_PLL_CFG[1:0] SYS_PLL_RAT[6:2] RESERVED_7[7] MEM_PLL_CFG[9:8] MEM_PLL_RAT[15:10] MEM2_PLL_CFG[17:16] MEM2_PLL_RAT[23:18] CGA_PLL1_CFG[25:24] CGA_PLL1_RAT[31:26] CGA_PLL2_CFG[33:32] CGA_PLL2_RAT[39:34] CGB_PLL1_CFG[49:48] CGB_PLL1_RAT[55:50] CGB_PLL2_CFG[57:56] CGB_PLL2_RAT[63:58] SYS_PLL_SPD[128] MEM1_PLL_SPD[129] MEM2_PLL_SPD[130] CGA_PLL1_SPD[132] CGA_PLL2_SPD[133] CGB_PLL1_SPD[135] CGB_PLL2_SPD[136] C1_PLL_SEL[147:144] C2_PLL_SEL[151:148] C3_PLL_SEL[155:152] C4_PLL_SEL[159:156] DDR_REFCLK_SEL[212:211] DRAM_LAT[214:213] DDR_RATE[215] BOOT_LOC[264:260] BOOT_HO[265] SB_EN[266] FLASH_MODE[275:267] PBI_LENGTH[287:276] SDBGEN[288] SYSCLK_FREQ[301:292] GPIO_LED_NUM[310:304] GPIO_LED_EN[311] UART_BASE[385:384] IIC2_BASE[387:386] IIC3_BASE[389:388] IIC4_BASE[391:390] SPI_BASE_BASE[393:392] SPI_PCS_BASE[395:394] SDHC_BASE[396] IRQ03_BASE[397] IRQ04_BASE[398] IRQ05_BASE[399] IRQ06_BASE[400] IRQ07_BASE[401] IRQ08_BASE[402] IRQ09_BASE[403] IRQ10_BASE[404] IRQ11_BASE[405] EVT_9[406] RTC[407] ASLEEP[408] IFC_GRP_D_EXT[427:425] IFC_GRP_A_BASE[449:448] IFC_GRP_E_BASE[457:456] IFC_GRP_FGHI_BASE[459:458] IFC_A_8_6[460] QSPI_OCT_EN[461] HOST_AGT_PEX1[485] HOST_AGT_PEX2[486] HOST_AGT_PEX3[487] HOST_AGT_PEX4[488] GP_INFO[799:768] IEEE_1588_EXT[832] USB_EXT[833] USB3_CLK_FSEL[849:844] SRDS_PLL_PD_S1[896] SRDS_PLL_PD_S2[897] SRDS_PLL_PD_S3[898] SRDS_PLL_PD_S4[899] SRDS_PRTCL_S1[919:912] SRDS_PRTCL_S2[927:920] SRDS_PLL_REF_CLK_SEL_S1[929:928] SRDS_PLL_REF_CLK_SEL_S2[931:930] SRDS_DIV_PEX_S1[945:944] SRDS_DIV_PEX_S2[947:946] vjardin-qoriq-rcw-f74d8cb/data/lx2160a.rcwi000066400000000000000000000047241526741135700205120ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright 2011-2016 Freescale Semiconductor, Inc. */ /* Copyright 2017-2020 NXP */ /* Vendored from nxp-qoriq/rcw: lx2160asi/lx2160a.rcwi */ %size=1024 %pbiformat=2 %classicbitnumbers=1 %littleendian=1 %nocrc=1 SYS_PLL_CFG[1:0] SYS_PLL_RAT[6:2] RESERVED_7[7] MEM_PLL_CFG[9:8] MEM_PLL_RAT[15:10] MEM2_PLL_CFG[17:16] MEM2_PLL_RAT[23:18] CGA_PLL1_CFG[25:24] CGA_PLL1_RAT[31:26] CGA_PLL2_CFG[33:32] CGA_PLL2_RAT[39:34] CGB_PLL1_CFG[49:48] CGB_PLL1_RAT[55:50] CGB_PLL2_CFG[57:56] CGB_PLL2_RAT[63:58] SYS_PLL_SPD[128] MEM_PLL_SPD[129] MEM2_PLL_SPD[130] CGA_PLL1_SPD[132] CGA_PLL2_SPD[133] CGB_PLL1_SPD[135] CGB_PLL2_SPD[136] C1_PLL_SEL[147:144] C2_PLL_SEL[151:148] C3_PLL_SEL[155:152] C4_PLL_SEL[159:156] C5_PLL_SEL[163:160] C6_PLL_SEL[167:164] C7_PLL_SEL[171:168] C8_PLL_SEL[175:172] HWA_CGA_M1_CLK_SEL[178:176] HWA_CGB_M1_CLK_SEL[187:185] HWA_CGA_M4_CLK_SEL[208:206] DDR_REFCLK_SEL[212:211] REQD_CUST_CONFIG[230:229] BOOT_LOC[264:260] BOOT_HO[265] SB_EN[266] FLASH_CFG1[269:268] PBI_LENGTH[287:276] SDBGEN[288] SYSCLK_FREQ[301:292] GPIO_LED_NUM[310:304] GPIO_LED_EN[311] IIC2_PMUX[354:352] IIC3_PMUX[357:355] IIC4_PMUX[360:358] IIC5_PMUX[363:361] IIC6_PMUX[366:364] XSPI1_A_DATA74_PMUX[369:367] XSPI1_A_DATA30_PMUX[372:370] XSPI1_A_BASE_PMUX[375:373] SDHC1_BASE_PMUX[378:376] SDHC1_DIR_PMUX[381:379] SDHC2_DAT74_PMUX[386:384] SDHC2_BASE_PMUX[389:387] UART1_SOUTSIN_PMUX[392:390] UART1_RTSCTS_PMUX[395:393] UART2_SOUTSIN_PMUX[398:396] UART2_RTSCTS_PMUX[401:399] IRQ11_08_PMUX[404:402] IRQ07_04_PMUX[407:405] IRQ03_00_PMUX[410:408] EVT43_PMUX[413:411] EVT20_PMUX[418:416] CLK_OUT_PMUX[421:419] ASLEEP_PMUX[424:422] RESET_REQ_PMUX[425] IIC1_PMUX[426] HOST_AGT_PEX1[485] HOST_AGT_PEX2[486] HOST_AGT_PEX3[487] HOST_AGT_PEX4[488] HOST_AGT_PEX5[489] HOST_AGT_PEX6[490] GP_INFO[799:768] EC1_PMUX[833:832] EC2_PMUX[835:834] USB_EXT_PMUX[836] GTX_CLK_PMUX[837] SDHC1_DS_PMUX[839:838] USB3_CLK_FSEL[849:844] SRDS_PLL_PD_PLL1[896] SRDS_PLL_PD_PLL2[897] SRDS_PLL_PD_PLL3[898] SRDS_PLL_PD_PLL4[899] SRDS_PLL_PD_PLL5[900] SRDS_PLL_PD_PLL6[901] SRDS_PRTCL_S1[916:912] SRDS_PRTCL_S2[921:917] SRDS_PRTCL_S3[926:922] SRDS_REFCLKF_DIS_S1[927] SRDS_REFCLKF_DIS_S2[928] SRDS_REFCLKF_DIS_S3[929] SRDS_PLL_REF_CLK_SEL_S1[933:932] SRDS_PLL_REF_CLK_SEL_S2[935:934] SRDS_PLL_REF_CLK_SEL_S3[937:936] SRDS_INTRA_REF_CLK_S1[940] SRDS_INTRA_REF_CLK_S2[941] SRDS_INTRA_REF_CLK_S3[942] SRDS_DIV_PEX_S1[945:944] SRDS_DIV_PEX_S2[947:946] SRDS_DIV_PEX_S3[949:948] vjardin-qoriq-rcw-f74d8cb/data/lx2160a_regs.rcw000066400000000000000000000120241526741135700213510ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Symbolic addresses for DCFG and Reset block registers used in PBI * commands. Include this from .rcw files to replace magic hex values * with readable names. * * Source: LX2160A Reference Manual Rev. 1, 10/2021 * Chapter 4 : Reset, Clocking, and Initialization * Section 4.3: Reset register descriptions (RST_CCSR at 0x01E60000) * Section 4.9: Power-on reset configuration (DCFG at 0x01E00000) */ /* * DCFG block (0x01E00000 + offset) * Full map: RM Section 2.3 "CCSR Address Map" and * RM Section 4.9.8 register descriptions */ #define DCFG_BASE 0x01E00000 #define DCFG_PORSR1 (DCFG_BASE + 0x000) /* POR Status 1 (RO) */ #define DCFG_GPPORCR1 (DCFG_BASE + 0x020) /* General-Purpose POR Cfg (RO) */ #define DCFG_SVR (DCFG_BASE + 0x0A4) /* System Version (RO) */ #define DCFG_RCWSR1 (DCFG_BASE + 0x100) /* RCW Status Register 1 (RO) */ /* Scratch registers: SCRATCHRW1..11 at +0x200..+0x228 (step 4) * SCRATCHRW12 at +0x22C (core warm reset) * SCRATCHRW13..16 at +0x230..+0x23C */ #define DCFG_SCRATCHRW(n) (DCFG_BASE + 0x200 + (((n) - 1) * 4)) /* Boot Location Pointer. Written by PBI to tell the A72 cores where the * bootloader lives. If both are 0, cores fall back to RCW[BOOT_LOC]. */ #define DCFG_BOOTLOCPTRL (DCFG_BASE + 0x400) /* low 32 bits */ #define DCFG_BOOTLOCPTRH (DCFG_BASE + 0x404) /* high 32 bits */ /* General Control Register 1. Sampled by A72 cores on their reset * de-assertion, so it MUST be written during PBI. * Bit 31 = SYSBARDISABLE * Bit 29 = BROADCASTCACHEMAINT */ #define DCFG_GENCR1 (DCFG_BASE + 0x620) #define GENCR1_SYSBARDISABLE 0x80000000 #define GENCR1_BROADCASTCACHEMAINT 0x20000000 /* * Reset block (RST_CCSR) at 0x01E60000 + offset * See RM Section 4.3.1 "RST_CCSR memory map" */ #define RST_BASE 0x01E60000 /* Boot Release Register (low). BRRL[15:0] = one bit per core (0..15). * A bit set to 1 holds the corresponding core in boot holdoff; clear * to release it and let it enter WFE (recommended for power savings). */ #define RST_BRRL (RST_BASE + 0x060) /* * PBI helper macros for common sequences */ /* 16-core "release all" value (LX2160A has 16 A72 cores). */ #define BRRL_RELEASE_ALL_16 0x0000FFFF /* * FlexSPI0 (XSPI1) AHB-mapped flash window * RM Section 2.2 "System Memory Map" - FlexSPI0 AHB region. * * When booting from FlexSPI NOR, the SoC memory-maps the flash contents * read-only at FLEXSPI0_AHB_BASE so the A72 cores can execute code in * place, without first copying to RAM. */ #define FLEXSPI0_AHB_BASE 0x20000000 /* * Standard NXP Layerscape flash layout for FlexSPI NOR boot. * * These offsets match the default NXP TF-A (ATF) platform for LX2160A * boards (lx2160ardb, lx2160aqds). When customizing, keep your TF-A * build variables aligned with the offsets below, otherwise the A72 * will jump to blank flash and hang. * * In plat/nxp/soc-lx2160a//platform.mk (or platform_def.h): * BL2_BASE = NXP_NOR_BL2_BASE (0x20100000) * BL2_TEXT_START = NXP_NOR_BL2_BASE * FUSE_PROG_IMAGE_OFFSET = NXP_NOR_BL2_OFFSET (0x00100000) * NXP_ROM_RSVD >= 0x100000 (1 MB reserved) * FIP_BASE = NXP_NOR_FIP_BASE (0x20300000) * * Flash offset 0x000000 - 0x001000 : RCW + PBI (Service Processor loads) * Flash offset 0x001000 - 0x100000 : reserved (DDR training data, fuses) * Flash offset 0x100000 - 0x200000 : BL2 (TF-A first stage or U-Boot SPL) * Flash offset 0x200000 - 0x300000 : BL31 (TF-A runtime) * Flash offset 0x300000 - 0x800000 : FIP / BL33 (U-Boot or full payload) * Flash offset 0x800000 - ... : fallback RCW+PBI (SP retries here * if primary RCW load from 0x0 fails) */ #define NXP_NOR_RCW_OFFSET 0x00000000 /* primary slot for SP */ #define NXP_NOR_BL2_OFFSET 0x00100000 /* 1 MB into flash */ #define NXP_NOR_BL31_OFFSET 0x00200000 /* 2 MB into flash */ #define NXP_NOR_FIP_OFFSET 0x00300000 /* 3 MB into flash */ #define NXP_NOR_FALLBACK_OFFSET 0x00800000 /* 8 MB, fallback slot */ /* Physical addresses in the SoC memory map when the CPU fetches * directly from NOR via the FlexSPI0 AHB window (execute-in-place). */ #define NXP_NOR_RCW_BASE (FLEXSPI0_AHB_BASE + NXP_NOR_RCW_OFFSET) #define NXP_NOR_BL2_BASE (FLEXSPI0_AHB_BASE + NXP_NOR_BL2_OFFSET) #define NXP_NOR_BL31_BASE (FLEXSPI0_AHB_BASE + NXP_NOR_BL31_OFFSET) #define NXP_NOR_FIP_BASE (FLEXSPI0_AHB_BASE + NXP_NOR_FIP_OFFSET) /* Value written to DCFG_BOOTLOCPTRL for a standard FlexSPI NOR boot. * This is where the A72 cores begin executing BL2 after the Service * Processor finishes PBI. It MUST equal TF-A's BL2_BASE in the board * platform.mk. Default: 0x20100000. */ #define NXP_NOR_BOOT_ENTRY NXP_NOR_BL2_BASE vjardin-qoriq-rcw-f74d8cb/debian/000077500000000000000000000000001526741135700170315ustar00rootroot00000000000000vjardin-qoriq-rcw-f74d8cb/debian/changelog000066400000000000000000000062171526741135700207110ustar00rootroot00000000000000qoriq-rcw (1.2.0-1) unstable; urgency=medium * Require libqoriq-rcw 1.4.0, whose PBI walk follows the jumps. * --check and --dump-flash: a Jump Conditional is a WARN, not a FAIL. * Debian packaging: 3.0 (quilt) source format -- Vincent Jardin Sun, 25 Oct 2026 15:30:54 +0100 qoriq-rcw (1.1.0-1) unstable; urgency=medium * Require libqoriq-rcw 1.3.0 * New --check mode: verify a PBL image * --dump-flash: check each slot with the library, which adds the PBI CRC and the RCW[PBI_LENGTH] consistency checks; report the PBI summary in the header; compare the slots over the PBL extent only. * --dump: report RCW[PBI_LENGTH], the amount of PBI that ran at boot. * New --define NAME[=VALUE] option: preprocessor macros for the .rcw and .rcwi files. * -D values are now like Python's int(x, 0): 0x, 0o and 0b accepted, a decimal with a leading zero refused. * Cross-validation also runs --check on every upstream board image. -- Vincent Jardin Sun, 25 Oct 2026 01:42:55 +0200 qoriq-rcw (1.0.3-1) unstable; urgency=medium * tests: check the write() result in the soc_db fixture, fixing a -Wunused-result warning in _FORTIFY_SOURCE builds. * meson: add a 'tests' feature option so packaging builds can skip the test suite with -Dtests=disabled. -- Vincent Jardin Sun, 20 Sep 2026 14:37:14 +0200 qoriq-rcw (1.0.2-1) unstable; urgency=medium * build: link libmcpp explicitly when building against a sibling libqoriq-rcw source tree. * ci: update GitHub Actions versions to fix workflow warnings. * flash_db: clean up boot-source table comments. -- Vincent Jardin Sat, 12 Sep 2026 19:50:28 +0200 qoriq-rcw (1.0.1-1) unstable; urgency=medium * meson: make pandoc man-page generation optional, controlled by the new `man` option, so the tool builds where pandoc is unavailable. * README: document the release process. -- Vincent Jardin Tue, 30 Jun 2026 16:56:40 +0200 qoriq-rcw (1.0.0-1) unstable; urgency=medium * Release 1.0.0. -- Vincent Jardin Fri, 19 Jun 2026 00:00:00 +0000 qoriq-rcw (0.2.0-1) unstable; urgency=medium * Add --dump mode: read the live RCW snapshot from /dev/mem on a running QorIQ/Layerscape board and emit a recompilable .rcw file with a header documenting the detected SoC, addresses, and the sanity checks performed. * Add --dump-flash mode: read the on-flash RCW + PBI directly from the boot medium. Auto-detects the device from PORSR1.RCW_SRC at DCFG offset 0x0 and a /proc/mtd partition-name scan. Supports --slot primary|fallback|all (with separate output files for "all"), --device override, and full sanity battery (preamble, load_cmd, checksum, terminator, rcw_consistent vs live RCWSR, fallback_match across slots). * New qoriq-rcw-data binary package shipping the canonical .rcwi files vendored from nxp-qoriq/rcw. -- Vincent Jardin Mon, 13 Apr 2026 14:00:00 +0000 qoriq-rcw (0.1.0-1) unstable; urgency=medium * Initial Debian/Ubuntu packaging. -- Vincent Jardin Mon, 13 Apr 2026 12:00:00 +0000 vjardin-qoriq-rcw-f74d8cb/debian/control000066400000000000000000000035611526741135700204410ustar00rootroot00000000000000Source: qoriq-rcw Section: utils Maintainer: Vincent Jardin Build-Depends: debhelper-compat (= 13), meson, ninja-build, pkgconf, libqoriq-rcw-dev (>= 1.4.0), libcmocka-dev , pandoc Standards-Version: 4.7.3 Homepage: https://github.com/vjardin/qoriq-rcw Rules-Requires-Root: no Package: qoriq-rcw Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Recommends: qoriq-rcw-data Description: QorIQ Reset Configuration Word compiler (CLI) qoriq-rcw compiles RCW source files into PBL/RCW binary images for NXP QorIQ and Layerscape SoCs that use PBI format 2 (LS1028, LS1088, LS2088, LX2160 families) and decompiles binary images back to RCW source text. It is built on top of libqoriq-rcw. . The CLI also checks an image the way the BootROM would do before it is flashed (--check), reads the live RCW snapshot from /dev/mem on a running QorIQ/Layerscape board (--dump) or the RCW and PBI commands from its boot flash (--dump-flash), runs sanity checks, and emits a recompilable .rcw file. Install qoriq-rcw-data to use the dump modes (and decompiled output) without --rcwi/-I flags. . Run --check on an image before flashing it: an image the Service Processor's BootROM rejects could leave the board silent after a reboot. Package: qoriq-rcw-data Architecture: all Multi-Arch: foreign Depends: ${misc:Depends} Description: QorIQ Reset Configuration Word compiler (data files) Canonical .rcwi bitfield definitions for the four pbiformat=2 SoC families (LS1028A, LS1088A, LS2088A, LX2160A - the latter shared with LX2120A and LX2080A). Vendored from the upstream nxp-qoriq/rcw repository under their original 3-clause BSD licence. . Installed to /usr/share/qoriq-rcw/. With this package present, qoriq-rcw --dump auto-resolves the right .rcwi for the detected SoC, and decompiled output recompiles via #include without needing -I. vjardin-qoriq-rcw-f74d8cb/debian/copyright000066400000000000000000000067061526741135700207750ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: qoriq-rcw Upstream-Contact: Vincent Jardin Source: https://github.com/vjardin/qoriq-rcw Files: * Copyright: 2026 Free Mobile -- Vincent Jardin License: BSD-3-Clause Files: data/*.rcwi data/LICENSE.upstream Copyright: 2011-2016 Freescale Semiconductor, Inc. 2017-2020 NXP License: BSD-3-Clause-NXP Comment: Vendored from https://github.com/nxp-qoriq/rcw, unmodified apart from an SPDX header. 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. License: BSD-3-Clause-NXP 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 REGENTS 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-qoriq-rcw-f74d8cb/debian/qoriq-rcw-data.install000066400000000000000000000000651526741135700232550ustar00rootroot00000000000000usr/share/qoriq-rcw/*.rcwi usr/share/qoriq-rcw/*.rcw vjardin-qoriq-rcw-f74d8cb/debian/qoriq-rcw.install000066400000000000000000000000611526741135700223420ustar00rootroot00000000000000usr/bin/qoriq-rcw usr/share/man/man1/qoriq-rcw.1 vjardin-qoriq-rcw-f74d8cb/debian/rules000077500000000000000000000007441526741135700201160ustar00rootroot00000000000000#!/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 = disabled else TESTS = enabled endif %: dh $@ --buildsystem=meson override_dh_auto_configure: dh_auto_configure -- -Dtests=$(TESTS) # The cross-validation suite needs a clone of nxp-qoriq/rcw. override_dh_auto_test: dh_auto_test -- --suite unit vjardin-qoriq-rcw-f74d8cb/debian/source/000077500000000000000000000000001526741135700203315ustar00rootroot00000000000000vjardin-qoriq-rcw-f74d8cb/debian/source/format000066400000000000000000000000141526741135700215370ustar00rootroot000000000000003.0 (quilt) vjardin-qoriq-rcw-f74d8cb/debian/tests/000077500000000000000000000000001526741135700201735ustar00rootroot00000000000000vjardin-qoriq-rcw-f74d8cb/debian/tests/control000066400000000000000000000001171526741135700215750ustar00rootroot00000000000000Tests: roundtrip Depends: qoriq-rcw, qoriq-rcw-data Restrictions: allow-stderr vjardin-qoriq-rcw-f74d8cb/debian/tests/roundtrip000077500000000000000000000015321526741135700221500ustar00rootroot00000000000000#!/bin/sh # SPDX-License-Identifier: BSD-3-Clause # Copyright 2026 Free Mobile -- Vincent Jardin # # Compile an RCW against the installed lx2160a.rcwi, check the image, # decompile it, recompile the result, and compare the two images. set -eu cd "${AUTOPKGTEST_TMP:-$(mktemp -d)}" cat > board.rcw <<'SRC' #include SYS_PLL_RAT=14 MEM_PLL_RAT=21 SRDS_PRTCL_S1=19 BOOT_LOC=26 .pbi write 0x01e00400,0x1800d000 loadc 0x01e00000,(0xf << 23) jumpc sd,(0x8 << 23) jump done sd: blockcopy 0x08,0x0000a000,0x1800d000,0x00020000 done: .end SRC qoriq-rcw -i board.rcw -o board.bin qoriq-rcw --check -i board.bin qoriq-rcw -r -i board.bin --rcwi /usr/share/qoriq-rcw/lx2160a.rcwi -o board-r.rcw grep -q '^jumpc L0,' board-r.rcw qoriq-rcw -i board-r.rcw -o board-r.bin cmp board.bin board-r.bin echo "round trip identical: $(wc -c < board.bin) bytes" vjardin-qoriq-rcw-f74d8cb/debian/upstream/000077500000000000000000000000001526741135700206715ustar00rootroot00000000000000vjardin-qoriq-rcw-f74d8cb/debian/upstream/metadata000066400000000000000000000003431526741135700223740ustar00rootroot00000000000000Bug-Database: https://github.com/vjardin/qoriq-rcw/issues Bug-Submit: https://github.com/vjardin/qoriq-rcw/issues/new Repository: https://github.com/vjardin/qoriq-rcw.git Repository-Browse: https://github.com/vjardin/qoriq-rcw vjardin-qoriq-rcw-f74d8cb/debian/watch000066400000000000000000000003711526741135700200630ustar00rootroot00000000000000version=4 opts="searchmode=plain, \ filenamemangle=s%.*/v?@ANY_VERSION@%@PACKAGE@-$1.tar.gz%" \ https://api.github.com/repos/vjardin/qoriq-rcw/tags?per_page=50 \ https://api.github.com/repos/vjardin/qoriq-rcw/tarball/refs/tags/v?@ANY_VERSION@ vjardin-qoriq-rcw-f74d8cb/man/000077500000000000000000000000001526741135700163625ustar00rootroot00000000000000vjardin-qoriq-rcw-f74d8cb/man/qoriq-rcw.1.md000066400000000000000000000361351526741135700207770ustar00rootroot00000000000000--- title: QORIQ-RCW section: 1 header: User Commands footer: qoriq-rcw 1.2.0 date: 2026-10-25 --- # NAME qoriq-rcw - compile, decompile and check QorIQ Reset Configuration Word files # SYNOPSIS **qoriq-rcw** **-i** *input* **-o** *output* [**-I** *path*]... [**-D** *field=value*]... [**\-\-define** *name*[=*value*]]... [**\-\-no-pbl**] [**-w**] **qoriq-rcw** **-r** **-i** *binary* **-o** *source* **\-\-rcwi** *rcwi* [**-I** *path*]... [**\-\-define** *name*[=*value*]]... **qoriq-rcw** **\-\-check** **-i** *image* [**-v**] **qoriq-rcw** **\-\-dump** **-o** *source* [**\-\-rcwi** *rcwi*] [**\-\-mem** *path*] [**\-\-force**] **qoriq-rcw** **\-\-dump-flash** **-o** *source* [**\-\-slot** *which*] [**\-\-device** *path*] [**\-\-rcwi** *rcwi*] [**\-\-mem** *path*] [**\-\-force**] # DESCRIPTION **qoriq-rcw** compiles RCW source files into PBL/RCW binary images for NXP QorIQ and Layerscape SoCs that use PBI format 2 (LS1028, LS1088, LS2088, LX2160 families). In reverse mode, it decompiles a binary image back to RCW source text. With **\-\-check**, it verifies an image the way the Service Processor's BootROM should do before running it, so that a bad image is caught before it is flashed. The input *.rcw* file is first run through the embedded C preprocessor (mcpp, invoked in-process - no external toolchain required), which enables the use of **#include**, **#define**, and **#ifdef** directives in source files. The preprocessed output is then parsed and compiled into a binary image containing the PBL preamble, the 1024-bit RCW register data, PBI commands, and a CRC or Stop terminator. # OPTIONS **-i** *file* : Input filename. In forward mode, this is an *.rcw* source file. In reverse mode (**-r**) and with **\-\-check**, this is a binary image. Required. **-o** *file* : Output filename. In forward mode, a binary *.bin* file is produced. In reverse mode, RCW source text is produced. Required, except with **\-\-check**. **-r** : Reverse mode: decompile a binary image into RCW source text. Requires **\-\-rcwi**. **\-\-rcwi** *file* : Path to the *.rcwi* bitfield definition file. Required when **-r** is specified. The *.rcwi* file provides the symbol names and bit positions needed to decode the binary. **-I** *path* : Add *path* to the include search path for the C preprocessor. The current directory (**.**) is always included. May be specified multiple times. **-D** *FIELD=VALUE* : Override a bitfield value, equivalent to adding an assignment line in the source file. *FIELD* is the bitfield name (e.g. **SYS_PLL_RAT**), and *VALUE* is read like Python's **int(VALUE, 0)**: decimal, or **0x** hexadecimal, **0o** octal, **0b** binary; a decimal with a leading zero is an error. Overrides take effect after source parsing, so they supersede values set in the *.rcw* file. May be specified multiple times. **\-\-define** *NAME*[=*VALUE*] : Define the preprocessor macro *NAME* as *VALUE*, or as **1** without one, like the **-D** option of a C compiler. The macro is seen by the *.rcw* source and by the *.rcwi* file that **-r**, **\-\-dump** and **\-\-dump-flash** preprocess, so that the **#ifdef** variants of the sources (the LX2160A errata and SerDes protocol files, for instance) can be selected from the command line. May be specified multiple times. **\-\-no-pbl** : Do not generate the PBL preamble (0xAA55AA55), Load RCW command, checksum, or terminator. Only the raw 128-byte RCW register data (and any PBI commands) are emitted. **-w** : Enable warning messages. When set, duplicate bitfield assignments produce a warning on standard error. **\-\-check** : Check a PBL image as the Service Processor does, and print on standard output **RCW[PBI_LENGTH]** and **RCW[SB_EN]**, then the same sanity log that heads a **\-\-dump-flash** output: *preamble*, *load_cmd*, *checksum* (the Load RCW checksum), *terminator* (every word up to the terminator must be a command, and the command **RCW[PBI_LENGTH]** points at must be a Stop or a CRC and Stop, RM section 8.2.6), *pbi_crc* (when the terminator is a CRC and Stop) and *pbi_commands*. The walk follows the jumps as the Service Processor's BootROM should do. A Jump Conditional depends on a register read at boot, so the walk stops there: the terminator and the CRC are still checked, the commands past it are not walked, and a *jumpc* WARN says so. **-v**, **\-\-verbose** : With **\-\-check**, list every PBI command executed, terminator included, as its byte offset in the image and its *.pbi* source line, in the decompiler's spelling, so that the listing pastes back into a source file. **\-\-dump** : Read the live 128-byte RCW snapshot from the running SoC's RCWSR registers via */dev/mem* and emit a *.rcw* source file. Detects the SoC from */proc/device-tree/compatible* (or, as a fallback, by reading the SVR register at the well-known DCFG offset). The output begins with a C-comment block listing the detected SoC, the DCFG base address used, and the result of every sanity check. Requires root (for */dev/mem*). Mutually exclusive with **-r** and **-i**. PBI commands are not recoverable in this mode - only the 1024-bit RCW register state; the sanity log reports **RCW[PBI_LENGTH]**, the amount of PBI the bootrom ran, which **\-\-dump-flash** recovers. **\-\-dump-flash** : Read the RCW + PBI commands directly from the boot medium. Auto- detects the boot device from PORSR1.RCW_SRC at DCFG offset 0x0 (mirrors the *CFG_RCW_SRC[3:0]* pin straps sampled at PORESET): FlexSPI NOR/NAND -> */dev/mtd0* (or whichever MTD partition matches *rcw* / *bl2* / *nor0* / *qspi* / *flash* / *boot* in */proc/mtd*), SDHC1 -> */dev/mmcblk0*, SDHC2 -> */dev/mmcblk0boot0*, I2C EEPROM -> must pass **\-\-device**. Recovers PBI commands too, unlike **\-\-dump** which only sees post-execution RCW state. Each slot is checked as **\-\-check** does (preamble, Load RCW checksum, terminator where **RCW[PBI_LENGTH]** points, PBI CRC) and compared with the live RCW; the source is decompiled from the image alone, the flash padding after the terminator is left out. Requires root. **\-\-slot** *which* : With **\-\-dump-flash**, selects the bootrom slot to read: *primary* (offset 0 or 0x1000), *fallback* (offset 0x800000 or 0x801000 - the bootrom retry location) or *all* (both, written to separate files; see **-o**). Default: *primary*. **\-\-device** *path* : With **\-\-dump-flash**, override the auto-detected boot device. Required when **PORSR1.RCW_SRC** indicates I2C EEPROM (no Linux convention for that). Otherwise honours the auto-detection result if absent. **\-\-mem** *path* : Override */dev/mem* with an alternate path. Intended for testing against a fixture file containing a snapshot of the DCFG page (RCWSR1 at offset 0x100, SVR at offset 0xA4, PORSR1 at offset 0x0, the rest unused). **\-\-force** : With **\-\-dump** and **\-\-dump-flash**, emit the source file even if one or more sanity checks failed (e.g. */dev/mem* returned all-zero, the SVR didn't match the detected SoC, or the on-flash PBI CRC is wrong). The failures are still recorded in the output's header comment block. **-h**, **\-\-help** : Display usage information and exit. # EXIT STATUS **0** : Success. With **\-\-check**, no check failed; a WARN does not fail the image. **1** : An error occurred (preprocessing failure, parse error, I/O error, etc.), or the **\-\-check** verdict is FAIL. A diagnostic message is printed to standard error; the **\-\-check** report goes to standard output. # BINARY FORMAT The output binary has the following layout when PBL generation is enabled (the default): ``` Offset Size Content 0x000 4 Preamble: 0xAA55AA55 0x004 4 Load RCW command (0x80100000 or 0x80110000) 0x008 128 RCW register data (1024 bits) 0x088 4 Checksum (sum of all preceding 32-bit words) 0x08C var PBI commands var 4 Terminator command (Stop or CRC-and-Stop) var+4 4 Terminator value (0 or CRC-32) ``` All 32-bit words are little-endian. **RCW[PBI_LENGTH]** counts the PBI in 32-bit words, terminator included: the Service Processor reads that many words after the checksum and requires the last command among them to be the terminator. The CRC-32 uses polynomial 0x04C11DB7 with initial value 0xFFFFFFFF. # RCW SOURCE SYNTAX *NAME*[*begin*:*end*] : Bitfield definition. Declares a named field occupying bit positions *begin* through *end*. Bit ordering matters: **[6:2]** differs from **[2:6]**. *NAME*[*pos*] : Single-bit field definition at bit position *pos*. *NAME*=*value* : Assign a numeric value to a previously defined bitfield. Numbers read like Python's **int(x, 0)**: decimal, **0x**, **0o** or **0b** prefixed, underscores allowed between digits; a decimal with a leading zero is an error. **%***var*=*value* : Set a control variable. Recognized variables: **%size**, **%pbiformat**, **%classicbitnumbers**, **%littleendian**, **%nocrc**, **%loadwochecksum**. **%sysaddr** belongs to the legacy PBL header and is refused with PBI format 2, as **rcw.py** refuses it. **.pbi** / **.end** : Delimit a Pre-Boot Initialization command block. **.uboot** / **.end** : Delimit a u-boot embedded-image block. The body is an **xxd**(1) hex dump of *u-boot.bin* (8 16-bit words per line, 16 bytes per line) used for SPI/SD/NAND boot chains. Each line has the form *ADDRESS*: *WORD WORD WORD WORD WORD WORD WORD WORD*. The encoder packs the lines into PBI write blocks anchored at fixed on-chip addresses; output is always big-endian regardless of **%littleendian**. Not used by any upstream pbiformat=2 board. ## PBI Commands The following commands are available inside **.pbi** / **.end** blocks: ``` write addr,value CCSR Write, 4 bytes write.b1 addr,value CCSR Write, 1 byte awrite addr,word AltConfig Write, 4 bytes (the same as .b3) awrite.bB addr,word[,word...] AltConfig Write of 2^(B-1) bytes, B = 1..10, one operand word per 4 bytes wait count Wait, count up to 0xFFFF poll addr,mask,value Poll Short poll.long addr,mask,value Poll Long blockcopy type,src,dst,size Block Copy loadacwindow value Load Alt Config Window ccsrcopy.bN src,dest,words CCSR Write from Address; .b1, .b2 or .b4 is the width of the destination registers ccsrset.bN addr,mask CCSR Read-Modify-Write: reg |= mask ccsrclear.bN addr,mask CCSR Read-Modify-Write: reg &= ~mask ccsrreplace.bN addr,mask,data CCSR Read-Modify-Write: reg = (reg & ~mask) | (data & mask); .b1, .b2 or .b4 is the access width loadsecurityheader w0,...,w19 Load Security Header: the 20 CSF header words, or a quoted hex text file name loadboot1csfptr addr Load Boot 1 CSF Header Pointer loadc addr,mask Load Condition: *addr & mask, kept for the next jumpc jump target Jump jumpc target,condition Jump Conditional: taken when the loaded condition equals condition label: a jump target, on a line of its own ``` The width suffix of **ccsrcopy**, **ccsrset**, **ccsrclear** and **ccsrreplace** is required. A jump *target* is a label, or a byte offset counted from the start of the jump itself (a multiple of 4, at least 8). **loadcondition** and **jumpif** are accepted as aliases of **loadc** and **jumpc**. **flush** is a command of the legacy PBI formats with no PBI format 2 encoding and is refused; its equivalent is a poll with an all-zero mask, **poll** *addr*,0,0. Operands are expressions à la Python's **eval()**: numbers as above, parentheses, **+**, **-**, **\***, **&**, **|**, **<<**, **>>**, and whatever macros the preprocessor expanded. The decompiler prints the same mnemonics and width suffixes back, with labels for the jump targets, and lists in a comment the RCW bits that no field of the *.rcwi* covers. ## .uboot block input The body inside **.uboot** / **.end** is generated from a u-boot binary by: ``` xxd u-boot.bin | cut -d ' ' -f1-10 > u-boot.xxd ``` then the contents of *u-boot.xxd* are pasted between the markers. # EXAMPLES Compile a board configuration: ``` $ cd ls1088ardb $ qoriq-rcw -i FCQQQQQQQQ_PPP_H_0x1d_0x0d/rcw_1600_qspi.rcw \ -o rcw_1600_qspi.bin ``` Override the system PLL ratio (decimal or binary, as **rcw.py** reads it): ``` $ qoriq-rcw -i config/rcw_2000.rcw -o rcw.bin -D SYS_PLL_RAT=10 $ qoriq-rcw -i config/rcw_2000.rcw -o rcw.bin -D SYS_PLL_RAT=0b1010 ``` Select a variant that the sources guard with **#ifdef**: ``` $ qoriq-rcw -i rcw_2000.rcw -o rcw.bin --define RCW_ERRATA_NO_TRIM ``` Check an image before flashing it; **-v** would list the 271 commands: ``` $ qoriq-rcw --check -i rcw_2000_700_2900_18_5_2.bin rcw_2000_700_2900_18_5_2.bin: 3156 bytes RCW[PBI_LENGTH]=754 words, SB_EN=0 [PASS] preamble : 0xaa55aa55 at offset 0 [PASS] load_cmd : 0x80100000 (Load RCW with checksum) [PASS] checksum : stored 0xe317b9b3 matches computed sum [PASS] terminator : Stop at byte 3148, where RCW[PBI_LENGTH]=754 words points [PASS] pbi_crc : none (plain Stop terminator) [PASS] pbi_commands : 271 commands in 3016 bytes, terminator included verdict: OK - the Service Processor/BootROM should accept this image ``` Decompile a binary back to source: ``` $ qoriq-rcw -r -i rcw.bin --rcwi ls1088rdb.rcwi -o rcw_decoded.rcw ``` Capture the live RCW from a running QorIQ board (auto-detects SoC): ``` # qoriq-rcw --dump -o /tmp/live.rcw # head -20 /tmp/live.rcw /* * Reset Configuration Word - runtime dump * * SoC family : NXP Layerscape LX2160A * SoC compatible: fsl,lx2160a * SVR : 0x87360120 (expected 0x87360000 mask 0xffff0000) * DCFG base : 0x01e00000 * ... */ ``` If the canonical *.rcwi* files are not installed (no */usr/share/qoriq-rcw/* directory), pass **\-\-rcwi** explicitly: ``` # qoriq-rcw --dump --rcwi ./lx2160a.rcwi -o /tmp/live.rcw ``` A source file embedding a u-boot image: ``` %size=1024 %pbiformat=2 %classicbitnumbers=1 %littleendian=1 %nocrc=1 PBI_LENGTH[287:276] .uboot 00000000: 27051956 04020602 4bcc7a40 00010000 ... 00000010: 0009ab21 00000003 00000000 00000004 ... ... .end ``` Capture the RCW + PBI commands from the boot flash (recovers PBI that the runtime registers no longer hold): ``` # qoriq-rcw --dump-flash -o /tmp/flash.rcw qoriq-rcw: auto-detected boot device: /dev/mtd0 (override with --device) ``` Capture both bootrom slots (primary + fallback): ``` # qoriq-rcw --dump-flash --slot all -o /tmp/dump.rcw # ls /tmp/dump.*.rcw /tmp/dump.fallback.rcw /tmp/dump.primary.rcw ``` # SEE ALSO **libqoriq-rcw**(3), **mcpp**(1), **xxd**(1) # AUTHORS Vincent Jardin, Free Mobile. vjardin-qoriq-rcw-f74d8cb/meson.build000066400000000000000000000046531526741135700177610ustar00rootroot00000000000000project('qoriq-rcw', 'c', version : '1.2.0', license : 'BSD-3-Clause', default_options : [ 'c_std=c17', 'warning_level=3', ]) # Find libqoriq-rcw. # # If installed system-wide, pkg-config is used automatically. # For local development, point at the built library tree: # 1. Build libqoriq-rcw first: # cd ../libqoriq-rcw && meson setup build && meson compile -C build # 2. Then configure qoriq-rcw: # meson setup build -Dlibqoriq-rcw-path=../libqoriq-rcw # libqoriq_rcw_path = get_option('libqoriq-rcw-path') if libqoriq_rcw_path != '' fs = import('fs') lib_src = fs.is_absolute(libqoriq_rcw_path) \ ? libqoriq_rcw_path \ : meson.project_source_root() / libqoriq_rcw_path lib_build = lib_src / 'build' cc = meson.get_compiler('c') libqoriq_rcw_lib = cc.find_library('qoriq-rcw', dirs : [lib_build], required : true) mcpp_dep = cc.find_library('mcpp', required : true) # Use -I flag directly since meson rejects absolute include_directories() libqoriq_rcw_dep = declare_dependency( dependencies : [libqoriq_rcw_lib, mcpp_dep], compile_args : ['-I' + lib_src / 'include']) else libqoriq_rcw_dep = dependency('libqoriq-rcw', version : '>= 1.4.0', required : true) endif datadir = get_option('prefix') / get_option('datadir') / 'qoriq-rcw' cli_sources = files( 'src/flash_db.c', 'src/flash_dump.c', 'src/main.c', 'src/pbl_check.c', 'src/runtime_dump.c', 'src/sanity_log.c', 'src/soc_db.c', ) cli_args = [ '-DQORIQ_RCW_DATADIR="' + datadir + '"', '-DQORIQ_RCW_VERSION="' + meson.project_version() + '"', ] executable('qoriq-rcw', cli_sources, c_args : cli_args, dependencies : libqoriq_rcw_dep, install : true) # Optional companion data - install canonical .rcwi files into # $datadir/qoriq-rcw so `--dump` works without `--rcwi` and decompiled # output recompiles via `#include ` without `-I`. rcwi_files = files( 'data/ls1028a.rcwi', 'data/ls1088a.rcwi', 'data/ls2088a.rcwi', 'data/lx2160a.rcwi', 'data/lx2160a_regs.rcw', ) install_data(rcwi_files, install_dir : get_option('datadir') / 'qoriq-rcw') pandoc = find_program('pandoc', required : get_option('man')) if pandoc.found() man1 = custom_target('qoriq-rcw.1', input : 'man/qoriq-rcw.1.md', output : 'qoriq-rcw.1', command : [pandoc, '-s', '-t', 'man', '@INPUT@', '-o', '@OUTPUT@'], install : true, install_dir : get_option('mandir') / 'man1') endif subdir('tests') vjardin-qoriq-rcw-f74d8cb/meson_options.txt000066400000000000000000000013421526741135700212440ustar00rootroot00000000000000option('libqoriq-rcw-path', type : 'string', value : '', description : 'Path to the libqoriq-rcw source tree (used if not installed system-wide)') option('rcw-ref-dir', type : 'string', value : '', description : 'Path to the upstream NXP rcw repository (clone of git@github.com:nxp-qoriq/rcw.git). Used by the cross-validate test. Default: ../../rcw relative to qoriq-rcw/. Can also be overridden at test time via the RCW_REF_DIR environment variable.') option('man', type : 'feature', value : 'auto', description : 'Build man pages (requires pandoc). auto = build if pandoc available.') option('tests', type : 'feature', value : 'auto', description : 'Build the test suite. auto = unit tests if cmocka is available.') vjardin-qoriq-rcw-f74d8cb/src/000077500000000000000000000000001526741135700163765ustar00rootroot00000000000000vjardin-qoriq-rcw-f74d8cb/src/flash_db.c000066400000000000000000000114131526741135700203040ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include "flash_db.h" /* * Boot source table * * Slot offsets per LX2160ARM:17688-9: primary at 0 (0x1000 on SD), fallback * at 0x800000 (0x801000 on SD). eMMC follows the same convention as SD; the * boot partition is selected by EXT_CSD[PARTITION_CONFIG] and exposed by * the kernel as /dev/mmcblk0boot0. */ static const flash_info_t FLASH_TABLE[] = { { 0x8, "SDHC1 (SD card)", "sd", "/dev/mmcblk0", 0x00001000ULL, 0x00801000ULL, false }, { 0x9, "SDHC2 (eMMC boot partition)", "emmc", "/dev/mmcblk0boot0", 0x00001000ULL, 0x00801000ULL, false }, { 0xA, "I2C1 EEPROM (extended addressing)", "i2c", NULL, 0x00000000ULL, 0x00000000ULL, true }, { 0xC, "FlexSPI Serial NAND (2 KiB pages)", "flexspi", "/dev/mtd0", 0x00000000ULL, 0x00800000ULL, false }, { 0xD, "FlexSPI Serial NAND (4 KiB pages)", "flexspi", "/dev/mtd0", 0x00000000ULL, 0x00800000ULL, false }, { 0xF, "FlexSPI Serial NOR (24-bit addressing)", "flexspi", "/dev/mtd0", 0x00000000ULL, 0x00800000ULL, false }, }; #define FLASH_COUNT (sizeof(FLASH_TABLE) / sizeof(FLASH_TABLE[0])) size_t flash_db_count(void) { return FLASH_COUNT; } const flash_info_t * flash_db_at(size_t i) { return (i < FLASH_COUNT) ? &FLASH_TABLE[i] : NULL; } const flash_info_t * flash_db_find_by_rcw_src(uint32_t rcw_src) { for (size_t i = 0; i < FLASH_COUNT; i++) if (FLASH_TABLE[i].rcw_src_value == rcw_src) return &FLASH_TABLE[i]; return NULL; } /* * On LX2160 PORSR1 places RCW_SRC at bits[28:23]. The low 4 bits of * that field carry the boot-source code (per the CFG_RCW_SRC pin * straps). We shift down by 23 and mask to 6 bits, then keep only the * low 4 bits - that is what matches the table values. * * Some tools encode RCW_SRC differently per SoC (LS1028 etc.); for * pbiformat=2 */ uint32_t flash_db_porsr1_to_rcw_src(uint32_t porsr1) { return (porsr1 >> 23) & 0xFu; } /* Lowercase in place. */ static void strlower(char *s) { for (; *s; s++) *s = (char)tolower((unsigned char)*s); } /* * MTD scanning. Parses lines of /proc/mtd (or its override) of the form: * * dev: size erasesize name * mtd0: 02000000 00040000 "qspi" * mtd1: 00100000 00040000 "rcw" * * Returns malloc'd "/dev/mtd" or NULL. */ char * flash_db_resolve_device(const flash_info_t *info, char *err, size_t errlen) { if (!info) { snprintf(err, errlen, "internal: NULL flash_info"); return NULL; } if (info->device_required) { snprintf(err, errlen, "boot source %s requires --device (no Linux default)", info->rcw_src_name); return NULL; } /* Non-FlexSPI: just hand back the static default. */ if (strcmp(info->interface, "flexspi") != 0) { if (!info->default_device) { snprintf(err, errlen, "internal: NULL default_device for %s", info->rcw_src_name); return NULL; } return strdup(info->default_device); } /* FlexSPI: scan /proc/mtd for a partition with a recognisable name. */ const char *path = getenv("QORIQ_RCW_PROC_MTD"); if (!path) path = "/proc/mtd"; FILE *fp = fopen(path, "r"); if (!fp) { /* /proc/mtd missing -> fall back to default */ if (!info->default_device) { snprintf(err, errlen, "could not open %s and no default device", path); return NULL; } return strdup(info->default_device); } /* First line is the header; skip. */ char line[256]; if (!fgets(line, sizeof(line), fp)) { fclose(fp); return strdup(info->default_device); } typedef struct { int idx; char name[64]; } mtd_entry_t; mtd_entry_t entries[32]; size_t n = 0; while (n < 32 && fgets(line, sizeof(line), fp)) { int idx; char name[64] = {0}; /* %63[^"] is locale-safe and matches everything up to the closing quote */ if (sscanf(line, " mtd%d: %*x %*x \"%63[^\"]\"", &idx, name) == 2) { entries[n].idx = idx; snprintf(entries[n].name, sizeof(entries[n].name), "%s", name); strlower(entries[n].name); n++; } } fclose(fp); if (n == 0) return strdup(info->default_device); /* Priority order: most specific first. Substring match against * lowercased partition names. */ static const char *patterns[] = { "rcw", "bl2", "nor0", "qspi", "flash", "boot", NULL }; for (size_t p = 0; patterns[p]; p++) { for (size_t i = 0; i < n; i++) { if (strstr(entries[i].name, patterns[p])) { char buf[32]; snprintf(buf, sizeof(buf), "/dev/mtd%d", entries[i].idx); return strdup(buf); } } } /* No name match -> first MTD entry. */ char buf[32]; snprintf(buf, sizeof(buf), "/dev/mtd%d", entries[0].idx); return strdup(buf); } vjardin-qoriq-rcw-f74d8cb/src/flash_db.h000066400000000000000000000052171526741135700203160ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Database of boot-source / flash-device mappings for --dump-flash. * * The boot interface (FlexSPI NOR/NAND, SDHC, I2C EEPROM) is encoded * in PORSR1.RCW_SRC at DCFG offset 0x0 - it mirrors the CFG_RCW_SRC[3:0] * pin straps sampled at PORESET (LX2160ARM:38244). For each known * encoding we record the bootrom slot offsets and a sensible default * Linux device path; for FlexSPI we also scan /proc/mtd to find the * partition by name (rcw, bl2, nor0, qspi, ...). * * Per LX2160ARM:17688-9, the bootrom searches: * primary : offset 0 (or 0x1000 on SD/eMMC) * fallback : offset 0x800000 (or 0x801000 on SD/eMMC) */ #pragma once #include #include #include typedef struct { uint32_t rcw_src_value; /* PORSR1.RCW_SRC encoding (CFG_RCW_SRC pins) */ const char *rcw_src_name; /* "FlexSPI Serial NOR 24-bit" etc. */ const char *interface; /* short tag: "flexspi" | "sd" | "emmc" | "i2c" */ const char *default_device; /* "/dev/mtd0", "/dev/mmcblk0", ... or NULL */ uint64_t primary_offset; uint64_t fallback_offset; /* 0 -> no fallback (I2C EEPROM) */ bool device_required; /* true -> caller must pass --device */ } flash_info_t; /* Lookup by raw RCW_SRC value (4-bit field per CLAUDE.md). NULL if unknown. */ const flash_info_t *flash_db_find_by_rcw_src(uint32_t rcw_src); /* Iterate the table - used by tests. */ size_t flash_db_count(void); const flash_info_t *flash_db_at(size_t i); /* * Resolve the boot device for an info entry: * - flexspi : scan /proc/mtd for a partition whose name matches one of * {"rcw", "bl2", "nor0", "qspi", "flash", "boot"} * (case-insensitive substring, priority by list order). * Falls back to info->default_device if /proc/mtd has no * obvious candidate. The override path is read from the * $QORIQ_RCW_PROC_MTD environment variable so unit tests * can point at a fixture. * - sd/emmc : returns info->default_device unchanged. * - i2c : returns NULL (caller must pass --device explicitly). * * Returns a malloc()'d path the caller must free, or NULL with `err` * populated on failure. */ char *flash_db_resolve_device(const flash_info_t *info, char *err, size_t errlen); /* * Extract the RCW_SRC field from a raw PORSR1 value. The field * encoding follows CLAUDE.md: a 4-bit value (0b1xxx). The hardware * field on LX2160 sits at bits[28:23] of PORSR1, but only the low * 4 bits carry the source code; we mask accordingly. */ uint32_t flash_db_porsr1_to_rcw_src(uint32_t porsr1); vjardin-qoriq-rcw-f74d8cb/src/flash_dump.c000066400000000000000000000125501526741135700206670ustar00rootroot00000000000000/* * 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 "flash_dump.h" #include "pbl_check.h" void flash_slot_free(flash_slot_t *slot) { if (!slot) return; free(slot->data); slot->data = NULL; slot->data_len = 0; } int flash_dump_read_slot(const char *device, uint64_t offset, const char *slot_name, flash_slot_t *out, char *err, size_t errlen) { if (!device || !out) { snprintf(err, errlen, "internal: NULL argument"); return -EINVAL; } int fd = open(device, O_RDONLY); if (fd < 0) { snprintf(err, errlen, "open(%s): %s", device, strerror(errno)); return -errno; } if (lseek(fd, (off_t)offset, SEEK_SET) == (off_t)-1) { int saved = errno; close(fd); snprintf(err, errlen, "lseek(%s, 0x%llx): %s", device, (unsigned long long)offset, strerror(saved)); return -saved; } uint8_t *buf = malloc(FLASH_SLOT_READ_BYTES); if (!buf) { close(fd); snprintf(err, errlen, "out of memory"); return -ENOMEM; } /* Read up to FLASH_SLOT_READ_BYTES; short reads are normal at EOF * (e.g., MTD partition smaller than 16 KiB, fallback slot past end * of an unprogrammed device). */ size_t total = 0; while (total < FLASH_SLOT_READ_BYTES) { ssize_t n = read(fd, buf + total, FLASH_SLOT_READ_BYTES - total); if (n < 0) { if (errno == EINTR) continue; int saved = errno; free(buf); close(fd); snprintf(err, errlen, "read(%s): %s", device, strerror(saved)); return -saved; } if (n == 0) break; /* EOF */ total += (size_t)n; } close(fd); memset(out, 0, sizeof(*out)); out->name = slot_name; out->offset = offset; out->data = buf; out->data_len = total; out->pbl_len = total; /* refined by sanity_run() once the terminator is found */ err[0] = '\0'; return 0; } void flash_dump_sanity_run(flash_slot_t *slot, const uint8_t *live_rcw, sanity_log_t *log) { memset(log, 0, sizeof(*log)); if (!slot || !slot->data) { sanity_log_add(log, SANITY_FAIL, "preamble", "no slot data"); return; } /* Every pbiformat=2 .rcwi sets %littleendian=1. */ rcw_pbl_report_t rep; rcw_pbl_verify(slot->data, slot->data_len, 1, NULL, NULL, &rep); pbl_sanity_run(&rep, slot->data_len, live_rcw, log); slot->pbl_len = rep.image_end; if (rep.envelope == RCW_OK) { slot->pbi_length = rep.pbl.pbi_length; slot->sb_en = rep.pbl.sb_en != 0; slot->ncmd = rep.chk.ncmd; } } void flash_dump_compare_slots(const flash_slot_t *primary, const flash_slot_t *fallback, sanity_log_t *log) { if (!primary || !fallback || !primary->data || !fallback->data) { sanity_log_add(log, SANITY_WARN, "fallback_match", "skipped (one or both slots empty)"); return; } if (primary->pbl_len == 0 || fallback->pbl_len == 0) { sanity_log_add(log, SANITY_WARN, "fallback_match", "skipped (zero bytes to compare)"); return; } if (primary->pbl_len != fallback->pbl_len) sanity_log_add(log, SANITY_WARN, "fallback_match", "primary (%zu bytes) and fallback (%zu bytes) PBL images differ in size", primary->pbl_len, fallback->pbl_len); else if (memcmp(primary->data, fallback->data, primary->pbl_len) == 0) sanity_log_add(log, SANITY_PASS, "fallback_match", "primary and fallback PBL images are identical (%zu bytes)", primary->pbl_len); else sanity_log_add(log, SANITY_WARN, "fallback_match", "primary and fallback slots differ - could be intentional (A/B images) or accidental drift"); } int flash_dump_write_header(FILE *out, const soc_info_t *soc, uint32_t svr, uint32_t porsr1, uint32_t rcw_src, const flash_info_t *flash, const char *device, const flash_slot_t *slot, const sanity_log_t *log, const char *tool_version) { char tstamp[32] = "unknown"; time_t now = time(NULL); struct tm tmv; if (gmtime_r(&now, &tmv)) strftime(tstamp, sizeof(tstamp), "%Y-%m-%dT%H:%M:%SZ", &tmv); fprintf(out, "/*\n" " * Reset Configuration Word - flash dump (%s slot)\n" " *\n" " * SoC family : %s\n" " * SoC compatible: %s\n" " * SVR : 0x%08x\n" " * PORSR1 : 0x%08x (RCW_SRC=0b%u%u%u%u -> %s)\n" " * Boot device : %s\n" " * Slot : %s (offset 0x%08llx, %zu/%zu bytes PBL/read)\n" " * PBI : RCW[PBI_LENGTH]=%u words, %zu commands walked, SB_EN=%u\n" " *\n" " * Sanity checks:\n" , slot->name , soc->pretty_name , soc->compat , svr , porsr1 , (rcw_src >> 3) & 1, (rcw_src >> 2) & 1, (rcw_src >> 1) & 1, rcw_src & 1 , flash->rcw_src_name , device , slot->name , (unsigned long long)slot->offset , slot->pbl_len, slot->data_len , slot->pbi_length, slot->ncmd, (unsigned)slot->sb_en ); for (size_t i = 0; i < log->count; i++) { const sanity_entry_t *e = &log->entries[i]; fprintf(out, " * [%s] %-16s : %s\n", sanity_status_label(e->status), e->id, e->detail); } fprintf(out, " *\n" " * Captured: %s by qoriq-rcw %s\n" " */\n\n", tstamp, tool_version ? tool_version : "0.0.0"); return 0; } vjardin-qoriq-rcw-f74d8cb/src/flash_dump.h000066400000000000000000000071321526741135700206740ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Read RCW slots from the boot medium (FlexSPI / SD / eMMC / I2C * EEPROM) and sanity-check the on-flash PBL binary. * * Differs from runtime_dump (which reads the live RCWSR registers via * /dev/mem) by recovering BOTH the RCW and the PBI commands - PBI * runs once at boot and is not present in the runtime registers. */ #pragma once #include #include #include #include #include "flash_db.h" #include "runtime_dump.h" /* Fixed read window per slot. Covers preamble + 128B RCW + checksum + * a generous PBI block + terminator. Increase if a real-world board * ever exceeds it. */ #define FLASH_SLOT_READ_BYTES (16u * 1024u) typedef struct { const char *name; /* "primary" | "fallback" */ uint64_t offset; /* offset within the boot device */ uint8_t *data; /* malloc()'d; caller frees */ size_t data_len; /* bytes read from device (<= FLASH_SLOT_READ_BYTES) */ size_t pbl_len; /* preamble through the terminator value, as the PBI check finds it * (rcw_pbl_report_t.image_end); populated by flash_dump_sanity_run(). * Use this length (not data_len) when feeding the slot to * rcw_decompile_buffer() - it strips the 0xFF flash padding that * follows the terminator. */ /* From the envelope and the PBI check, populated by flash_dump_sanity_run(). */ uint32_t pbi_length; /* RCW[PBI_LENGTH], in 32-bit words */ bool sb_en; /* RCW[SB_EN] */ size_t ncmd; /* PBI commands walked */ } flash_slot_t; void flash_slot_free(flash_slot_t *slot); /* * Open `device`, lseek to `offset`, read up to FLASH_SLOT_READ_BYTES * bytes into a freshly-allocated buffer in `out`. Works equally well on * /dev/mtd*, /dev/mmcblk*, and a regular file (for tests). * * Returns 0 on success, -errno on failure (with `err` populated). */ int flash_dump_read_slot(const char *device, uint64_t offset, const char *slot_name, flash_slot_t *out, char *err, size_t errlen); /* * Run the sanity battery on a slot's bytes: rcw_pbl_verify() then * pbl_sanity_run() (see pbl_check.h for the entries), with `live_rcw` * compared against the on-flash RCW when non-NULL. * * Sets slot->pbl_len to the image extent and slot->pbi_length / sb_en / * ncmd from the envelope and the check. */ void flash_dump_sanity_run(flash_slot_t *slot, const uint8_t *live_rcw, /* may be NULL */ sanity_log_t *log); /* * Optional cross-slot check: append a fallback_match entry comparing * the PBL images of `primary` and `fallback` (pbl_len bytes each). * Either operand may be NULL/empty (recorded as WARN). */ void flash_dump_compare_slots(const flash_slot_t *primary, const flash_slot_t *fallback, sanity_log_t *log); /* * Write the C-comment header describing a flash slot dump (SoC, boot * source/device, slot offset/size, PBI summary, sanity log, capture * time, version). */ int flash_dump_write_header(FILE *out, const soc_info_t *soc, uint32_t svr, uint32_t porsr1, uint32_t rcw_src, const flash_info_t *flash, const char *device, const flash_slot_t *slot, const sanity_log_t *log, const char *tool_version); vjardin-qoriq-rcw-f74d8cb/src/main.c000066400000000000000000000573001526741135700174730ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * qoriq-rcw: CLI tool for compiling/decompiling RCW files. * Equivalent to rcw.py for pbiformat=2 platforms. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include "flash_db.h" #include "flash_dump.h" #include "pbl_check.h" #include "runtime_dump.h" #include "soc_db.h" #ifndef QORIQ_RCW_DATADIR # define QORIQ_RCW_DATADIR "/usr/share/qoriq-rcw" #endif #ifndef QORIQ_RCW_VERSION # define QORIQ_RCW_VERSION "0.0.0" #endif static void usage(const char *prog) { fprintf(stderr, "Usage: %s [options]\n" "\n" "Modes:\n" " (default) Compile : -i SRC.rcw -o OUT.bin\n" " -r Decompile: -r -i IN.bin --rcwi FILE.rcwi -o OUT.rcw\n" " --check Check : --check -i IMAGE.bin [-v]\n" " --dump Runtime : --dump -o OUT.rcw [--rcwi FILE.rcwi]\n" " --dump-flash Flash : --dump-flash -o OUT.rcw [--slot S] [--device PATH]\n" "\n" "Options:\n" " -i FILE Input filename (compile / decompile / check)\n" " -o FILE Output filename (required; with --slot=all the slot\n" " name is inserted before the extension: foo.rcw ->\n" " foo.primary.rcw + foo.fallback.rcw)\n" " -r Reverse: decompile binary to source\n" " --rcwi FILE RCWI definition file (required with -r;\n" " optional with --dump/--dump-flash if " QORIQ_RCW_DATADIR " holds it)\n" " -I PATH Include path (can be repeated)\n" " -D FIELD=VAL Override bitfield value (can be repeated); VAL is read as\n" " Python's int(VAL, 0): decimal, 0x..., 0o... or 0b...\n" " --define M[=V] Define preprocessor macro M (as V, or 1) for the .rcw and\n" " .rcwi files (can be repeated)\n" " --no-pbl Do not generate PBL preamble/end-command\n" " -w Enable warning messages\n" " --check Check a PBL image as the Service Processor does: Load RCW\n" " checksum, Stop/CRC and Stop at RCW[PBI_LENGTH], PBI CRC\n" " -v With --check, list every PBI command as a .pbi line\n" " --dump Read live RCWSR registers via /dev/mem and emit a .rcw\n" " --dump-flash Read RCW from the boot medium (recovers PBI commands too)\n" " --slot WHICH primary | fallback | all (default: primary)\n" " --device PATH Override boot device (auto-detected via PORSR1.RCW_SRC\n" " + /proc/mtd scan; required for I2C EEPROM)\n" " --mem PATH Override /dev/mem (for testing with a fixture file)\n" " --force Emit dump even if sanity checks fail\n" " -h, --help Show this help\n", prog); } static int file_readable(const char *p) { return access(p, R_OK) == 0; } /* Resolve a .rcwi for --dump mode: * 1. honour an explicit --rcwi if given * 2. else try $DATADIR/qoriq-rcw/default_rcwi> * 3. else NULL (caller errors out) */ static char * resolve_rcwi(const char *user_rcwi, const soc_info_t *soc, char *err, size_t errlen) { if (user_rcwi) { if (!file_readable(user_rcwi)) { snprintf(err, errlen, "rcwi not readable: %s", user_rcwi); return NULL; } return strdup(user_rcwi); } size_t need = strlen(QORIQ_RCW_DATADIR) + 1 + strlen(soc->default_rcwi) + 1; char *path = malloc(need); if (!path) { snprintf(err, errlen, "out of memory"); return NULL; } snprintf(path, need, "%s/%s", QORIQ_RCW_DATADIR, soc->default_rcwi); if (file_readable(path)) return path; snprintf(err, errlen, "no .rcwi found at %s - install qoriq-rcw-data or pass --rcwi", path); free(path); return NULL; } /* Run the --dump pipeline. Returns EXIT_SUCCESS / EXIT_FAILURE. */ static int cmd_dump(rcw_ctx_t *ctx, const char *output_path, const char *user_rcwi, const char *mem_path, int force) { if (!output_path) { warnx("--dump requires -o"); return EXIT_FAILURE; } char dt_detail[256] = {0}; const soc_info_t *soc = soc_db_detect(dt_detail, sizeof(dt_detail)); const char *dt_detail_for_log = soc ? dt_detail : NULL; /* If DT detection failed, try an SVR probe via the default DCFG * layout (all four families share 0x01E00000). */ if (!soc) { /* Pick the first table entry as a "probe template": all entries * share dcfg_base/svr_offset, so any one works for reading SVR. */ const soc_info_t *probe = soc_db_at(0); if (!probe) { warnx("internal: empty SoC database"); return EXIT_FAILURE; } uint8_t dummy_rcw[RCW_DUMP_BYTES]; uint32_t svr = 0; char rerr[256] = {0}; int rc = runtime_read_rcw(probe, mem_path, dummy_rcw, &svr, rerr, sizeof(rerr)); if (rc != 0) { warnx("SoC detection failed via DT (%s) and SVR probe (%s)", dt_detail, rerr); return EXIT_FAILURE; } soc = soc_db_find_by_svr(svr); if (!soc) { warnx("SoC detection failed: DT match: %s; SVR=0x%08x has no " "matching SoC entry", dt_detail, svr); return EXIT_FAILURE; } } /* Now read the live RCWSR + SVR for real. */ uint8_t rcw[RCW_DUMP_BYTES]; uint32_t svr = 0; char rerr[256] = {0}; int rc = runtime_read_rcw(soc, mem_path, rcw, &svr, rerr, sizeof(rerr)); if (rc != 0) errx(EXIT_FAILURE, "%s", rerr); /* Sanity. */ sanity_log_t log; runtime_sanity_run(soc, dt_detail_for_log, svr, rcw, &log); if (log.any_failed && !force) errx(EXIT_FAILURE, "sanity checks failed (use --force to emit anyway); the most useful failed check is the first FAIL above"); /* Locate .rcwi. */ char rerr2[256] = {0}; char *rcwi_path = resolve_rcwi(user_rcwi, soc, rerr2, sizeof(rerr2)); if (!rcwi_path) errx(EXIT_FAILURE, "%s", rerr2); /* * In-memory decompile path: preprocess the .rcwi via mcpp (so * macros / #include in the .rcwi expand), then feed the resulting * text together with the live 128-byte RCW buffer to * rcw_decompile_buffer(). No tempfile, no double I/O. The library * accepts a bare 128-byte buffer with no PBL preamble (see * rcw_decompile.c - len == 128 skips the preamble check). */ char *rcwi_pp = NULL; size_t rcwi_pp_len = 0; rcw_error_t rcw_err = rcw_preprocess_file(ctx, rcwi_path, &rcwi_pp, &rcwi_pp_len); if (rcw_err != RCW_OK) { char *path_copy = strdup(rcwi_path); free(rcwi_path); errx(EXIT_FAILURE, "preprocess %s: %s", path_copy ? path_copy : "(?)", rcw_strerror(rcw_err)); } /* basename for the #include line in the output. */ const char *base = strrchr(rcwi_path, '/'); base = base ? base + 1 : rcwi_path; char *source = NULL; size_t source_len = 0; rcw_err = rcw_decompile_buffer(ctx, rcwi_pp, rcwi_pp_len, rcw, sizeof(rcw), base, &source, &source_len); rcw_free(rcwi_pp); free(rcwi_path); if (rcw_err != RCW_OK) errx(EXIT_FAILURE, "decompile: %s: %s", rcw_strerror(rcw_err), rcw_ctx_last_error_detail(ctx)); /* Emit: our header + the library's source text. */ FILE *out = fopen(output_path, "w"); if (!out) { rcw_free(source); err(EXIT_FAILURE, "%s", output_path); } runtime_write_header(out, soc, svr, &log, QORIQ_RCW_VERSION); fwrite(source, 1, source_len, out); fclose(out); rcw_free(source); return EXIT_SUCCESS; } /* ------------------------------------------------------------------ */ /* --dump-flash */ /* ------------------------------------------------------------------ */ /* * Insert "." before the last "." of `base`, or append it if no * dot is present. Returns malloc'd string the caller must free. * * Examples: ("foo.rcw", "primary") -> "foo.primary.rcw" * ("foo", "primary") -> "foo.primary" * ("a/b.x", "fb") -> "a/b.fb.x" */ static char * slot_filename(const char *base, const char *slot) { /* Find last "." after the last "/" so paths like "a.b/c" work. */ const char *last_slash = strrchr(base, '/'); const char *search_from = last_slash ? last_slash + 1 : base; const char *dot = strrchr(search_from, '.'); size_t prefix_len = dot ? (size_t)(dot - base) : strlen(base); const char *suffix = dot ? dot : ""; size_t need = prefix_len + 1 + strlen(slot) + strlen(suffix) + 1; char *out = malloc(need); if (!out) return NULL; snprintf(out, need, "%.*s.%s%s", (int)prefix_len, base, slot, suffix); return out; } /* * Write one slot's output: header + (preprocessed .rcwi -> decompiled * source via rcw_decompile_buffer). Returns 0 / -1. */ static int write_slot_output(rcw_ctx_t *ctx, const char *out_path, const soc_info_t *soc, uint32_t svr, uint32_t porsr1, uint32_t rcw_src, const flash_info_t *flash, const char *device, const flash_slot_t *slot, const sanity_log_t *log, const char *rcwi_pp, size_t rcwi_pp_len, const char *rcwi_basename) { char *source = NULL; size_t source_len = 0; rcw_error_t e = rcw_decompile_buffer(ctx, rcwi_pp, rcwi_pp_len, slot->data, slot->pbl_len, rcwi_basename, &source, &source_len); if (e != RCW_OK) { warnx("decompile %s slot: %s: %s", slot->name, rcw_strerror(e), rcw_ctx_last_error_detail(ctx)); return -1; } FILE *f = fopen(out_path, "w"); if (!f) { warn("%s", out_path); rcw_free(source); return -1; } flash_dump_write_header(f, soc, svr, porsr1, rcw_src, flash, device, slot, log, QORIQ_RCW_VERSION); fwrite(source, 1, source_len, f); fclose(f); rcw_free(source); return 0; } static int cmd_dump_flash(rcw_ctx_t *ctx, const char *output_path, const char *user_rcwi, const char *mem_path, const char *user_device, const char *slot_arg, int force) { if (!output_path) errx(EXIT_FAILURE, "--dump-flash requires -o"); const char *want_slot = slot_arg ? slot_arg : "primary"; bool want_primary = (strcmp(want_slot, "primary") == 0 || strcmp(want_slot, "all") == 0); bool want_fallback = (strcmp(want_slot, "fallback") == 0 || strcmp(want_slot, "all") == 0); if (!want_primary && !want_fallback) errx(EXIT_FAILURE, "--slot must be one of: primary, fallback, all"); /* SoC detection (DT then SVR-probe). */ char dt_detail[256] = {0}; const soc_info_t *soc = soc_db_detect(dt_detail, sizeof(dt_detail)); if (!soc) { /* Probe via SVR using the first table entry as the address template. */ const soc_info_t *probe = soc_db_at(0); uint32_t svr_probe = 0; char rerr[256] = {0}; uint8_t dummy_rcw[RCW_DUMP_BYTES]; if (runtime_read_rcw(probe, mem_path, dummy_rcw, &svr_probe, rerr, sizeof(rerr)) != 0) errx(EXIT_FAILURE, "SoC detection failed: DT (%s) and SVR probe (%s)", dt_detail, rerr); soc = soc_db_find_by_svr(svr_probe); if (!soc) errx(EXIT_FAILURE, "SoC detection failed: SVR=0x%08x has no entry", svr_probe); } /* Read PORSR1 + RCW + SVR (RCW used for rcw_consistent check). */ uint32_t porsr1 = 0, svr = 0, rcw_src = 0; uint8_t live_rcw[RCW_DUMP_BYTES]; char rerr[256] = {0}; if (runtime_read_porsr1(soc, mem_path, &porsr1, rerr, sizeof(rerr)) != 0) errx(EXIT_FAILURE, "%s", rerr); if (runtime_read_rcw(soc, mem_path, live_rcw, &svr, rerr, sizeof(rerr)) != 0) errx(EXIT_FAILURE, "%s", rerr); rcw_src = flash_db_porsr1_to_rcw_src(porsr1); const flash_info_t *flash = flash_db_find_by_rcw_src(rcw_src); if (!flash) errx(EXIT_FAILURE, "PORSR1=0x%08x -> RCW_SRC=0b%u%u%u%u not in known boot-source table", porsr1, (rcw_src>>3)&1,(rcw_src>>2)&1,(rcw_src>>1)&1,rcw_src&1); /* Resolve boot device. */ char *device = NULL; if (user_device) { device = strdup(user_device); } else { char derr[256] = {0}; device = flash_db_resolve_device(flash, derr, sizeof(derr)); if (!device) errx(EXIT_FAILURE, "%s", derr); warnx("auto-detected boot device: %s (override with --device)", device); } /* Resolve .rcwi (shared with --dump). */ char rerr2[256] = {0}; char *rcwi_path = resolve_rcwi(user_rcwi, soc, rerr2, sizeof(rerr2)); if (!rcwi_path) { free(device); errx(EXIT_FAILURE, "%s", rerr2); } const char *rcwi_basename = strrchr(rcwi_path, '/'); rcwi_basename = rcwi_basename ? rcwi_basename + 1 : rcwi_path; /* Preprocess .rcwi once via mcpp; reused for both slots. */ char *rcwi_pp = NULL; size_t rcwi_pp_len = 0; rcw_error_t pe = rcw_preprocess_file(ctx, rcwi_path, &rcwi_pp, &rcwi_pp_len); if (pe != RCW_OK) { /* Don't free rcwi_path before errx - keep the pointer valid for * the diagnostic. The process exits immediately after. */ free(device); errx(EXIT_FAILURE, "preprocess %s: %s", rcwi_path, rcw_strerror(pe)); } /* Read the requested slot(s). */ flash_slot_t primary = {0}, fallback = {0}; sanity_log_t plog = {0}, flog = {0}; if (want_primary) { if (flash_dump_read_slot(device, flash->primary_offset, "primary", &primary, rerr, sizeof(rerr)) != 0) errx(EXIT_FAILURE, "%s", rerr); flash_dump_sanity_run(&primary, live_rcw, &plog); } if (want_fallback) { if (flash->fallback_offset == 0) { warnx("boot source %s has no fallback slot - skipping", flash->rcw_src_name); want_fallback = false; } else if (flash_dump_read_slot(device, flash->fallback_offset, "fallback", &fallback, rerr, sizeof(rerr)) != 0) { warnx("could not read fallback: %s", rerr); want_fallback = false; } else { flash_dump_sanity_run(&fallback, live_rcw, &flog); } } /* If both, also append fallback_match to whichever log gets written. */ if (want_primary && want_fallback) flash_dump_compare_slots(&primary, &fallback, &flog); /* Honour --force. Both slots collapse into one decision. */ bool any_failed = (want_primary && plog.any_failed) || (want_fallback && flog.any_failed); if (any_failed && !force) { flash_slot_free(&primary); flash_slot_free(&fallback); rcw_free(rcwi_pp); free(rcwi_path); free(device); errx(EXIT_FAILURE, "sanity checks failed (use --force to emit anyway)"); } int rc = 0; bool slot_all = (strcmp(want_slot, "all") == 0); if (want_primary) { char *path = slot_all ? slot_filename(output_path, "primary") : strdup(output_path); if (!path) rc = -1; else if (write_slot_output(ctx, path, soc, svr, porsr1, rcw_src, flash, device, &primary, &plog, rcwi_pp, rcwi_pp_len, rcwi_basename) != 0) rc = -1; free(path); } if (want_fallback) { char *path = slot_all ? slot_filename(output_path, "fallback") : strdup(output_path); if (!path) rc = -1; else if (write_slot_output(ctx, path, soc, svr, porsr1, rcw_src, flash, device, &fallback, &flog, rcwi_pp, rcwi_pp_len, rcwi_basename) != 0) rc = -1; free(path); } flash_slot_free(&primary); flash_slot_free(&fallback); rcw_free(rcwi_pp); free(rcwi_path); free(device); return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } /* Read a whole file; works on pipes too. NULL with errno set on failure. */ static uint8_t * read_file(const char *path, size_t *len_out) { FILE *f = fopen(path, "rb"); if (!f) return NULL; size_t cap = 4096, len = 0; uint8_t *buf = malloc(cap); while (buf) { len += fread(buf + len, 1, cap - len, f); if (len < cap) break; /* EOF or error */ uint8_t *grown = realloc(buf, cap * 2); if (!grown) { free(buf); buf = NULL; break; } buf = grown; cap *= 2; } if (!buf) { fclose(f); errno = ENOMEM; return NULL; } if (ferror(f)) { int saved = errno; fclose(f); free(buf); errno = saved; return NULL; } fclose(f); *len_out = len; return buf; } static void check_visit(const rcw_pbi_cmd_t *cmd, size_t off, void *arg) { pbl_print_cmd(arg, cmd, RCW_PBL_PBI_OFFSET + off); } static int cmd_check(const char *input, int verbose) { size_t len = 0; uint8_t *buf = read_file(input, &len); if (!buf) err(EXIT_FAILURE, "%s", input); printf("%s: %zu bytes\n", input, len); if (verbose) printf(" PBI commands (byte offset in the image, .pbi source line):\n"); /* Every pbiformat=2 .rcwi sets %littleendian=1. */ rcw_pbl_report_t rep; rcw_pbl_verify(buf, len, 1, verbose ? check_visit : NULL, stdout, &rep); free(buf); if (rep.envelope == RCW_OK) printf(" RCW[PBI_LENGTH]=%u words, SB_EN=%d\n", rep.pbl.pbi_length, rep.pbl.sb_en); sanity_log_t log = {0}; pbl_sanity_run(&rep, len, NULL, &log); for (size_t i = 0; i < log.count; i++) { const sanity_entry_t *e = &log.entries[i]; printf(" [%s] %-14s : %s\n", sanity_status_label(e->status), e->id, e->detail); } if (len > rep.image_end) printf(" %zu bytes follow the image\n", len - rep.image_end); /* A Jump Conditional leaves the path to the hardware: the checks made before execution decide. */ bool accepted = rep.ok || (rep.pbi == RCW_ERR_PBI_CONDITIONAL && rep.checksum_ok && rep.crc_ok); printf(" verdict: %s\n", accepted ? "OK - the Service Processor/BootROM should accept this image" : "FAIL"); return accepted ? EXIT_SUCCESS : EXIT_FAILURE; } enum { OPT_DUMP = 1000, OPT_MEM, OPT_FORCE, OPT_DUMP_FLASH, OPT_SLOT, OPT_DEVICE, OPT_CHECK, OPT_DEFINE, }; int main(int argc, char **argv) { const char *input = NULL; const char *output = NULL; const char *rcwi = NULL; const char *mem_path = "/dev/mem"; const char *device = NULL; const char *slot = NULL; int reverse = 0; int check = 0; int verbose = 0; int dump = 0; int dump_flash = 0; int force = 0; int no_pbl = 0; int warnings = 0; /* Collect -D, -I and --define options */ const char *includes[32]; int nincludes = 0; const char *defines[64]; int ndefines = 0; const char *macros[64]; int nmacros = 0; static struct option long_opts[] = { {"rcwi", required_argument, NULL, 'R'}, {"no-pbl", no_argument, NULL, 'P'}, {"define", required_argument, NULL, OPT_DEFINE}, {"verbose", no_argument, NULL, 'v'}, {"help", no_argument, NULL, 'h'}, {"check", no_argument, NULL, OPT_CHECK}, {"dump", no_argument, NULL, OPT_DUMP}, {"dump-flash", no_argument, NULL, OPT_DUMP_FLASH}, {"slot", required_argument, NULL, OPT_SLOT}, {"device", required_argument, NULL, OPT_DEVICE}, {"mem", required_argument, NULL, OPT_MEM}, {"force", no_argument, NULL, OPT_FORCE}, {NULL, 0, NULL, 0}, }; int opt; while ((opt = getopt_long(argc, argv, "i:o:rI:D:wvh", long_opts, NULL)) != -1) { switch (opt) { case 'i': input = optarg; break; case 'o': output = optarg; break; case 'r': reverse = 1; break; case 'R': rcwi = optarg; break; case 'I': if (nincludes < 32) includes[nincludes++] = optarg; break; case 'D': if (ndefines < 64) defines[ndefines++] = optarg; break; case OPT_DEFINE: if (nmacros < 64) macros[nmacros++] = optarg; break; case 'P': no_pbl = 1; break; case 'w': warnings = 1; break; case 'v': verbose = 1; break; case OPT_CHECK: check = 1; break; case OPT_DUMP: dump = 1; break; case OPT_DUMP_FLASH: dump_flash = 1; break; case OPT_SLOT: slot = optarg; break; case OPT_DEVICE: device = optarg; break; case OPT_MEM: mem_path = optarg; break; case OPT_FORCE: force = 1; break; case 'h': usage(argv[0]); return EXIT_SUCCESS; default: usage(argv[0]); return EXIT_FAILURE; } } /* Mode validation. */ int mode_count = (dump != 0) + (dump_flash != 0) + (reverse != 0) + (check != 0); if (mode_count > 1) errx(EXIT_FAILURE, "--dump, --dump-flash, --check and -r are mutually exclusive"); if ((dump || dump_flash) && input) errx(EXIT_FAILURE, "--dump/--dump-flash do not take -i"); if (check) { if (!input) errx(EXIT_FAILURE, "--check requires -i"); } else if (!dump && !dump_flash) { if (!input) { warnx("-i is required"); usage(argv[0]); return EXIT_FAILURE; } if (!output) { warnx("-o is required"); usage(argv[0]); return EXIT_FAILURE; } if (reverse && !rcwi) { warnx("-r requires --rcwi"); return EXIT_FAILURE; } } rcw_ctx_t *ctx = rcw_ctx_new(); if (!ctx) errx(EXIT_FAILURE, "out of memory"); if (no_pbl) rcw_ctx_set_pbl(ctx, 0); if (warnings) rcw_ctx_set_warnings(ctx, 1); for (int i = 0; i < nincludes; i++) rcw_ctx_add_include_path(ctx, includes[i]); /* Always add the data dir to the search path so #include * lines emitted by --dump or by the decompiler resolve when the * qoriq-rcw-data package is installed. Cheap if the dir is absent. */ rcw_ctx_add_include_path(ctx, QORIQ_RCW_DATADIR); /* --define: macros for mcpp, seen by the .rcw and by the .rcwi that * the decompile and dump modes preprocess alike. */ for (int i = 0; i < nmacros; i++) { rcw_error_t rcw_err = rcw_ctx_add_define(ctx, macros[i], NULL); if (rcw_err != RCW_OK) { rcw_ctx_free(ctx); errx(EXIT_FAILURE, "--define %s: %s", macros[i], rcw_err == RCW_ERR_PARSE ? "want NAME or NAME=VALUE" : rcw_err == RCW_ERR_NOMEM ? "too many macros" : rcw_strerror(rcw_err)); } } /* -D FIELD=VALUE: the bitfield override */ for (int i = 0; i < ndefines; i++) { rcw_error_t rcw_err = rcw_ctx_set_bitfield_text(ctx, defines[i]); if (rcw_err != RCW_OK) { char why[256]; snprintf(why, sizeof(why), "%s", rcw_err == RCW_ERR_PARSE ? rcw_ctx_last_error_detail(ctx) : rcw_err == RCW_ERR_NOMEM ? "too many overrides" : rcw_strerror(rcw_err)); rcw_ctx_free(ctx); errx(EXIT_FAILURE, "-D: %s (NAME=VALUE, VALUE as Python's int(x, 0): decimal, 0x, 0o or 0b)", why); } } int status = EXIT_SUCCESS; if (check) { status = cmd_check(input, verbose); } else if (dump) { status = cmd_dump(ctx, output, rcwi, mem_path, force); } else if (dump_flash) { status = cmd_dump_flash(ctx, output, rcwi, mem_path, device, slot, force); } else if (reverse) { char *source = NULL; size_t source_len = 0; rcw_error_t rcw_err = rcw_decompile_file(ctx, input, rcwi, &source, &source_len); if (rcw_err != RCW_OK) { rcw_ctx_free(ctx); errx(EXIT_FAILURE, "%s: %s", rcw_strerror(rcw_err), rcw_ctx_last_error_detail(ctx)); } FILE *f = fopen(output, "w"); if (!f) { rcw_free(source); rcw_ctx_free(ctx); err(EXIT_FAILURE, "%s", output); } fwrite(source, 1, source_len, f); fclose(f); rcw_free(source); } else { uint8_t *binary = NULL; size_t binary_len = 0; rcw_error_t rcw_err = rcw_compile_file(ctx, input, &binary, &binary_len); if (rcw_err != RCW_OK) { rcw_ctx_free(ctx); errx(EXIT_FAILURE, "%s: %s", rcw_strerror(rcw_err), rcw_ctx_last_error_detail(ctx)); } FILE *f = fopen(output, "wb"); if (!f) { rcw_free(binary); rcw_ctx_free(ctx); err(EXIT_FAILURE, "%s", output); } fwrite(binary, 1, binary_len, f); fclose(f); rcw_free(binary); } rcw_ctx_free(ctx); return status; } vjardin-qoriq-rcw-f74d8cb/src/pbl_check.c000066400000000000000000000132711526741135700204600ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #define _POSIX_C_SOURCE 200809L #include #include #include "pbl_check.h" void pbl_sanity_run(const rcw_pbl_report_t *rep, size_t len, const uint8_t *live_rcw, sanity_log_t *log) { /* preamble, load_cmd */ if (rep->envelope == RCW_ERR_TRUNCATED) { sanity_log_add(log, SANITY_FAIL, "preamble", "image too short (%zu bytes) - a PBL image is at least %u", len, RCW_PBL_PBI_OFFSET); return; } if (!rep->preamble_ok) { sanity_log_add(log, SANITY_FAIL, "preamble", "expected 0x%08x at offset 0, got 0x%08x", RCW_PBL_PREAMBLE, rep->word0); return; } sanity_log_add(log, SANITY_PASS, "preamble", "0x%08x at offset 0", rep->word0); if (!rep->load_rcw_ok || rep->envelope != RCW_OK) { sanity_log_add(log, SANITY_FAIL, "load_cmd", "expected 0x%08x or 0x%08x at offset 4, got 0x%08x", RCW_PBL_CMD_LOAD_RCW, RCW_PBL_CMD_LOAD_RCW_NOCS, rep->word1); return; } const rcw_pbl_t *pbl = &rep->pbl; const rcw_pbi_check_t *chk = &rep->chk; sanity_log_add(log, SANITY_PASS, "load_cmd", "0x%08x (Load RCW %s checksum)", pbl->load_rcw_cmd, rep->has_checksum ? "with" : "without"); /* checksum */ if (!rep->has_checksum) sanity_log_add(log, SANITY_PASS, "checksum", "skipped (Load RCW without checksum)"); else if (rep->checksum_ok) sanity_log_add(log, SANITY_PASS, "checksum", "stored 0x%08x matches computed sum", pbl->checksum); else sanity_log_add(log, SANITY_FAIL, "checksum", "stored 0x%08x != computed 0x%08x", pbl->checksum, pbl->checksum_computed); /* terminator - the Boot 0 Reset Handler's PBI check */ bool checked = rep->pbi == RCW_OK || rep->pbi == RCW_ERR_PBI_CONDITIONAL; switch (rep->pbi) { case RCW_OK: case RCW_ERR_PBI_CONDITIONAL: if (chk->len == 0) sanity_log_add(log, SANITY_WARN, "terminator", "RCW[PBI_LENGTH]=0 - no PBI, the bootrom runs nothing after the RCW"); else sanity_log_add(log, SANITY_PASS, "terminator", "%s at byte %zu, where RCW[PBI_LENGTH]=%u words points", pbl_op_label(chk->stop_op), RCW_PBL_PBI_OFFSET + chk->len - RCW_PBL_TERMINATOR_BYTES, pbl->pbi_length); break; case RCW_ERR_PBI_LENGTH: sanity_log_add(log, SANITY_FAIL, "terminator", "RCW[PBI_LENGTH]=%u words does not point at a Stop/CRC and Stop (walk ended at byte %zu after %zu commands)", pbl->pbi_length, RCW_PBL_PBI_OFFSET + chk->end, chk->ncmd); break; case RCW_ERR_PBI_OVERRUN: sanity_log_add(log, SANITY_FAIL, "terminator", "%s at byte %zu runs past RCW[PBI_LENGTH]=%u words", pbl_op_label(chk->cmd.op), RCW_PBL_PBI_OFFSET + chk->end, pbl->pbi_length); break; case RCW_ERR_PBI_JUMP: sanity_log_add(log, SANITY_FAIL, "terminator", "%s at byte %zu: offset 0x%08x is not a multiple of 4 above 8, or lands at or past RCW[PBI_LENGTH]=%u words", pbl_op_label(chk->cmd.op), RCW_PBL_PBI_OFFSET + chk->end, chk->cmd.args[0], pbl->pbi_length); break; case RCW_ERR_PBI_NO_CONDITION: sanity_log_add(log, SANITY_FAIL, "terminator", "Jump Conditional at byte %zu before any Load Condition", RCW_PBL_PBI_OFFSET + chk->end); break; case RCW_ERR_BAD_BINARY: sanity_log_add(log, SANITY_FAIL, "terminator", "word 0x%08x at byte %zu is not a PBI command", chk->cmd.word, RCW_PBL_PBI_OFFSET + chk->end); break; case RCW_ERR_TRUNCATED: sanity_log_add(log, SANITY_WARN, "terminator", "RCW[PBI_LENGTH]=%u words (%zu bytes) runs past the %zu bytes available - truncated image or read window", pbl->pbi_length, chk->len, pbl->pbi_avail); break; default: sanity_log_add(log, SANITY_FAIL, "terminator", "PBI check: %s", rcw_strerror(rep->pbi)); break; } /* jumpc - the path the image leaves to the hardware */ if (rep->pbi == RCW_ERR_PBI_CONDITIONAL) sanity_log_add(log, SANITY_WARN, "jumpc", "Jump Conditional at byte %zu: the path taken depends on a register read at boot, the commands after it are not walked", RCW_PBL_PBI_OFFSET + chk->end); /* pbi_crc */ if (checked && chk->len > 0) { if (chk->stop_op != RCW_PBI_CRC_STOP) sanity_log_add(log, SANITY_PASS, "pbi_crc", "none (plain Stop terminator)"); else if (rep->crc_ok) sanity_log_add(log, SANITY_PASS, "pbi_crc", "stored 0x%08x matches computed CRC-32", chk->crc); else sanity_log_add(log, SANITY_FAIL, "pbi_crc", "stored 0x%08x != computed 0x%08x", chk->crc, chk->crc_computed); } /* rcw_consistent (optional) */ if (live_rcw) { if (memcmp(pbl->rcw, live_rcw, RCW_PBL_RCW_BYTES) == 0) sanity_log_add(log, SANITY_PASS, "rcw_consistent", "on-flash RCW matches live RCWSR snapshot"); else sanity_log_add(log, SANITY_WARN, "rcw_consistent", "on-flash RCW differs from live RCWSR - perhaps reading the fallback slot, or flash was rewritten since boot"); } /* pbi_commands - informational */ if (rep->pbi == RCW_ERR_PBI_CONDITIONAL) sanity_log_add(log, SANITY_PASS, "pbi_commands", "%zu commands walked, up to the Jump Conditional at byte %zu", chk->ncmd, RCW_PBL_PBI_OFFSET + chk->end); else sanity_log_add(log, SANITY_PASS, "pbi_commands", "%zu commands in %zu bytes, terminator included", chk->ncmd, chk->end); } const char * pbl_op_label(rcw_pbi_op_t op) { switch (op) { case RCW_PBI_STOP: return "Stop"; case RCW_PBI_CRC_STOP: return "CRC and Stop"; default: return rcw_pbi_op_name(op); } } void pbl_print_cmd(FILE *out, const rcw_pbi_cmd_t *cmd, size_t off) { char line[RCW_PBI_SOURCE_MAX]; rcw_pbi_to_source(cmd, line, sizeof(line)); fprintf(out, "%6zu %s\n", off, line); } vjardin-qoriq-rcw-f74d8cb/src/pbl_check.h000066400000000000000000000010011526741135700204510ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * The sanity log of a PBL image, from the verdict of libqoriq-rcw */ #pragma once #include #include #include #include #include "sanity_log.h" void pbl_sanity_run(const rcw_pbl_report_t *rep, size_t len, const uint8_t *live_rcw, sanity_log_t *log); const char *pbl_op_label(rcw_pbi_op_t op); void pbl_print_cmd(FILE *out, const rcw_pbi_cmd_t *cmd, size_t off); vjardin-qoriq-rcw-f74d8cb/src/runtime_dump.c000066400000000000000000000167611526741135700212650ustar00rootroot00000000000000/* * 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 #include #include "runtime_dump.h" _Static_assert(RCW_DUMP_BYTES == RCW_PBL_RCW_BYTES, "RCWSR snapshot is the PBL image's RCW"); int runtime_read_rcw(const soc_info_t *soc, const char *mem_path, uint8_t rcw_out[RCW_DUMP_BYTES], uint32_t *svr_out, char *err, size_t errlen) { if (!soc || !mem_path || !rcw_out || !svr_out) { snprintf(err, errlen, "internal: NULL argument"); return -EINVAL; } int fd = open(mem_path, O_RDONLY | O_SYNC); if (fd < 0) { snprintf(err, errlen, "open(%s): %s", mem_path, strerror(errno)); return -errno; } long page = sysconf(_SC_PAGESIZE); if (page <= 0) page = 4096; /* * Map exactly one page. RCWSR1..32 (128 bytes starting at offset * 0x100) plus SVR (offset 0xA4) all fit comfortably in the first * page of DCFG. * * For /dev/mem (a character device) we offset the mmap by * `soc->dcfg_base` to land on the DCFG block. For a regular file * (test fixture), the DCFG page IS the file: mmap from offset 0. */ off_t map_off = (off_t)soc->dcfg_base; struct stat st; if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) map_off = 0; void *m = mmap(NULL, (size_t)page, PROT_READ, MAP_SHARED, fd, map_off); if (m == MAP_FAILED) { int saved = errno; close(fd); snprintf(err, errlen, "mmap(%s @ 0x%llx): %s", mem_path, (unsigned long long)soc->dcfg_base, strerror(saved)); return -saved; } if (soc->rcwsr1_offset + soc->rcw_bytes > (uint32_t)page || soc->svr_offset + 4 > (uint32_t)page) { munmap(m, (size_t)page); close(fd); snprintf(err, errlen, "internal: page size (%ld) too small for SoC layout", page); return -EINVAL; } /* * Read SVR as a 32-bit native-endian load, then memcpy the 128-byte * RCW snapshot. The CCSR space on Layerscape ARM is little-endian * by default, which matches the on-disk RCW binary layout (every * pbiformat=2 .rcwi sets %littleendian=1). */ const volatile uint32_t *svr_ptr = (const volatile uint32_t *)((const uint8_t *)m + soc->svr_offset); *svr_out = *svr_ptr; memcpy(rcw_out, (const uint8_t *)m + soc->rcwsr1_offset, soc->rcw_bytes); munmap(m, (size_t)page); close(fd); err[0] = '\0'; return 0; } int runtime_read_porsr1(const soc_info_t *soc, const char *mem_path, uint32_t *porsr1_out, char *err, size_t errlen) { if (!soc || !mem_path || !porsr1_out) { snprintf(err, errlen, "internal: NULL argument"); return -EINVAL; } int fd = open(mem_path, O_RDONLY | O_SYNC); if (fd < 0) { snprintf(err, errlen, "open(%s): %s", mem_path, strerror(errno)); return -errno; } long page = sysconf(_SC_PAGESIZE); if (page <= 0) page = 4096; off_t map_off = (off_t)soc->dcfg_base; struct stat st; if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) map_off = 0; void *m = mmap(NULL, (size_t)page, PROT_READ, MAP_SHARED, fd, map_off); if (m == MAP_FAILED) { int saved = errno; close(fd); snprintf(err, errlen, "mmap(%s @ 0x%llx): %s", mem_path, (unsigned long long)soc->dcfg_base, strerror(saved)); return -saved; } /* PORSR1 is at DCFG offset 0x0 (LX2160ARM:38241). */ const volatile uint32_t *p = (const volatile uint32_t *)m; *porsr1_out = *p; munmap(m, (size_t)page); close(fd); err[0] = '\0'; return 0; } void runtime_sanity_run(const soc_info_t *soc, const char *dt_detail, uint32_t svr, const uint8_t rcw[RCW_DUMP_BYTES], sanity_log_t *log) { memset(log, 0, sizeof(*log)); /* dt_compat */ if (dt_detail && dt_detail[0]) sanity_log_add(log,SANITY_PASS, "dt_compat", "matched %s", soc->compat); else sanity_log_add(log,SANITY_WARN, "dt_compat", "DT compatible unavailable; SoC identified via SVR probe"); /* svr_match */ if ((svr & soc->svr_mask) == soc->svr_match) sanity_log_add(log,SANITY_PASS, "svr_match", "0x%08x matches expected 0x%08x/0x%08x for %s", svr, soc->svr_match, soc->svr_mask, soc->compat); else sanity_log_add(log,SANITY_FAIL, "svr_match", "0x%08x does not match expected 0x%08x/0x%08x for %s", svr, soc->svr_match, soc->svr_mask, soc->compat); /* not_zero */ size_t nonzero = 0; for (size_t i = 0; i < RCW_DUMP_BYTES; i++) if (rcw[i] != 0) nonzero++; if (nonzero > 0) sanity_log_add(log,SANITY_PASS, "not_zero", "%zu/%u bytes nonzero", nonzero, RCW_DUMP_BYTES); else sanity_log_add(log,SANITY_FAIL, "not_zero", "RCW dump is all-zero (likely /dev/mem restriction)"); /* not_ones */ size_t ones_bytes = 0; for (size_t i = 0; i < RCW_DUMP_BYTES; i++) if (rcw[i] == 0xFF) ones_bytes++; if (ones_bytes < RCW_DUMP_BYTES) sanity_log_add(log,SANITY_PASS, "not_ones", "not stuck-high (%zu/%u bytes are 0xFF)", ones_bytes, RCW_DUMP_BYTES); else sanity_log_add(log,SANITY_FAIL, "not_ones", "RCW dump is all-0xFF (likely unmapped region)"); /* endianness - the SVR readback gives us a sanity check on byte * order. If the upper 16 bits of the LE-interpreted value match the * SoC family pattern, we're good. If they match when byte-swapped, * warn loudly (data would need swapping). */ if ((svr & soc->svr_mask) == soc->svr_match) { sanity_log_add(log,SANITY_PASS, "endianness", "SVR readback is little-endian (matches %s)", soc->compat); } else { uint32_t swapped = ((svr & 0xFF000000u) >> 24) | ((svr & 0x00FF0000u) >> 8) | ((svr & 0x0000FF00u) << 8) | ((svr & 0x000000FFu) << 24) ; if ((swapped & soc->svr_mask) == soc->svr_match) sanity_log_add(log,SANITY_WARN, "endianness", "SVR matches when byte-swapped (0x%08x); RCW data may need swapping before decompile", swapped); else sanity_log_add(log,SANITY_WARN, "endianness", "SVR 0x%08x doesn't match either endianness - proceed with caution", svr); } /* pbi_not_recoverable - informational, always WARN; RCW[PBI_LENGTH] * says how much PBI the bootrom ran */ rcw_pbl_t pbl; rcw_pbl_from_rcw(rcw, &pbl); sanity_log_add(log, SANITY_WARN, "pbi_not_recoverable", "RCW[PBI_LENGTH]=%u words of PBI ran once at boot and are not in the registers - use --dump-flash", pbl.pbi_length); } int runtime_write_header(FILE *out, const soc_info_t *soc, uint32_t svr, const sanity_log_t *log, const char *tool_version) { char tstamp[32] = "unknown"; time_t now = time(NULL); struct tm tmv; if (gmtime_r(&now, &tmv)) strftime(tstamp, sizeof(tstamp), "%Y-%m-%dT%H:%M:%SZ", &tmv); fprintf(out, "/*\n" " * Reset Configuration Word - runtime dump\n" " *\n" " * SoC family : %s\n" " * SoC compatible: %s\n" " * SVR : 0x%08x (expected 0x%08x mask 0x%08x)\n" " * DCFG base : 0x%08llx\n" " * RCWSR1 offset : 0x%03x\n" " * SVR offset : 0x%03x\n" " * RCW size : %zu bytes (1024 bits)\n" " *\n" " * Sanity checks:\n" , soc->pretty_name , soc->compat , svr, soc->svr_match, soc->svr_mask , (unsigned long long)soc->dcfg_base , soc->rcwsr1_offset , soc->svr_offset , soc->rcw_bytes ); for (size_t i = 0; i < log->count; i++) { const sanity_entry_t *e = &log->entries[i]; fprintf(out, " * [%s] %-22s : %s\n", sanity_status_label(e->status), e->id, e->detail); } fprintf(out, " *\n" " * Captured: %s by qoriq-rcw %s\n" " */\n\n", tstamp, tool_version ? tool_version : "0.0.0"); return 0; } vjardin-qoriq-rcw-f74d8cb/src/runtime_dump.h000066400000000000000000000042241526741135700212610ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Read the live RCWSR registers via /dev/mem and sanity-check the * resulting 128-byte dump. * * Everything platform-specific is contained in this module. The library * itself is unchanged: the dump is fed to rcw_decompile_buffer(). */ #ifndef QORIQ_RCW_RUNTIME_DUMP_H #define QORIQ_RCW_RUNTIME_DUMP_H #include #include #include #include #include "sanity_log.h" #include "soc_db.h" /* RCW_BYTES is fixed at 128 for pbiformat=2; soc_info_t carries the * number too in case future entries deviate. */ #define RCW_DUMP_BYTES 128u /* * Open `mem_path` (typically "/dev/mem"), mmap one page at * `soc->dcfg_base`, copy 128 bytes from offset `soc->rcwsr1_offset` * into `rcw_out`, copy the 32-bit SVR at offset `soc->svr_offset` * into `*svr_out`. Returns 0 on success or -errno on failure (with a * human-readable message in `err`). */ int runtime_read_rcw(const soc_info_t *soc, const char *mem_path, uint8_t rcw_out[RCW_DUMP_BYTES], uint32_t *svr_out, char *err, size_t errlen); /* * Read PORSR1 (DCFG offset 0x0) - captures the CFG_RCW_SRC pin straps * sampled at PORESET. Used by --dump-flash to learn which boot medium * the bootrom loaded the RCW from. */ int runtime_read_porsr1(const soc_info_t *soc, const char *mem_path, uint32_t *porsr1_out, char *err, size_t errlen); /* * Run the sanity-check battery. Always populates `log` with one entry * per check; never returns an error. Caller decides what to do with * `log->any_failed`. * * If `dt_detail` is NULL, the dt_compat check is recorded as WARN * ("DT compatible unavailable; SoC identified via SVR probe"). */ void runtime_sanity_run(const soc_info_t *soc, const char *dt_detail, uint32_t svr, const uint8_t rcw[RCW_DUMP_BYTES], sanity_log_t *log); /* * Write the C-comment header describing the dump (SoC, addresses, * sanity log, capture time, tool version) to `out`. Returns 0 on * success. */ int runtime_write_header(FILE *out, const soc_info_t *soc, uint32_t svr, const sanity_log_t *log, const char *tool_version); #endif /* QORIQ_RCW_RUNTIME_DUMP_H */ vjardin-qoriq-rcw-f74d8cb/src/sanity_log.c000066400000000000000000000013621526741135700207140ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #include #include #include "sanity_log.h" void sanity_log_add(sanity_log_t *log, sanity_status_t st, const char *id, const char *fmt, ...) { if (log->count >= SANITY_LOG_MAX) return; sanity_entry_t *e = &log->entries[log->count++]; e->status = st; e->id = id; va_list ap; va_start(ap, fmt); vsnprintf(e->detail, sizeof(e->detail), fmt, ap); va_end(ap); if (st == SANITY_FAIL) log->any_failed = true; } const char * sanity_status_label(sanity_status_t s) { switch (s) { case SANITY_PASS: return "PASS"; case SANITY_WARN: return "WARN"; case SANITY_FAIL: return "FAIL"; } return "????"; } vjardin-qoriq-rcw-f74d8cb/src/sanity_log.h000066400000000000000000000016471526741135700207270ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * The sanity log: PASS / WARN / FAIL entry per check, printed as a * C comment at the top of the --dump and --dump-flash output and as * the --check report. */ #pragma once #include #include typedef enum { SANITY_PASS = 0, SANITY_WARN, SANITY_FAIL, } sanity_status_t; typedef struct { sanity_status_t status; const char *id; /* short identifier, e.g. "svr_match" */ char detail[160]; /* one-line human description */ } sanity_entry_t; #define SANITY_LOG_MAX 16 typedef struct { sanity_entry_t entries[SANITY_LOG_MAX]; size_t count; bool any_failed; } sanity_log_t; void sanity_log_add(sanity_log_t *log, sanity_status_t st, const char *id, const char *fmt, ...); /* "PASS" / "WARN" / "FAIL" */ const char *sanity_status_label(sanity_status_t s); vjardin-qoriq-rcw-f74d8cb/src/soc_db.c000066400000000000000000000070111526741135700177720ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "soc_db.h" /* * SVR signatures (mask 0xFFFF0000 - match by 16-bit family ID, ignoring * variant byte and silicon revision). The full reset value for LX2160A * is 0x87360120 per LX2160ARM.txt:37468; the other families use the * same encoding scheme with different family IDs. * * Family IDs taken from the Linux kernel guts driver and U-Boot's * arch/arm/include/asm/arch-fsl-layerscape/cpu.h: * LS1028A = 0x870B LS1088A = 0x8703 LS2088A = 0x8709 * LX2160A = 0x8736 LX2120A = 0x8737 LX2080A = 0x8738 * * LX2080A and LX2120A are reduced personalities of the LX2160A die * and use the same .rcwi. */ static const soc_info_t SOCS[] = { { "fsl,ls1028a", "NXP Layerscape LS1028A", 0x870B0000u, 0xFFFF0000u, 0x01E00000ULL, 0x100u, 0x0A4u, 128, "ls1028a.rcwi" }, { "fsl,ls1088a", "NXP Layerscape LS1088A", 0x87030000u, 0xFFFF0000u, 0x01E00000ULL, 0x100u, 0x0A4u, 128, "ls1088a.rcwi" }, { "fsl,ls2088a", "NXP Layerscape LS2088A", 0x87090000u, 0xFFFF0000u, 0x01E00000ULL, 0x100u, 0x0A4u, 128, "ls2088a.rcwi" }, { "fsl,lx2160a", "NXP Layerscape LX2160A", 0x87360000u, 0xFFFF0000u, 0x01E00000ULL, 0x100u, 0x0A4u, 128, "lx2160a.rcwi" }, { "fsl,lx2120a", "NXP Layerscape LX2120A", 0x87370000u, 0xFFFF0000u, 0x01E00000ULL, 0x100u, 0x0A4u, 128, "lx2160a.rcwi" }, { "fsl,lx2080a", "NXP Layerscape LX2080A", 0x87380000u, 0xFFFF0000u, 0x01E00000ULL, 0x100u, 0x0A4u, 128, "lx2160a.rcwi" }, }; #define SOCS_COUNT (sizeof(SOCS) / sizeof(SOCS[0])) size_t soc_db_count(void) { return SOCS_COUNT; } const soc_info_t * soc_db_at(size_t i) { if (i >= SOCS_COUNT) return NULL; return &SOCS[i]; } const soc_info_t * soc_db_find_by_compat(const char *compat) { if (!compat) return NULL; for (size_t i = 0; i < SOCS_COUNT; i++) { if (strcmp(SOCS[i].compat, compat) == 0) return &SOCS[i]; } return NULL; } const soc_info_t * soc_db_find_by_svr(uint32_t svr) { for (size_t i = 0; i < SOCS_COUNT; i++) { if ((svr & SOCS[i].svr_mask) == SOCS[i].svr_match) return &SOCS[i]; } return NULL; } /* * Read /proc/device-tree/compatible - a NUL-separated list of strings, * "most specific first". Walk each entry through the SoC table and * return the first match. * * The path can be overridden via $QORIQ_RCW_DT_COMPAT_FILE so unit * tests can point at a fixture without needing /proc. */ const soc_info_t * soc_db_detect(char *detail, size_t detail_size) { const char *path = getenv("QORIQ_RCW_DT_COMPAT_FILE"); if (!path) path = "/proc/device-tree/compatible"; FILE *fp = fopen(path, "rb"); if (!fp) { snprintf(detail, detail_size, "could not open %s: %s", path, strerror(errno)); return NULL; } /* * The DT 'compatible' property is bounded - typical size is a few * hundred bytes. 4 KiB is plenty. */ char buf[BUFSIZ]; size_t n = fread(buf, 1, sizeof(buf) - 1, fp); fclose(fp); buf[n] = '\0'; for (size_t off = 0; off < n; ) { const char *entry = buf + off; size_t entry_len = strnlen(entry, n - off); if (entry_len == 0) { off++; continue; } const soc_info_t *soc = soc_db_find_by_compat(entry); if (soc) { snprintf(detail, detail_size, "%s: %s", path, entry); return soc; } off += entry_len + 1; } snprintf(detail, detail_size, "%s: no entry matched a known QorIQ/Layerscape SoC", path); return NULL; } vjardin-qoriq-rcw-f74d8cb/src/soc_db.h000066400000000000000000000050311526741135700177770ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Database of supported NXP Layerscape SoCs for runtime RCW dump. * * Each entry maps a Device Tree "compatible" string and an SVR (System * Version Register) signature to: * - the CCSR DCFG block base address, * - the RCWSR1 offset (where the 1024-bit RCW snapshot lives), * - the SVR offset (used both for matching and as a sanity check), * - the canonical .rcwi filename for the family. * * SVR values: see LX2160ARM.txt section 9.3.1.12 (LX2160A reset value 0x87360120) * and the equivalent registers in the LS1028A/LS1088A/LS2088A reference * manuals. The 16-bit family ID lives in bits[31:16]; we mask away the * variant and revision bytes so a single entry matches every silicon rev. * * Layout (DCFG @ 0x01E0_0000, RCWSR1 @ +0x100, SVR @ +0xA4) is identical * across all four pbiformat=2 families. */ #pragma once #include #include typedef struct { const char *compat; /* DT compatible, e.g. "fsl,lx2160a" */ const char *pretty_name; /* "NXP Layerscape LX2160A" */ uint32_t svr_match; /* expected SVR after masking */ uint32_t svr_mask; /* mask applied before compare */ uint64_t dcfg_base; /* CCSR DCFG block base */ uint32_t rcwsr1_offset; /* offset of RCWSR1 within DCFG */ uint32_t svr_offset; /* offset of SVR within DCFG */ size_t rcw_bytes; /* always 128 for pbiformat=2 */ const char *default_rcwi; /* canonical .rcwi filename */ } soc_info_t; /* Lookup helpers - return NULL if no match. */ const soc_info_t *soc_db_find_by_compat(const char *compat); const soc_info_t *soc_db_find_by_svr(uint32_t svr); /* Iterate the table (count + indexed access) - used by tests and by the SVR-probe fallback in soc_db_detect(). */ size_t soc_db_count(void); const soc_info_t *soc_db_at(size_t i); /* * Detect the running SoC. * * Strategy: * 1. Read /proc/device-tree/compatible (or the override at * $QORIQ_RCW_DT_COMPAT_FILE for tests). Each NUL-separated entry * is looked up via soc_db_find_by_compat(). * 2. If DT detection fails, the caller may probe SVR directly * (soc_db_find_by_svr() once an SVR has been read). * * On success returns a non-NULL soc_info_t* and writes a one-line * description of how detection succeeded into `detail` (e.g. * "/proc/device-tree/compatible: fsl,lx2160a"). On failure returns * NULL and writes the reason into `detail`. */ const soc_info_t *soc_db_detect(char *detail, size_t detail_size); vjardin-qoriq-rcw-f74d8cb/tests/000077500000000000000000000000001526741135700167515ustar00rootroot00000000000000vjardin-qoriq-rcw-f74d8cb/tests/compare_with_rcwpy.sh000077500000000000000000000072061526741135700232220ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: BSD-3-Clause # Copyright 2026 Free Mobile - Vincent Jardin # # Cross-validation: compile all pbiformat=2 boards with both rcw.py and # qoriq-rcw, compare the binary outputs byte-for-byte, then check every # image with qoriq-rcw --check # # This script is for development validation only. # # Locating the upstream NXP RCW reference tree (rcw.py + per-board .rcw): # 1. RCW_REF_DIR environment variable (highest priority) # 2. -Drcw-ref-dir= meson option (forwarded to the test as # RCW_REF_DIR) # 3. ../../rcw relative to this script (fallback) set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" RCW_DIR="${RCW_REF_DIR:-${SCRIPT_DIR}/../../rcw}" RCW_PY="${RCW_DIR}/rcw.py" QORIQ_RCW="${MESON_BUILD_ROOT:-${SCRIPT_DIR}/../build}/qoriq-rcw" TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT if [ ! -x "$QORIQ_RCW" ]; then echo "ERROR: qoriq-rcw not found at $QORIQ_RCW" >&2 exit 1 fi if ! command -v python3 &>/dev/null; then echo "SKIP: python3 not found" >&2 exit 77 fi if [ ! -d "$RCW_DIR" ] || [ ! -f "$RCW_PY" ]; then echo "ERROR: NXP rcw.py reference tree not found." >&2 echo " Expected directory: $RCW_DIR" >&2 echo " Expected script: $RCW_PY" >&2 echo "" >&2 echo "This cross-validation test requires the upstream NXP RCW" >&2 echo "repository (rcw.py and the per-board .rcw source files)." >&2 echo "Clone it from:" >&2 echo "" >&2 echo " git clone git@github.com:nxp-qoriq/rcw.git" >&2 echo " # or: git clone https://github.com/nxp-qoriq/rcw.git" >&2 echo "" >&2 echo "Then point this test at the clone using one of:" >&2 echo "" >&2 echo " 1. At meson configure time:" >&2 echo " meson setup build -Drcw-ref-dir=/path/to/rcw" >&2 echo "" >&2 echo " 2. At test time, via environment variable:" >&2 echo " RCW_REF_DIR=/path/to/rcw meson test -C build" >&2 echo "" >&2 echo " 3. By cloning into the default location:" >&2 echo " (cd $(cd "$SCRIPT_DIR/../.." && pwd) \\" >&2 echo " && git clone https://github.com/nxp-qoriq/rcw.git)" >&2 exit 1 fi PASS=0 FAIL=0 SKIP=0 # Boards that use pbiformat=2 BOARDS="ls1028ardb ls1028aqds ls1088ardb ls1088aqds ls2088ardb ls2088ardb_rev1.1 ls2088aqds lx2160ardb lx2160ardb_rev2 lx2160aqds lx2160aqds_rev2 lx2162aqds" for board in $BOARDS; do board_dir="${RCW_DIR}/${board}" if [ ! -d "$board_dir" ]; then echo "SKIP: $board (directory not found)" SKIP=$((SKIP + 1)) continue fi for rcw_file in $(cd "$board_dir" && find . -name '*.rcw' -path '*/*/*.rcw' | sort); do rel="${board}/${rcw_file#./}" py_out="$TMPDIR/py.bin" c_out="$TMPDIR/c.bin" py_cmd="(cd $board_dir && python3 $RCW_PY -i $rcw_file -o $py_out)" c_cmd="(cd $board_dir && $QORIQ_RCW -i $rcw_file -o $c_out)" echo "[$rel]" echo " PY: $py_cmd" echo " C: $c_cmd" # Compile with rcw.py if ! (cd "$board_dir" && python3 "$RCW_PY" -i "$rcw_file" -o "$py_out") 2>/dev/null; then echo " SKIP: rcw.py failed" SKIP=$((SKIP + 1)) continue fi # Compile with qoriq-rcw if ! (cd "$board_dir" && "$QORIQ_RCW" -i "$rcw_file" -o "$c_out") 2>/dev/null; then echo " FAIL: qoriq-rcw failed" FAIL=$((FAIL + 1)) continue fi # Compare if ! cmp -s "$py_out" "$c_out"; then echo " FAIL: output differs" FAIL=$((FAIL + 1)) continue fi if ! "$QORIQ_RCW" --check -i "$c_out" >/dev/null 2>&1; then echo " FAIL: qoriq-rcw --check rejects the image" FAIL=$((FAIL + 1)) continue fi echo " PASS" PASS=$((PASS + 1)) rm -f "$py_out" "$c_out" done done echo "" echo "Results: $PASS passed, $FAIL failed, $SKIP skipped" if [ $FAIL -gt 0 ]; then exit 1 fi exit 0 vjardin-qoriq-rcw-f74d8cb/tests/meson.build000066400000000000000000000036751526741135700211260ustar00rootroot00000000000000tests_opt = get_option('tests') cmocka_dep = dependency('cmocka', required : tests_opt) if cmocka_dep.found() test_inc = include_directories('../src') test_soc_db = executable('test_soc_db', ['test_soc_db.c', '../src/soc_db.c'], include_directories : test_inc, dependencies : [cmocka_dep]) test('test_soc_db', test_soc_db, suite : 'unit') test_runtime_dump = executable('test_runtime_dump', ['test_runtime_dump.c', '../src/runtime_dump.c', '../src/sanity_log.c', '../src/soc_db.c'], include_directories : test_inc, dependencies : [cmocka_dep, libqoriq_rcw_dep]) test('test_runtime_dump', test_runtime_dump, suite : 'unit') test_flash_db = executable('test_flash_db', ['test_flash_db.c', '../src/flash_db.c'], include_directories : test_inc, dependencies : [cmocka_dep]) test('test_flash_db', test_flash_db, suite : 'unit') test_pbl_check = executable('test_pbl_check', ['test_pbl_check.c', '../src/pbl_check.c', '../src/sanity_log.c'], include_directories : test_inc, dependencies : [cmocka_dep, libqoriq_rcw_dep]) test('test_pbl_check', test_pbl_check, suite : 'unit') test_flash_dump = executable('test_flash_dump', ['test_flash_dump.c', '../src/flash_dump.c', '../src/pbl_check.c', '../src/runtime_dump.c', '../src/sanity_log.c', '../src/soc_db.c', '../src/flash_db.c'], include_directories : test_inc, dependencies : [cmocka_dep, libqoriq_rcw_dep]) test('test_flash_dump', test_flash_dump, suite : 'unit') endif python3 = find_program('python3', required : false) if not tests_opt.disabled() and python3.found() test_env = environment() rcw_ref_dir = get_option('rcw-ref-dir') if rcw_ref_dir != '' test_env.set('RCW_REF_DIR', rcw_ref_dir) endif test('cross-validate', find_program('compare_with_rcwpy.sh'), workdir : meson.project_source_root(), env : test_env, timeout : 300, is_parallel : false, suite : 'cross-validation') endif vjardin-qoriq-rcw-f74d8cb/tests/pbl_fixture.h000066400000000000000000000055021526741135700214470ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin */ #pragma once #include #include #include /* * The envelope, then write (2 words), blockcopy (4), wait (1) and the * terminator (2): RCW[PBI_LENGTH] = 9 */ #define FIX_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 FIX_PBI \ ".pbi\n" \ "write 0x01e00620,0x80000000\n" \ "blockcopy 0x0f,0x20009000,0x1800d000,0x1000\n" \ "wait 0x64\n" \ ".end\n" #define FIX_SRC_STOP FIX_HDR "%nocrc=1\n" FIX_PBI #define FIX_SRC_CRCSTOP FIX_HDR FIX_PBI #define FIX_SRC_NOCS FIX_HDR "%nocrc=1\n" "%loadwochecksum=1\n" FIX_PBI #define FIX_PBI_WORDS 9 #define FIX_PBI_BYTES (4 * FIX_PBI_WORDS) #define FIX_NCMD 4 /* write, blockcopy, wait, the terminator */ #define FIX_IMG_BYTES (RCW_PBL_PBI_OFFSET + FIX_PBI_BYTES) #define FIX_PBI_OFF_WRITE 0 #define FIX_PBI_OFF_BLOCKCOPY 8 #define FIX_PBI_OFF_WAIT 24 #define FIX_PBI_OFF_STOP 28 /* RCW[PBI_LENGTH] is bits 287:276: the top 12 bits of RCW word 8. */ #define FIX_PBI_LENGTH_AT (RCW_PBL_RCW_OFFSET + 8 * 4) static inline uint32_t fix_get_le32(const uint8_t *p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); } static inline void fix_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); } /* Compile src into out (cap bytes). Returns the image length, 0 on failure. */ static inline size_t fix_compile(const char *src, uint8_t *out, size_t cap) { rcw_ctx_t *ctx = rcw_ctx_new(); if (!ctx) return 0; uint8_t *bin = NULL; size_t len = 0; rcw_error_t err = rcw_compile_buffer(ctx, src, strlen(src), &bin, &len); if (err == RCW_OK && len <= cap) memcpy(out, bin, len); else len = 0; rcw_free(bin); rcw_ctx_free(ctx); return len; } static inline void fix_set_pbi_length(uint8_t *img, uint32_t words) { uint32_t w = fix_get_le32(img + FIX_PBI_LENGTH_AT); fix_put_le32(img + FIX_PBI_LENGTH_AT, (w & 0x000FFFFFu) | (words << 20)); } /* Recompute the Load RCW checksum after patching the RCW. */ static inline void fix_checksum(uint8_t *img) { uint32_t sum = 0; for (size_t off = 0; off < RCW_PBL_CHECKSUM_OFFSET; off += 4) sum += fix_get_le32(img + off); fix_put_le32(img + RCW_PBL_CHECKSUM_OFFSET, sum); } vjardin-qoriq-rcw-f74d8cb/tests/test_flash_db.c000066400000000000000000000137361526741135700217300ustar00rootroot00000000000000/* * 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 "flash_db.h" static void test_lookup_by_rcw_src(void **state) { (void)state; assert_string_equal(flash_db_find_by_rcw_src(0xF)->interface, "flexspi"); assert_string_equal(flash_db_find_by_rcw_src(0xC)->interface, "flexspi"); assert_string_equal(flash_db_find_by_rcw_src(0xD)->interface, "flexspi"); assert_string_equal(flash_db_find_by_rcw_src(0x8)->interface, "sd"); assert_string_equal(flash_db_find_by_rcw_src(0x9)->interface, "emmc"); assert_string_equal(flash_db_find_by_rcw_src(0xA)->interface, "i2c"); assert_null(flash_db_find_by_rcw_src(0x0)); assert_null(flash_db_find_by_rcw_src(0xE)); } static void test_porsr1_extract(void **state) { (void)state; /* RCW_SRC field at bits[28:23], low 4 bits carry the source code. */ assert_int_equal(flash_db_porsr1_to_rcw_src(0x07800000), 0xF); assert_int_equal(flash_db_porsr1_to_rcw_src(0x04000000), 0x8); assert_int_equal(flash_db_porsr1_to_rcw_src(0x05000000), 0xA); /* Garbage in upper bits is ignored. */ assert_int_equal(flash_db_porsr1_to_rcw_src(0xFFFFFFFF), 0xF); assert_int_equal(flash_db_porsr1_to_rcw_src(0x00000000), 0x0); } static void test_offsets_per_interface(void **state) { (void)state; /* FlexSPI: primary @ 0, fallback @ 8 MiB */ assert_int_equal(flash_db_find_by_rcw_src(0xF)->primary_offset, 0x0); assert_int_equal(flash_db_find_by_rcw_src(0xF)->fallback_offset, 0x800000); /* SD/eMMC: primary @ 0x1000, fallback @ 0x801000 */ assert_int_equal(flash_db_find_by_rcw_src(0x8)->primary_offset, 0x1000); assert_int_equal(flash_db_find_by_rcw_src(0x8)->fallback_offset, 0x801000); assert_int_equal(flash_db_find_by_rcw_src(0x9)->primary_offset, 0x1000); assert_int_equal(flash_db_find_by_rcw_src(0x9)->fallback_offset, 0x801000); /* I2C: no fallback, device required */ assert_int_equal(flash_db_find_by_rcw_src(0xA)->fallback_offset, 0); assert_true(flash_db_find_by_rcw_src(0xA)->device_required); assert_null(flash_db_find_by_rcw_src(0xA)->default_device); } static void test_resolve_device_emmc_default(void **state) { (void)state; char err[256] = {0}; char *dev = flash_db_resolve_device(flash_db_find_by_rcw_src(0x9), err, sizeof(err)); assert_non_null(dev); assert_string_equal(dev, "/dev/mmcblk0boot0"); free(dev); } static void test_resolve_device_i2c_errors(void **state) { (void)state; char err[256] = {0}; char *dev = flash_db_resolve_device(flash_db_find_by_rcw_src(0xA), err, sizeof(err)); assert_null(dev); assert_non_null(strstr(err, "requires --device")); } /* Write a /proc/mtd-style fixture and point QORIQ_RCW_PROC_MTD at it. */ static char * make_proc_mtd_fixture(const char *body) { char tmpl[] = "/tmp/proc-mtd-XXXXXX"; int fd = mkstemp(tmpl); assert_true(fd >= 0); ssize_t n = write(fd, body, strlen(body)); assert_true(n > 0); close(fd); return strdup(tmpl); } static void test_resolve_device_flexspi_picks_named_partition(void **state) { (void)state; /* Two MTD entries - "rcw" should win over "userdata" via priority. */ char *fix = make_proc_mtd_fixture( "dev: size erasesize name\n" "mtd0: 02000000 00040000 \"userdata\"\n" "mtd1: 00100000 00040000 \"rcw\"\n"); setenv("QORIQ_RCW_PROC_MTD", fix, 1); char err[256] = {0}; char *dev = flash_db_resolve_device(flash_db_find_by_rcw_src(0xF), err, sizeof(err)); assert_non_null(dev); assert_string_equal(dev, "/dev/mtd1"); free(dev); unsetenv("QORIQ_RCW_PROC_MTD"); unlink(fix); free(fix); } static void test_resolve_device_flexspi_priority_order(void **state) { (void)state; /* "qspi" appears in mtd0 but "bl2" should win because it ranks higher. */ char *fix = make_proc_mtd_fixture( "dev: size erasesize name\n" "mtd0: 02000000 00040000 \"qspi-flash\"\n" "mtd1: 00100000 00040000 \"bl2-image\"\n"); setenv("QORIQ_RCW_PROC_MTD", fix, 1); char err[256] = {0}; char *dev = flash_db_resolve_device(flash_db_find_by_rcw_src(0xF), err, sizeof(err)); assert_non_null(dev); assert_string_equal(dev, "/dev/mtd1"); free(dev); unsetenv("QORIQ_RCW_PROC_MTD"); unlink(fix); free(fix); } static void test_resolve_device_flexspi_falls_back_to_first_mtd(void **state) { (void)state; /* No name matches any pattern -> first MTD entry wins. */ char *fix = make_proc_mtd_fixture( "dev: size erasesize name\n" "mtd0: 02000000 00040000 \"system-a\"\n" "mtd1: 00100000 00040000 \"system-b\"\n"); setenv("QORIQ_RCW_PROC_MTD", fix, 1); char err[256] = {0}; char *dev = flash_db_resolve_device(flash_db_find_by_rcw_src(0xF), err, sizeof(err)); assert_non_null(dev); assert_string_equal(dev, "/dev/mtd0"); free(dev); unsetenv("QORIQ_RCW_PROC_MTD"); unlink(fix); free(fix); } static void test_resolve_device_flexspi_no_proc_mtd_falls_back_to_default(void **state) { (void)state; setenv("QORIQ_RCW_PROC_MTD", "/nonexistent/path/qzzx", 1); char err[256] = {0}; char *dev = flash_db_resolve_device(flash_db_find_by_rcw_src(0xF), err, sizeof(err)); assert_non_null(dev); assert_string_equal(dev, "/dev/mtd0"); free(dev); unsetenv("QORIQ_RCW_PROC_MTD"); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_lookup_by_rcw_src), cmocka_unit_test(test_porsr1_extract), cmocka_unit_test(test_offsets_per_interface), cmocka_unit_test(test_resolve_device_emmc_default), cmocka_unit_test(test_resolve_device_i2c_errors), cmocka_unit_test(test_resolve_device_flexspi_picks_named_partition), cmocka_unit_test(test_resolve_device_flexspi_priority_order), cmocka_unit_test(test_resolve_device_flexspi_falls_back_to_first_mtd), cmocka_unit_test(test_resolve_device_flexspi_no_proc_mtd_falls_back_to_default), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-qoriq-rcw-f74d8cb/tests/test_flash_dump.c000066400000000000000000000307321526741135700223030ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Tests for flash_dump.c - slot reading, sanity battery, header. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "flash_db.h" #include "flash_dump.h" #include "pbl_fixture.h" /* Compile src into out, 0xFF-padded to cap like flash is. Returns the image length. */ static size_t make_pbl(uint8_t *out, size_t cap, const char *src) { memset(out, 0xFF, cap); size_t len = fix_compile(src, out, cap); assert_int_equal(len, FIX_IMG_BYTES); return len; } static char * make_flash_fixture(const uint8_t *pbl_primary, size_t plen, const uint8_t *pbl_fallback, size_t flen) { /* 16 MiB image: primary @ 0, fallback @ 0x800000 */ uint8_t *flash = malloc(16u * 1024u * 1024u); memset(flash, 0xFF, 16u * 1024u * 1024u); if (pbl_primary && plen) memcpy(flash, pbl_primary, plen); if (pbl_fallback && flen) memcpy(flash + 0x800000u, pbl_fallback, flen); char tmpl[] = "/tmp/flash-fix-XXXXXX"; int fd = mkstemp(tmpl); assert_true(fd >= 0); ssize_t n = write(fd, flash, 16u * 1024u * 1024u); assert_int_equal(n, (ssize_t)(16u * 1024u * 1024u)); close(fd); free(flash); return strdup(tmpl); } static void test_read_slot_primary(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); char *fix = make_flash_fixture(pbl, pblen, NULL, 0); flash_slot_t slot = {0}; char err[256] = {0}; int rc = flash_dump_read_slot(fix, 0, "primary", &slot, err, sizeof(err)); assert_int_equal(rc, 0); assert_int_equal(slot.data_len, FLASH_SLOT_READ_BYTES); assert_memory_equal(slot.data, pbl, pblen); /* Bytes after the PBL come from the 0xFF padding. */ for (size_t i = pblen; i < pblen + 8; i++) assert_int_equal(slot.data[i], 0xFF); /* The sanity run trims the slot to the image. */ sanity_log_t log; flash_dump_sanity_run(&slot, NULL, &log); assert_false(log.any_failed); assert_int_equal(slot.pbl_len, pblen); flash_slot_free(&slot); unlink(fix); free(fix); } static void test_read_slot_fallback(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); char *fix = make_flash_fixture(NULL, 0, pbl, pblen); flash_slot_t slot = {0}; char err[256] = {0}; int rc = flash_dump_read_slot(fix, 0x800000, "fallback", &slot, err, sizeof(err)); assert_int_equal(rc, 0); assert_memory_equal(slot.data, pbl, pblen); flash_slot_free(&slot); unlink(fix); free(fix); } static void test_read_slot_open_fails(void **state) { (void)state; flash_slot_t slot = {0}; char err[256] = {0}; int rc = flash_dump_read_slot("/nonexistent/path/qzzx", 0, "primary", &slot, err, sizeof(err)); assert_int_not_equal(rc, 0); assert_non_null(strstr(err, "open")); } static const sanity_entry_t * find_entry(const sanity_log_t *log, const char *id) { for (size_t i = 0; i < log->count; i++) if (strcmp(log->entries[i].id, id) == 0) return &log->entries[i]; return NULL; } static flash_slot_t slot_of(uint8_t *data, size_t len) { flash_slot_t slot = { .name = "primary", .offset = 0, .data = data, .data_len = len, .pbl_len = len, }; return slot; } static void test_sanity_all_pass(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); flash_slot_t slot = slot_of(pbl, sizeof(pbl)); const uint8_t *live_rcw = pbl + RCW_PBL_RCW_OFFSET; /* matches the in-PBL RCW */ sanity_log_t log; flash_dump_sanity_run(&slot, live_rcw, &log); assert_false(log.any_failed); assert_int_equal(find_entry(&log, "preamble")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "load_cmd")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "checksum")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_PASS); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "Stop at byte 168")); assert_int_equal(find_entry(&log, "pbi_crc")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "rcw_consistent")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "pbi_commands")->status, SANITY_PASS); /* Slot was trimmed to the image (preamble..terminator value), padding dropped. */ assert_int_equal(slot.pbl_len, pblen); assert_int_equal(slot.pbi_length, FIX_PBI_WORDS); assert_int_equal(slot.ncmd, FIX_NCMD); assert_false(slot.sb_en); } static void test_sanity_crcstop_pass(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_CRCSTOP); flash_slot_t slot = slot_of(pbl, pblen); sanity_log_t log; flash_dump_sanity_run(&slot, NULL, &log); assert_false(log.any_failed); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "CRC and Stop")); assert_int_equal(find_entry(&log, "pbi_crc")->status, SANITY_PASS); assert_non_null(strstr(find_entry(&log, "pbi_crc")->detail, "matches")); assert_null(find_entry(&log, "rcw_consistent")); } static void test_sanity_without_checksum(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_NOCS); flash_slot_t slot = slot_of(pbl, pblen); sanity_log_t log; flash_dump_sanity_run(&slot, NULL, &log); assert_false(log.any_failed); assert_non_null(strstr(find_entry(&log, "load_cmd")->detail, "without checksum")); assert_non_null(strstr(find_entry(&log, "checksum")->detail, "skipped")); } static void test_sanity_bad_preamble(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); pbl[0] = 0xDE; /* wreck preamble */ flash_slot_t slot = slot_of(pbl, pblen); sanity_log_t log; flash_dump_sanity_run(&slot, NULL, &log); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "preamble")->status, SANITY_FAIL); assert_null(find_entry(&log, "load_cmd")); assert_int_equal(slot.pbl_len, pblen); } static void test_sanity_bad_load_cmd(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); pbl[6] = 0x12; /* 0x80120000 is no Load RCW */ flash_slot_t slot = slot_of(pbl, pblen); sanity_log_t log; flash_dump_sanity_run(&slot, NULL, &log); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "preamble")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "load_cmd")->status, SANITY_FAIL); } static void test_sanity_too_short(void **state) { (void)state; uint8_t pbl[200]; make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); flash_slot_t slot = slot_of(pbl, RCW_PBL_PBI_OFFSET - 1); sanity_log_t log; flash_dump_sanity_run(&slot, NULL, &log); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "preamble")->status, SANITY_FAIL); assert_non_null(strstr(find_entry(&log, "preamble")->detail, "too short")); } static void test_sanity_bad_checksum(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); pbl[RCW_PBL_CHECKSUM_OFFSET] ^= 0xFF; /* corrupt one checksum byte */ flash_slot_t slot = slot_of(pbl, pblen); sanity_log_t log; flash_dump_sanity_run(&slot, NULL, &log); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "checksum")->status, SANITY_FAIL); /* The PBI is still fine. */ assert_int_equal(find_entry(&log, "terminator")->status, SANITY_PASS); } static void test_sanity_crc_mismatch(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_CRCSTOP); pbl[RCW_PBL_PBI_OFFSET + FIX_PBI_OFF_WRITE + 4] ^= 0x01; /* the write's value */ flash_slot_t slot = slot_of(pbl, pblen); sanity_log_t log; flash_dump_sanity_run(&slot, NULL, &log); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "pbi_crc")->status, SANITY_FAIL); assert_int_equal(slot.pbl_len, pblen); } static void test_sanity_bad_pbi_length(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); fix_set_pbi_length(pbl, FIX_PBI_WORDS - 2); /* points into the wait */ fix_checksum(pbl); flash_slot_t slot = slot_of(pbl, pblen); sanity_log_t log; flash_dump_sanity_run(&slot, NULL, &log); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "checksum")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_FAIL); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "RCW[PBI_LENGTH]=7")); assert_null(find_entry(&log, "pbi_crc")); } static void test_sanity_truncated(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); /* The read window ends inside the PBI. */ flash_slot_t slot = slot_of(pbl, pblen - 4); sanity_log_t log; flash_dump_sanity_run(&slot, NULL, &log); /* WARN, not FAIL - the flash may hold the rest. */ assert_false(log.any_failed); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_WARN); assert_int_equal(slot.pbl_len, pblen - 4); } static void test_sanity_no_pbi(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); fix_set_pbi_length(pbl, 0); fix_checksum(pbl); flash_slot_t slot = slot_of(pbl, pblen); sanity_log_t log; flash_dump_sanity_run(&slot, NULL, &log); assert_false(log.any_failed); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_WARN); assert_null(find_entry(&log, "pbi_crc")); assert_int_equal(slot.pbl_len, RCW_PBL_PBI_OFFSET); assert_int_equal(slot.ncmd, 0); } static void test_sanity_rcw_inconsistent(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); flash_slot_t slot = slot_of(pbl, pblen); uint8_t live_rcw[RCW_PBL_RCW_BYTES]; memcpy(live_rcw, pbl + RCW_PBL_RCW_OFFSET, sizeof(live_rcw)); live_rcw[0] ^= 0x42; /* differs from the in-PBL RCW */ sanity_log_t log; flash_dump_sanity_run(&slot, live_rcw, &log); assert_false(log.any_failed); assert_int_equal(find_entry(&log, "rcw_consistent")->status, SANITY_WARN); } static void test_compare_slots_match(void **state) { (void)state; uint8_t pbl[200]; size_t pblen = make_pbl(pbl, sizeof(pbl), FIX_SRC_STOP); flash_slot_t a = { .name="primary", .data=pbl, .data_len=pblen, .pbl_len=pblen }; flash_slot_t b = { .name="fallback", .data=pbl, .data_len=pblen, .pbl_len=pblen }; sanity_log_t log = {0}; flash_dump_compare_slots(&a, &b, &log); assert_int_equal(find_entry(&log, "fallback_match")->status, SANITY_PASS); } static void test_compare_slots_differ(void **state) { (void)state; uint8_t pbl_a[200], pbl_b[200]; size_t pa = make_pbl(pbl_a, sizeof(pbl_a), FIX_SRC_STOP); size_t pb = make_pbl(pbl_b, sizeof(pbl_b), FIX_SRC_STOP); pbl_b[10] ^= 0xFF; flash_slot_t a = { .name="primary", .data=pbl_a, .data_len=pa, .pbl_len=pa }; flash_slot_t b = { .name="fallback", .data=pbl_b, .data_len=pb, .pbl_len=pb }; sanity_log_t log = {0}; flash_dump_compare_slots(&a, &b, &log); assert_int_equal(find_entry(&log, "fallback_match")->status, SANITY_WARN); /* Different sizes: the padding after the shorter image is not compared. */ flash_slot_t c = { .name="fallback", .data=pbl_a, .data_len=pa, .pbl_len=pa - 4 }; sanity_log_t log2 = {0}; flash_dump_compare_slots(&a, &c, &log2); assert_int_equal(find_entry(&log2, "fallback_match")->status, SANITY_WARN); assert_non_null(strstr(find_entry(&log2, "fallback_match")->detail, "size")); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_read_slot_primary), cmocka_unit_test(test_read_slot_fallback), cmocka_unit_test(test_read_slot_open_fails), cmocka_unit_test(test_sanity_all_pass), cmocka_unit_test(test_sanity_crcstop_pass), cmocka_unit_test(test_sanity_without_checksum), cmocka_unit_test(test_sanity_bad_preamble), cmocka_unit_test(test_sanity_bad_load_cmd), cmocka_unit_test(test_sanity_too_short), cmocka_unit_test(test_sanity_bad_checksum), cmocka_unit_test(test_sanity_crc_mismatch), cmocka_unit_test(test_sanity_bad_pbi_length), cmocka_unit_test(test_sanity_truncated), cmocka_unit_test(test_sanity_no_pbi), cmocka_unit_test(test_sanity_rcw_inconsistent), cmocka_unit_test(test_compare_slots_match), cmocka_unit_test(test_compare_slots_differ), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-qoriq-rcw-f74d8cb/tests/test_pbl_check.c000066400000000000000000000334031526741135700220710ustar00rootroot00000000000000/* * 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 "pbl_check.h" #include "pbl_fixture.h" static size_t compile(const char *src, uint8_t *out, size_t cap) { size_t len = fix_compile(src, out, cap); assert_int_equal(len, FIX_IMG_BYTES); return len; } static const sanity_entry_t * find_entry(const sanity_log_t *log, const char *id) { for (size_t i = 0; i < log->count; i++) if (strcmp(log->entries[i].id, id) == 0) return &log->entries[i]; return NULL; } static void verify(const uint8_t *img, size_t len, rcw_pbl_report_t *rep, sanity_log_t *log) { rcw_pbl_verify(img, len, 1, NULL, NULL, rep); memset(log, 0, sizeof(*log)); pbl_sanity_run(rep, len, NULL, log); } static void test_stop_image(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_STOP, img, sizeof(img)); rcw_pbl_report_t r; sanity_log_t log; verify(img, len, &r, &log); assert_true(r.ok); assert_int_equal(r.pbl.pbi_length, FIX_PBI_WORDS); assert_int_equal(r.chk.ncmd, FIX_NCMD); assert_int_equal(r.image_end, len); assert_false(log.any_failed); assert_int_equal(find_entry(&log, "preamble")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "load_cmd")->status, SANITY_PASS); assert_non_null(strstr(find_entry(&log, "load_cmd")->detail, "with checksum")); assert_int_equal(find_entry(&log, "checksum")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_PASS); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "Stop at byte 168")); assert_int_equal(find_entry(&log, "pbi_crc")->status, SANITY_PASS); assert_non_null(strstr(find_entry(&log, "pbi_crc")->detail, "none")); assert_null(find_entry(&log, "rcw_consistent")); assert_int_equal(find_entry(&log, "pbi_commands")->status, SANITY_PASS); assert_non_null(strstr(find_entry(&log, "pbi_commands")->detail, "4 commands in 36 bytes")); } static void test_crcstop_image(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_CRCSTOP, img, sizeof(img)); rcw_pbl_report_t r; sanity_log_t log; verify(img, len, &r, &log); assert_true(r.ok); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "CRC and Stop")); assert_int_equal(find_entry(&log, "pbi_crc")->status, SANITY_PASS); assert_non_null(strstr(find_entry(&log, "pbi_crc")->detail, "matches")); /* Corrupt the value of the write: the CRC no longer matches. */ img[RCW_PBL_PBI_OFFSET + FIX_PBI_OFF_WRITE + 4] ^= 0x01; verify(img, len, &r, &log); assert_false(r.ok); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "pbi_crc")->status, SANITY_FAIL); } static void test_without_checksum(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_NOCS, img, sizeof(img)); rcw_pbl_report_t r; sanity_log_t log; verify(img, len, &r, &log); assert_true(r.ok); assert_non_null(strstr(find_entry(&log, "load_cmd")->detail, "without checksum")); assert_non_null(strstr(find_entry(&log, "checksum")->detail, "skipped")); } static void test_checksum_mismatch(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_STOP, img, sizeof(img)); img[RCW_PBL_RCW_OFFSET] ^= 0x01; /* SYS_PLL_RAT bits, not PBI_LENGTH */ rcw_pbl_report_t r; sanity_log_t log; verify(img, len, &r, &log); assert_false(r.ok); assert_int_equal(find_entry(&log, "checksum")->status, SANITY_FAIL); /* The PBI is still fine. */ assert_int_equal(find_entry(&log, "terminator")->status, SANITY_PASS); } static void test_pbi_length_too_small(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_STOP, img, sizeof(img)); /* Points into the wait: the walk reaches it without a Stop. */ fix_set_pbi_length(img, FIX_PBI_WORDS - 2); fix_checksum(img); rcw_pbl_report_t r; sanity_log_t log; verify(img, len, &r, &log); assert_false(r.ok); assert_int_equal(find_entry(&log, "checksum")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_FAIL); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "RCW[PBI_LENGTH]=7")); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "after 3 commands")); assert_null(find_entry(&log, "pbi_crc")); } static void test_bad_word(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_STOP, img, sizeof(img)); /* CMD 0x30 is in no table: the walk stops at the wait's slot. */ fix_put_le32(img + RCW_PBL_PBI_OFFSET + FIX_PBI_OFF_WAIT, 0x80300000u); rcw_pbl_report_t r; sanity_log_t log; verify(img, len, &r, &log); assert_false(r.ok); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_FAIL); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "0x80300000 at byte 164")); } static void test_truncated(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_STOP, img, sizeof(img)); /* Cut inside the Stop. */ rcw_pbl_report_t r; sanity_log_t log; verify(img, len - 4, &r, &log); assert_false(r.ok); assert_int_equal(r.image_end, len - 4); /* WARN, not FAIL - the rest may just not have been read. */ assert_false(log.any_failed); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_WARN); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "runs past the 32 bytes available")); } static void test_not_a_pbl(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_STOP, img, sizeof(img)); rcw_pbl_report_t r; sanity_log_t log; verify(img, RCW_PBL_RCW_BYTES, &r, &log); assert_false(r.ok); assert_int_equal(find_entry(&log, "preamble")->status, SANITY_FAIL); assert_non_null(strstr(find_entry(&log, "preamble")->detail, "too short")); assert_null(find_entry(&log, "load_cmd")); img[0] = 0xDE; verify(img, len, &r, &log); assert_int_equal(find_entry(&log, "preamble")->status, SANITY_FAIL); assert_non_null(strstr(find_entry(&log, "preamble")->detail, "got 0xaa55aade")); assert_null(find_entry(&log, "load_cmd")); img[0] = 0x55; img[6] = 0x12; /* 0x80120000 is no Load RCW */ verify(img, len, &r, &log); assert_int_equal(find_entry(&log, "preamble")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "load_cmd")->status, SANITY_FAIL); assert_non_null(strstr(find_entry(&log, "load_cmd")->detail, "got 0x80120000")); assert_null(find_entry(&log, "checksum")); } static void test_no_pbi(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_STOP, img, sizeof(img)); fix_set_pbi_length(img, 0); fix_checksum(img); rcw_pbl_report_t r; sanity_log_t log; verify(img, len, &r, &log); assert_true(r.ok); assert_int_equal(r.image_end, RCW_PBL_PBI_OFFSET); assert_false(log.any_failed); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_WARN); assert_null(find_entry(&log, "pbi_crc")); } static void test_rcw_consistent(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_STOP, img, sizeof(img)); uint8_t live[RCW_PBL_RCW_BYTES]; memcpy(live, img + RCW_PBL_RCW_OFFSET, sizeof(live)); rcw_pbl_report_t r; rcw_pbl_verify(img, len, 1, NULL, NULL, &r); sanity_log_t log = {0}; pbl_sanity_run(&r, len, live, &log); assert_int_equal(find_entry(&log, "rcw_consistent")->status, SANITY_PASS); live[0] ^= 0x42; memset(&log, 0, sizeof(log)); pbl_sanity_run(&r, len, live, &log); assert_false(log.any_failed); assert_int_equal(find_entry(&log, "rcw_consistent")->status, SANITY_WARN); } struct listing { FILE *f; size_t n; }; static void list_visit(const rcw_pbi_cmd_t *cmd, size_t off, void *arg) { struct listing *l = arg; pbl_print_cmd(l->f, cmd, RCW_PBL_PBI_OFFSET + off); l->n++; } static void test_print(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_STOP, img, sizeof(img)); char *buf = NULL; size_t buflen = 0; struct listing l = { .f = open_memstream(&buf, &buflen), .n = 0 }; assert_non_null(l.f); rcw_pbl_report_t r; rcw_pbl_verify(img, len, 1, list_visit, &l, &r); fclose(l.f); assert_true(r.ok); assert_int_equal(l.n, FIX_NCMD); assert_string_equal(buf, " 140 write 0x01e00620,0x80000000\n" " 148 blockcopy 0x0f,0x20009000,0x1800d000,0x00001000\n" " 164 wait 0x00000064\n" " 168 /* Stop command */\n"); free(buf); assert_string_equal(pbl_op_label(RCW_PBI_STOP), "Stop"); assert_string_equal(pbl_op_label(RCW_PBI_CRC_STOP), "CRC and Stop"); assert_string_equal(pbl_op_label(RCW_PBI_WAIT), "wait"); } /* * SolidRun's auto-boot shape: loadc PORSR1, jumpc over a jump, the block * copy of one boot source. */ #define COND_PBI \ ".pbi\n" \ "loadc 0x01e00000,(0xf << 23)\n" \ "jumpc sd,(0x8 << 23)\n" \ "jump done\n" \ "sd:\n" \ "blockcopy 0x08,0x0000a000,0x1800d000,0x00020000\n" \ "write 0x01e00400,0x1800d000\n" \ "done:\n" \ ".end\n" #define COND_IMG_BYTES (RCW_PBL_PBI_OFFSET + 64) #define COND_WRITE_AT (RCW_PBL_PBI_OFFSET + 48) static void test_conditional_image(void **state) { (void)state; uint8_t img[256]; size_t len = fix_compile(FIX_HDR "%nocrc=1\n" COND_PBI, img, sizeof(img)); assert_int_equal(len, COND_IMG_BYTES); rcw_pbl_report_t r; sanity_log_t log; verify(img, len, &r, &log); assert_int_equal(r.pbi, RCW_ERR_PBI_CONDITIONAL); assert_false(r.ok); assert_true(r.checksum_ok); assert_true(r.crc_ok); assert_int_equal(r.chk.ncmd, 2); assert_int_equal(r.image_end, len); assert_false(log.any_failed); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_PASS); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "Stop at byte 196")); assert_int_equal(find_entry(&log, "jumpc")->status, SANITY_WARN); assert_non_null(strstr(find_entry(&log, "jumpc")->detail, "Jump Conditional at byte 152")); assert_int_equal(find_entry(&log, "pbi_crc")->status, SANITY_PASS); assert_non_null(strstr(find_entry(&log, "pbi_crc")->detail, "none")); assert_non_null(strstr(find_entry(&log, "pbi_commands")->detail, "2 commands walked, up to the Jump Conditional at byte 152")); len = fix_compile(FIX_HDR COND_PBI, img, sizeof(img)); assert_int_equal(len, COND_IMG_BYTES); verify(img, len, &r, &log); assert_false(log.any_failed); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "CRC and Stop at byte 196")); assert_non_null(strstr(find_entry(&log, "pbi_crc")->detail, "matches")); img[COND_WRITE_AT + 4] ^= 0x01; verify(img, len, &r, &log); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "jumpc")->status, SANITY_WARN); assert_int_equal(find_entry(&log, "pbi_crc")->status, SANITY_FAIL); } static void test_jump_out_of_range(void **state) { (void)state; uint8_t img[256]; /* jump at PBI byte 0, 0x100 past a 20-byte PBI */ size_t len = fix_compile(FIX_HDR "%nocrc=1\n" ".pbi\njump 0x100\nwait 1\n.end\n", img, sizeof(img)); assert_int_equal(len, RCW_PBL_PBI_OFFSET + 20); rcw_pbl_report_t r; sanity_log_t log; verify(img, len, &r, &log); assert_int_equal(r.pbi, RCW_ERR_PBI_JUMP); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_FAIL); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "jump at byte 140: offset 0x00000100")); assert_null(find_entry(&log, "jumpc")); assert_null(find_entry(&log, "pbi_crc")); } static void test_jumpc_without_condition(void **state) { (void)state; uint8_t img[256]; size_t len = fix_compile(FIX_HDR "%nocrc=1\n" ".pbi\njumpc 0x10,0x1\nwait 1\nwait 1\n.end\n", img, sizeof(img)); assert_int_equal(len, RCW_PBL_PBI_OFFSET + 28); rcw_pbl_report_t r; sanity_log_t log; verify(img, len, &r, &log); assert_int_equal(r.pbi, RCW_ERR_PBI_NO_CONDITION); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_FAIL); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "Jump Conditional at byte 140 before any Load Condition")); } static void test_overrun(void **state) { (void)state; uint8_t img[256]; size_t len = compile(FIX_SRC_STOP, img, sizeof(img)); /* Points inside the blockcopy. */ fix_set_pbi_length(img, 3); fix_checksum(img); rcw_pbl_report_t r; sanity_log_t log; verify(img, len, &r, &log); assert_int_equal(r.pbi, RCW_ERR_PBI_OVERRUN); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "terminator")->status, SANITY_FAIL); assert_non_null(strstr(find_entry(&log, "terminator")->detail, "blockcopy at byte 148 runs past RCW[PBI_LENGTH]=3 words")); assert_null(find_entry(&log, "pbi_crc")); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_conditional_image), cmocka_unit_test(test_jump_out_of_range), cmocka_unit_test(test_jumpc_without_condition), cmocka_unit_test(test_overrun), cmocka_unit_test(test_stop_image), cmocka_unit_test(test_crcstop_image), cmocka_unit_test(test_without_checksum), cmocka_unit_test(test_checksum_mismatch), cmocka_unit_test(test_pbi_length_too_small), cmocka_unit_test(test_bad_word), cmocka_unit_test(test_truncated), cmocka_unit_test(test_not_a_pbl), cmocka_unit_test(test_no_pbi), cmocka_unit_test(test_rcw_consistent), cmocka_unit_test(test_print), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-qoriq-rcw-f74d8cb/tests/test_runtime_dump.c000066400000000000000000000204611526741135700226670ustar00rootroot00000000000000/* * SPDX-License-Identifier: BSD-3-Clause * Copyright 2026 Free Mobile - Vincent Jardin * * Unit tests for runtime_dump.c. * * runtime_read_rcw() is exercised against a temp fixture file (no * /dev/mem, no root needed). The sanity-check suite is exercised * against synthetic 128-byte buffers. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include "runtime_dump.h" #include "soc_db.h" /* * Build a 4 KiB DCFG page fixture: * - SVR at offset 0xA4 * - 128-byte RCW at offset 0x100 (filled with `fill_byte` unless a * custom buffer is provided) * Returns the temp file path (caller frees + unlinks). */ static char * make_page_fixture(uint32_t svr_le, const uint8_t *rcw_or_null, uint8_t fill_byte) { char tmpl[] = "/tmp/qoriq-page-XXXXXX"; int fd = mkstemp(tmpl); assert_true(fd >= 0); uint8_t page[4096]; memset(page, 0, sizeof(page)); /* SVR @ 0xA4, little-endian native uint32 store */ memcpy(page + 0xA4, &svr_le, sizeof(svr_le)); /* RCW @ 0x100 */ if (rcw_or_null) memcpy(page + 0x100, rcw_or_null, 128); else memset(page + 0x100, fill_byte, 128); ssize_t n = write(fd, page, sizeof(page)); assert_int_equal(n, sizeof(page)); close(fd); return strdup(tmpl); } /* A slightly less artificial RCW for sanity-check tests: enough nonzero * bytes to satisfy not_zero, with a small PBI_LENGTH. */ static void fill_realistic_rcw(uint8_t rcw[128]) { memset(rcw, 0, 128); rcw[0] = 0x38; /* SYS_PLL_RAT-ish bits */ rcw[1] = 0x54; rcw[34] = 0x00; /* PBI_LENGTH high byte */ rcw[35] = 0x42; /* PBI_LENGTH low byte = 66 (sane) */ } /* ------------------------------------------------------------------ */ /* runtime_read_rcw */ /* ------------------------------------------------------------------ */ static void test_read_rcw_from_fixture(void **state) { (void)state; uint8_t rcw_in[128]; for (size_t i = 0; i < 128; i++) rcw_in[i] = (uint8_t)i; char *fix = make_page_fixture(0x87360120, rcw_in, 0); /* Override dcfg_base = 0 so mmap targets the start of our fixture * file. The other fields stay at the LX2160 values. */ soc_info_t soc = *soc_db_find_by_compat("fsl,lx2160a"); soc.dcfg_base = 0; uint8_t rcw_out[128] = {0}; uint32_t svr_out = 0; char err[256] = {0}; int rc = runtime_read_rcw(&soc, fix, rcw_out, &svr_out, err, sizeof(err)); assert_int_equal(rc, 0); assert_int_equal(svr_out, 0x87360120); assert_memory_equal(rcw_out, rcw_in, 128); unlink(fix); free(fix); } static void test_read_rcw_open_fails(void **state) { (void)state; const soc_info_t *soc = soc_db_find_by_compat("fsl,lx2160a"); uint8_t rcw_out[128]; uint32_t svr_out = 0; char err[256] = {0}; int rc = runtime_read_rcw(soc, "/nonexistent/path/qzzx", rcw_out, &svr_out, err, sizeof(err)); assert_int_not_equal(rc, 0); assert_non_null(strstr(err, "open")); } /* ------------------------------------------------------------------ */ /* runtime_sanity_run */ /* ------------------------------------------------------------------ */ static const sanity_entry_t * find_entry(const sanity_log_t *log, const char *id) { for (size_t i = 0; i < log->count; i++) if (strcmp(log->entries[i].id, id) == 0) return &log->entries[i]; return NULL; } static void test_sanity_all_pass_normal(void **state) { (void)state; const soc_info_t *soc = soc_db_find_by_compat("fsl,lx2160a"); uint8_t rcw[128]; fill_realistic_rcw(rcw); sanity_log_t log; runtime_sanity_run(soc, "match-detail", 0x87360120, rcw, &log); assert_false(log.any_failed); assert_int_equal(find_entry(&log, "dt_compat")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "svr_match")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "not_zero")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "not_ones")->status, SANITY_PASS); assert_int_equal(find_entry(&log, "endianness")->status, SANITY_PASS); /* informational warning is always present */ assert_int_equal(find_entry(&log, "pbi_not_recoverable")->status, SANITY_WARN); } static void test_sanity_all_zero_rcw_fails(void **state) { (void)state; const soc_info_t *soc = soc_db_find_by_compat("fsl,lx2160a"); uint8_t rcw[128] = {0}; sanity_log_t log; runtime_sanity_run(soc, "match-detail", 0x87360120, rcw, &log); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "not_zero")->status, SANITY_FAIL); } static void test_sanity_all_ones_rcw_fails(void **state) { (void)state; const soc_info_t *soc = soc_db_find_by_compat("fsl,lx2160a"); uint8_t rcw[128]; memset(rcw, 0xFF, sizeof(rcw)); sanity_log_t log; runtime_sanity_run(soc, "match-detail", 0x87360120, rcw, &log); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "not_ones")->status, SANITY_FAIL); } static void test_sanity_wrong_svr_fails(void **state) { (void)state; const soc_info_t *soc = soc_db_find_by_compat("fsl,lx2160a"); uint8_t rcw[128]; fill_realistic_rcw(rcw); sanity_log_t log; runtime_sanity_run(soc, "match-detail", 0xDEADBEEF, rcw, &log); assert_true(log.any_failed); assert_int_equal(find_entry(&log, "svr_match")->status, SANITY_FAIL); } static void test_sanity_byteswapped_svr_warns(void **state) { (void)state; const soc_info_t *soc = soc_db_find_by_compat("fsl,lx2160a"); uint8_t rcw[128]; fill_realistic_rcw(rcw); /* If the bus is wrong-endian we'd read 0x87360120 as 0x20013687. */ uint32_t bswapped = 0x20013687u; sanity_log_t log; runtime_sanity_run(soc, "match-detail", bswapped, rcw, &log); /* svr_match still FAILs (we don't auto-swap), and endianness WARNs * with a hint that the data may need swapping. */ assert_true(log.any_failed); const sanity_entry_t *e = find_entry(&log, "endianness"); assert_int_equal(e->status, SANITY_WARN); assert_non_null(strstr(e->detail, "byte-swapped")); } static void test_sanity_dt_unavailable_warns(void **state) { (void)state; const soc_info_t *soc = soc_db_find_by_compat("fsl,lx2160a"); uint8_t rcw[128]; fill_realistic_rcw(rcw); sanity_log_t log; runtime_sanity_run(soc, NULL, 0x87360120, rcw, &log); assert_int_equal(find_entry(&log, "dt_compat")->status, SANITY_WARN); /* The other checks still pass. */ assert_int_equal(find_entry(&log, "svr_match")->status, SANITY_PASS); assert_false(log.any_failed); } /* ------------------------------------------------------------------ */ /* runtime_write_header */ /* ------------------------------------------------------------------ */ static void test_write_header_contains_key_fields(void **state) { (void)state; const soc_info_t *soc = soc_db_find_by_compat("fsl,lx2160a"); uint8_t rcw[128]; fill_realistic_rcw(rcw); sanity_log_t log; runtime_sanity_run(soc, "fsl,lx2160a", 0x87360120, rcw, &log); char buf[8192]; FILE *out = fmemopen(buf, sizeof(buf), "w"); assert_non_null(out); runtime_write_header(out, soc, 0x87360120, &log, "0.1.0"); fclose(out); assert_non_null(strstr(buf, "Reset Configuration Word")); assert_non_null(strstr(buf, "NXP Layerscape LX2160A")); assert_non_null(strstr(buf, "fsl,lx2160a")); assert_non_null(strstr(buf, "0x87360120")); assert_non_null(strstr(buf, "0x01e00000")); assert_non_null(strstr(buf, "0x100")); /* RCWSR1 offset */ assert_non_null(strstr(buf, "qoriq-rcw 0.1.0")); assert_non_null(strstr(buf, "[PASS]")); assert_non_null(strstr(buf, "[WARN]")); /* pbi_not_recoverable */ } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_read_rcw_from_fixture), cmocka_unit_test(test_read_rcw_open_fails), cmocka_unit_test(test_sanity_all_pass_normal), cmocka_unit_test(test_sanity_all_zero_rcw_fails), cmocka_unit_test(test_sanity_all_ones_rcw_fails), cmocka_unit_test(test_sanity_wrong_svr_fails), cmocka_unit_test(test_sanity_byteswapped_svr_warns), cmocka_unit_test(test_sanity_dt_unavailable_warns), cmocka_unit_test(test_write_header_contains_key_fields), }; return cmocka_run_group_tests(tests, NULL, NULL); } vjardin-qoriq-rcw-f74d8cb/tests/test_soc_db.c000066400000000000000000000130751526741135700214130ustar00rootroot00000000000000/* * 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 "soc_db.h" static void test_compat_lookup(void **state) { (void)state; const soc_info_t *s = soc_db_find_by_compat("fsl,lx2160a"); assert_non_null(s); assert_string_equal(s->compat, "fsl,lx2160a"); assert_string_equal(s->default_rcwi, "lx2160a.rcwi"); assert_int_equal(s->dcfg_base, 0x01E00000); assert_int_equal(s->rcwsr1_offset, 0x100); assert_int_equal(s->svr_offset, 0xA4); assert_int_equal(s->rcw_bytes, 128); } static void test_compat_unknown(void **state) { (void)state; assert_null(soc_db_find_by_compat("fsl,acme-mips")); assert_null(soc_db_find_by_compat("")); assert_null(soc_db_find_by_compat(NULL)); } static void test_svr_lookup_masks_revision(void **state) { (void)state; /* LX2160A reset value 0x87360120 (rev 1.2 per the doc) - should * still match the entry whose svr_match is the family pattern. */ const soc_info_t *s = soc_db_find_by_svr(0x87360120); assert_non_null(s); assert_string_equal(s->compat, "fsl,lx2160a"); /* A different revision still matches. */ s = soc_db_find_by_svr(0x873601FF); assert_non_null(s); assert_string_equal(s->compat, "fsl,lx2160a"); } static void test_svr_lookup_distinguishes_families(void **state) { (void)state; assert_string_equal(soc_db_find_by_svr(0x87360100)->compat, "fsl,lx2160a"); assert_string_equal(soc_db_find_by_svr(0x87370100)->compat, "fsl,lx2120a"); assert_string_equal(soc_db_find_by_svr(0x87380100)->compat, "fsl,lx2080a"); assert_string_equal(soc_db_find_by_svr(0x870B0010)->compat, "fsl,ls1028a"); assert_string_equal(soc_db_find_by_svr(0x87030001)->compat, "fsl,ls1088a"); assert_string_equal(soc_db_find_by_svr(0x87090020)->compat, "fsl,ls2088a"); assert_null(soc_db_find_by_svr(0xDEADBEEF)); } static void test_lx2120_lx2080_share_lx2160_rcwi(void **state) { (void)state; assert_string_equal(soc_db_find_by_compat("fsl,lx2120a")->default_rcwi, "lx2160a.rcwi"); assert_string_equal(soc_db_find_by_compat("fsl,lx2080a")->default_rcwi, "lx2160a.rcwi"); } static void test_table_iteration_nonempty(void **state) { (void)state; size_t n = soc_db_count(); assert_true(n >= 4); for (size_t i = 0; i < n; i++) { const soc_info_t *s = soc_db_at(i); assert_non_null(s); assert_non_null(s->compat); assert_non_null(s->default_rcwi); } assert_null(soc_db_at(n)); } /* Write a NUL-separated DT compatible blob to a tmpfile, point * QORIQ_RCW_DT_COMPAT_FILE at it, and exercise soc_db_detect(). */ static char * make_dt_fixture(const char *const *entries, size_t n) { char tmpl[] = "/tmp/soc-dt-compatXXXXXX"; int fd = mkstemp(tmpl); assert_true(fd >= 0); for (size_t i = 0; i < n; i++) { size_t len = strlen(entries[i]) + 1; ssize_t w = write(fd, entries[i], len); assert_int_equal(w, (ssize_t)len); } close(fd); return strdup(tmpl); } static void test_detect_dt_match_first_entry(void **state) { (void)state; const char *e[] = { "fsl,lx2160a", "arm,armv8" }; char *fix = make_dt_fixture(e, 2); setenv("QORIQ_RCW_DT_COMPAT_FILE", fix, 1); char detail[256] = {0}; const soc_info_t *s = soc_db_detect(detail, sizeof(detail)); assert_non_null(s); assert_string_equal(s->compat, "fsl,lx2160a"); assert_non_null(strstr(detail, "fsl,lx2160a")); unsetenv("QORIQ_RCW_DT_COMPAT_FILE"); unlink(fix); free(fix); } static void test_detect_dt_match_later_entry(void **state) { (void)state; /* "Most specific" first per DT convention; first entry may be * board-specific (no match) and the second one the SoC. */ const char *e[] = { "vendor,my-board", "fsl,ls1028a", "arm,armv8" }; char *fix = make_dt_fixture(e, 3); setenv("QORIQ_RCW_DT_COMPAT_FILE", fix, 1); char detail[256] = {0}; const soc_info_t *s = soc_db_detect(detail, sizeof(detail)); assert_non_null(s); assert_string_equal(s->compat, "fsl,ls1028a"); unsetenv("QORIQ_RCW_DT_COMPAT_FILE"); unlink(fix); free(fix); } static void test_detect_no_match(void **state) { (void)state; const char *e[] = { "vendor,unknown", "arm,armv8" }; char *fix = make_dt_fixture(e, 2); setenv("QORIQ_RCW_DT_COMPAT_FILE", fix, 1); char detail[256] = {0}; const soc_info_t *s = soc_db_detect(detail, sizeof(detail)); assert_null(s); assert_non_null(strstr(detail, "no entry matched")); unsetenv("QORIQ_RCW_DT_COMPAT_FILE"); unlink(fix); free(fix); } static void test_detect_missing_dt_file(void **state) { (void)state; setenv("QORIQ_RCW_DT_COMPAT_FILE", "/nonexistent/path/xyzzy", 1); char detail[256] = {0}; const soc_info_t *s = soc_db_detect(detail, sizeof(detail)); assert_null(s); assert_non_null(strstr(detail, "could not open")); unsetenv("QORIQ_RCW_DT_COMPAT_FILE"); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_compat_lookup), cmocka_unit_test(test_compat_unknown), cmocka_unit_test(test_svr_lookup_masks_revision), cmocka_unit_test(test_svr_lookup_distinguishes_families), cmocka_unit_test(test_lx2120_lx2080_share_lx2160_rcwi), cmocka_unit_test(test_table_iteration_nonempty), cmocka_unit_test(test_detect_dt_match_first_entry), cmocka_unit_test(test_detect_dt_match_later_entry), cmocka_unit_test(test_detect_no_match), cmocka_unit_test(test_detect_missing_dt_file), }; return cmocka_run_group_tests(tests, NULL, NULL); }