diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2477c1b..6aef125 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,92 @@ -name: make all +name: build and check on: push: branches: [ master ] - pull_request: - branches: [ master ] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ fromJSON(vars.RK_RUNNER_LABELS || '["self-hosted","Linux","X64"]') }} + timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 with: submodules: recursive + persist-credentials: false - name: Install deps - run: sudo apt update && sudo apt install -y gcc make gcc-aarch64-linux-gnu libusb-1.0-0-dev device-tree-compiler - - name: make all - run: make SHELL=/bin/bash all -j`nproc` + run: sudo apt-get update && sudo apt-get install -y git gcc make gcc-aarch64-linux-gnu pkg-config libusb-1.0-0-dev device-tree-compiler python3 xxd xz-utils + - name: Clean-clone build and checks + run: make SHELL=/bin/bash check -j"$(nproc)" + + chainload: + name: chainload (${{ matrix.board }}) + runs-on: ${{ fromJSON(vars.RK_RUNNER_LABELS || '["self-hosted","Linux","X64"]') }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + board: [yy3568, rock3a] + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + persist-credentials: false + - name: Install U-Boot and firmware dependencies + run: >- + sudo apt-get update && sudo apt-get install -y + git gcc make gcc-aarch64-linux-gnu pkg-config bc bison flex swig + libssl-dev libgnutls28-dev python3 python3-dev python3-setuptools + python3-pyelftools device-tree-compiler libusb-1.0-0-dev xxd xz-utils + - name: Fetch manifest-pinned U-Boot source + shell: bash + env: + BOARD: ${{ matrix.board }} + run: | + set -euo pipefail + manifest=tools/chainload-manifest.py + repository=$(python3 "${manifest}" get "${BOARD}" uboot.repository) + ref=$(python3 "${manifest}" get "${BOARD}" uboot.ref) + commit=$(python3 "${manifest}" get "${BOARD}" uboot.commit) + source_dir=$(mktemp -d "${RUNNER_TEMP}/u-boot-${BOARD}.XXXXXX") + git init "${source_dir}" + git -C "${source_dir}" remote add origin "${repository}" + git -C "${source_dir}" fetch --depth=1 origin "${ref}" + git -C "${source_dir}" cat-file -e "${commit}^{commit}" + test "$(git -C "${source_dir}" rev-parse 'FETCH_HEAD^{commit}')" = "${commit}" + echo "UBOOT_SRC=${source_dir}" >> "${GITHUB_ENV}" + - name: Build twice and verify reproducibility + shell: bash + env: + BOARD: ${{ matrix.board }} + run: | + set -euo pipefail + make SHELL=/bin/bash chainload BOARD="${BOARD}" -j"$(nproc)" + read -ra first_outputs <<< "$(python3 tools/chainload-manifest.py artifacts "${BOARD}")" + for artifact in "${first_outputs[@]}"; do + cp "${artifact}" "${RUNNER_TEMP}/first-${artifact}" + done + make SHELL=/bin/bash clean + make SHELL=/bin/bash chainload BOARD="${BOARD}" -j"$(nproc)" + for artifact in "${first_outputs[@]}"; do + cmp "${RUNNER_TEMP}/first-${artifact}" "${artifact}" + done + + mkimage="build/chainload/${BOARD}/source/tools/mkimage" + ddr=$(python3 tools/chainload-manifest.py get "${BOARD}" boot_media.ddr) + binary=$(python3 tools/chainload-manifest.py get "${BOARD}" artifacts.binary) + idblock=$(python3 tools/chainload-manifest.py get "${BOARD}" artifacts.idblock) + spi=$(python3 tools/chainload-manifest.py get "${BOARD}" artifacts.spi_nor) + "${mkimage}" -n rk3568 -T rksd -d "${ddr}:${binary}" \ + "${RUNNER_TEMP}/reference-idblock.img" + "${mkimage}" -n rk3568 -T rkspi -d "${ddr}:${binary}" \ + "${RUNNER_TEMP}/reference-spi.img" + cmp "${RUNNER_TEMP}/reference-idblock.img" "${idblock}" + cmp "${RUNNER_TEMP}/reference-spi.img" "${spi}" + make SHELL=/bin/bash chainload-check BOARD="${BOARD}" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cd9450a..43343f3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,15 +1,24 @@ name: docs + on: push: branches: - master - - main + +permissions: + contents: write + jobs: deploy: - runs-on: ubuntu-latest + runs-on: ${{ fromJSON(vars.RK_RUNNER_LABELS || '["self-hosted","Linux","X64"]') }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.x - - run: pip3 install mkdocs pymdown-extensions --break-system-packages && mkdocs gh-deploy --force + - uses: actions/checkout@v7 + - name: Install documentation dependencies + run: | + sudo apt-get update + sudo apt-get install -y python3 python3-pip python3-venv + python3 -m venv "${RUNNER_TEMP}/docs-venv" + "${RUNNER_TEMP}/docs-venv/bin/pip" install mkdocs pymdown-extensions + - name: Deploy documentation + run: | + "${RUNNER_TEMP}/docs-venv/bin/mkdocs" gh-deploy --force diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..18d5b26 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,185 @@ +name: release + +on: + push: + tags: + - 'v*.*.*' + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + runs-on: ${{ fromJSON(vars.RK_RUNNER_LABELS || '["self-hosted","Linux","X64"]') }} + timeout-minutes: 120 + permissions: + contents: write + steps: + - name: Check out tagged source + uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive + persist-credentials: false + + - name: Validate release tag + id: metadata + shell: bash + run: | + set -euo pipefail + tag=${GITHUB_REF_NAME} + if [[ ! ${tag} =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + echo "release tags must use stable SemVer: vMAJOR.MINOR.PATCH" >&2 + exit 1 + fi + + tag_commit=$(git rev-list -n 1 "${tag}") + if [[ $(git rev-parse HEAD) != "${tag_commit}" ]]; then + echo "checked-out commit does not match ${tag}" >&2 + exit 1 + fi + if ! git show-ref --verify --quiet refs/remotes/origin/master; then + echo "origin/master was not fetched by the full checkout" >&2 + exit 1 + fi + if ! git merge-base --is-ancestor "${tag_commit}" refs/remotes/origin/master; then + echo "${tag} does not point to a commit contained in origin/master" >&2 + exit 1 + fi + + echo "version=${tag}" >> "${GITHUB_OUTPUT}" + + - name: Install build dependencies + run: >- + sudo apt-get update && sudo apt-get install -y + git gh gcc make gcc-aarch64-linux-gnu pkg-config libusb-1.0-0-dev + device-tree-compiler python3 python3-dev python3-setuptools + python3-pyelftools bc bison flex swig libssl-dev libgnutls28-dev + xxd xz-utils + + - name: Build and test tagged source + run: make SHELL=/bin/bash check -j"$(nproc)" + + - name: Build release assets + shell: bash + run: | + set -euo pipefail + make SHELL=/bin/bash chainload BOARD=yy3568 -j"$(nproc)" + make SHELL=/bin/bash chainload-check BOARD=yy3568 + make SHELL=/bin/bash chainload BOARD=rock3a -j"$(nproc)" + make SHELL=/bin/bash chainload-check BOARD=rock3a + CHAINLOAD_RELEASE=1 make SHELL=/bin/bash release-dist \ + VERSION="${{ steps.metadata.outputs.version }}" DIST_DIR=dist -j"$(nproc)" + + - name: Verify release assets + shell: bash + run: | + set -euo pipefail + version=${{ steps.metadata.outputs.version }} + expected=( + "SHA256SUMS" + "rk-${version}-genbook.tar.xz" + "rk-${version}-pinebook-pro.tar.xz" + "rk-${version}-roc3566.tar.xz" + "rk-${version}-rock3a.tar.xz" + "rk-${version}-yy3568.tar.xz" + ) + mapfile -t actual < <(find dist -mindepth 1 -maxdepth 1 -type f -printf '%f\n' | sort) + mapfile -t sorted_expected < <(printf '%s\n' "${expected[@]}" | sort) + diff -u <(printf '%s\n' "${sorted_expected[@]}") <(printf '%s\n' "${actual[@]}") + (cd dist && sha256sum -c SHA256SUMS) + + - name: Publish GitHub release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + VERSION: ${{ steps.metadata.outputs.version }} + run: | + set -euo pipefail + + assets=( + "dist/rk-${VERSION}-genbook.tar.xz" + "dist/rk-${VERSION}-pinebook-pro.tar.xz" + "dist/rk-${VERSION}-roc3566.tar.xz" + "dist/rk-${VERSION}-rock3a.tar.xz" + "dist/rk-${VERSION}-yy3568.tar.xz" + "dist/SHA256SUMS" + ) + + load_release_record() { + local repo_owner=${GITHUB_REPOSITORY%%/*} + local repo_name=${GITHUB_REPOSITORY#*/} + local record + local query + release_id= + release_draft= + query='query($owner:String!,$name:String!,$tag:String!){repository(owner:$owner,name:$name){release(tagName:$tag){databaseId isDraft}}}' + record=$(gh api graphql \ + -f query="${query}" \ + -F owner="${repo_owner}" \ + -F name="${repo_name}" \ + -F tag="${VERSION}" \ + --jq '.data.repository.release | select(. != null) | [.databaseId, .isDraft] | @tsv') + [[ -n ${record} ]] || return 0 + IFS=$'\t' read -r release_id release_draft <<< "${record}" + if [[ ! ${release_id} =~ ^[0-9]+$ ]] || + [[ ${release_draft} != true && ${release_draft} != false ]]; then + echo "GitHub returned malformed release metadata for ${VERSION}" >&2 + exit 1 + fi + } + + compare_remote_assets() { + local release_id=$1 + local local_assets=${RUNNER_TEMP}/local-release-assets.tsv + local remote_assets=${RUNNER_TEMP}/remote-release-assets.tsv + for asset in "${assets[@]}"; do + printf '%s\t%s\n' "$(basename "${asset}")" "$(stat -c %s "${asset}")" + done | sort > "${local_assets}" + gh api "repos/${GITHUB_REPOSITORY}/releases/${release_id}" \ + --jq '.assets[] | [.name, (.size | tostring)] | @tsv' | + sort > "${remote_assets}" + diff -u "${local_assets}" "${remote_assets}" + } + + load_release_record + if [[ -n ${release_id} ]]; then + if [[ ${release_draft} != true ]]; then + echo "published release ${VERSION} already exists and will not be modified" + compare_remote_assets "${release_id}" + + published_dir=$(mktemp -d "${RUNNER_TEMP}/published-release.XXXXXX") + gh release download "${VERSION}" --dir "${published_dir}" + if ! cmp -s dist/SHA256SUMS "${published_dir}/SHA256SUMS"; then + echo "published release ${VERSION} has a different SHA256SUMS" >&2 + exit 1 + fi + (cd "${published_dir}" && sha256sum -c SHA256SUMS) + echo "published release ${VERSION} matches the rebuilt assets; retry is complete" + exit 0 + fi + echo "removing stale unpublished draft for ${VERSION}" + gh release delete "${VERSION}" --yes + fi + + draft_url=$(gh release create "${VERSION}" "${assets[@]}" \ + --verify-tag \ + --draft \ + --generate-notes \ + --title "rk ${VERSION}" \ + --notes "Download the archive for your board and verify it with SHA256SUMS.") + printf '%s\n' "${draft_url}" + + load_release_record + if [[ -z ${release_id} || ${release_draft} != true ]]; then + echo "new draft for ${VERSION} was not returned by GitHub's pending-tag lookup" >&2 + exit 1 + fi + compare_remote_assets "${release_id}" + + gh release edit "${VERSION}" --draft=false --latest diff --git a/.gitignore b/.gitignore index 2c98549..6a71d8a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,8 @@ graveyard config.mk rk3588-drivers *.out.h +/dist/ +/build/ +*.itb +*-u-boot-source.tar.xz +__pycache__/ diff --git a/Chainload-rk356x.ld b/Chainload-rk356x.ld new file mode 100644 index 0000000..fbfce59 --- /dev/null +++ b/Chainload-rk356x.ld @@ -0,0 +1,41 @@ +ENTRY(_start) + +SECTIONS { + . = 0x00000000; + . = ALIGN(16); + .text : { + _text_start = .; + *(.text .text.*) + _text_end = .; + } + . = ALIGN(16); + .rodata : { + _rodata_start = .; + *(.rodata .rodata.*) + _rodata_end = .; + } + . = ALIGN(16); + .data : { + _data_start = .; + *(.data .data.*) + _data_end = .; + } + . = ALIGN(4096); + .bss : { + _bss_start = .; + *(.bss .bss.* COMMON) + _bss_end = .; + } + . = ALIGN(16); + .got : { *(.got .got.*) } + . = ALIGN(16); + _end_of_image = .; + _fit_start = .; + /DISCARD/ : { + *(.comment .comment.*) + *(.note .note.*) + *(.eh_frame .eh_frame.*) + } + ASSERT(_end_of_image < 0x00040000, + "RK356x chainloader exceeds the first BL31 load address") +} diff --git a/Makefile b/Makefile index 053d544..d15daaf 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,33 @@ # Needed to compile targets of different architectures convert_target_arm64 = $(patsubst %.o,%.arm64.o,$1) +convert_target_rk356x = $(patsubst %.o,%.rk356x.o,$1) + +# Chainloader objects deliberately live in a board-and-variant namespace. +CHAINLOAD_BOARDS := yy3568 rock3a +CHAINLOAD_MANIFEST := tools/chainload-manifest.py +chain_c_obj = $(patsubst %.c,build/chainload/$(1)/obj/%.o,$(filter %.c,$(2))) +chain_s_obj = $(patsubst %.S,build/chainload/$(1)/obj/%.o,$(filter %.S,$(2))) +chain_get = $(shell python3 $(CHAINLOAD_MANIFEST) get $(1) $(2)) XROCK ?= xrock +RKDEVELOPTOOL ?= rkdeveloptool ARMCC ?= aarch64-linux-gnu +DIST_DIR ?= dist + +RELEASE_ARTIFACTS := pinebook.bin demo_pinebook.bin pinebook.img demo_pinebook.img +RELEASE_ARTIFACTS += pinebook-ddr.bin pinebook-poc-ddr.bin +RELEASE_ARTIFACTS += genbook.bin demo_genbook.bin genbook.img genbook_demo.img genbook-ddr.bin +RELEASE_ARTIFACTS += roc3566.bin demo_roc3566.bin roc3566.img demo_roc3566.img +RELEASE_ARTIFACTS += yy3568.bin demo_yy3568.bin yy3568.img demo_yy3568.img +RELEASE_ARTIFACTS += rock3a.bin demo_rock3a.bin rock3a.img demo_rock3a.img all: makeboot.out rock.out pinebook.bin pinebook-ddr.bin opi5.bin genbook.bin genbook-ddr.bin genbook_demo.img demo_pinebook.img all: pinebook.img genbook.img +all: roc3566.bin demo_roc3566.bin roc3566.img demo_roc3566.img +all: yy3568.bin demo_yy3568.bin yy3568.img demo_yy3568.img +all: rock3a.bin demo_rock3a.bin rock3a.img demo_rock3a.img -ARMCFLAGS := -march=armv8-a -nostdlib -Wall -Wno-array-bounds -Isrc -Isrc/rk3399 -Isrc/rk3588 -ffunction-sections -ffreestanding +ARMCFLAGS := -march=armv8-a -nostdlib -Wall -Wno-array-bounds -Isrc -Isrc/rk3399 -Isrc/rk3588 -Isrc/rk356x -ffunction-sections -ffreestanding ARMLDFLAGS := -T Linker.ld --gc-sections # Align+pad to _end_of_image defined in linker script OBJCOPYFLAGS = --pad-to 0x`readelf -s src/$@.elf | awk '/_end_of_image/ {print $$2}'` @@ -22,14 +42,14 @@ $(call convert_target_arm64,src/rk3399/ram2.o): ARMCFLAGS += -Os GENBOOK_DDR_OBJ := $(call convert_target_arm64,src/rk3588/ddr.o src/rk3588/genbook-ddr.o src/rk3588/gpio.o src/rk3588/pwm.o src/lib.o) 3399_OBJ := src/boot.o src/mmu.o src/asm.o src/pl011.o src/vectors.o src/rk3399/gpio.o src/rk3399/timer.o src/analogix_edp.o src/rk3399/vop.o src/firmware.o -3399_OBJ += src/rk3399/clock.o src/rk3399/soc.o src/lib.o src/ohci.o src/rk3399/mmc.o src/rk3399/io.o +3399_OBJ += src/rk3399/clock.o src/rk3399/soc.o src/lib.o src/ohci.o src/hid_keyboard.o src/input.o src/rk3399/mmc.o src/rk3399/io.o PINEBOOK_OBJ := $(3399_OBJ) src/pinebook.o PINEBOOK_OBJ := $(call convert_target_arm64,$(PINEBOOK_OBJ)) $(PINEBOOK_OBJ): src/rk3399/pinebook.dtb.out.h 3588_OBJ := src/boot.o src/rk3588/io.o src/rk3588/sgrf.o src/rk3588/ioc.o src/rk3588/pmu.o src/rk3588/cru.o src/rk3588/vop2.o src/rk3588/video.o src/rk3588/gpio.o src/rk3588/pwm.o -3588_OBJ += src/pl011.o src/asm.o src/vectors.o src/mmu.o src/lib.o src/firmware.o src/analogix_edp.o +3588_OBJ += src/pl011.o src/asm.o src/vectors.o src/mmu.o src/lib.o src/firmware.o src/input.o src/analogix_edp.o 3588_OBJ += external/samsung_phy_edp.o OPI5_OBJ := $(3588_OBJ) src/opi5.o @@ -39,6 +59,44 @@ GENBOOK_OBJ := $(3588_OBJ) src/genbook.o GENBOOK_OBJ := $(call convert_target_arm64,$(GENBOOK_OBJ)) $(GENBOOK_OBJ): src/rk3588/genbook.dtb.out.h +RK356X_OBJ := src/boot.o src/mmu.o src/asm.o src/pl011.o src/vectors.o src/lib.o +RK356X_OBJ += src/firmware.o src/input.o src/hid_keyboard.o src/ohci.o src/edid.o +RK356X_OBJ += src/rk356x/board.o src/rk356x/io.o src/rk356x/dram.o src/rk356x/gpio.o src/rk356x/sgrf.o +RK356X_OBJ += src/rk356x/cru.o src/rk356x/vop2.o src/rk356x/hdmi.o src/rk356x/usb.o +RK356X_OBJ := $(call convert_target_rk356x,$(RK356X_OBJ)) + +ROC3566_OBJ := $(RK356X_OBJ) src/roc3566.rk356x.o +YY3568_OBJ := $(RK356X_OBJ) src/yy3568.rk356x.o +ROCK3A_OBJ := $(RK356X_OBJ) src/rock3a.rk356x.o +$(ROC3566_OBJ): src/rk356x/roc3566.dtb.out.h +$(YY3568_OBJ): src/rk356x/yy3568.dtb.out.h +$(ROCK3A_OBJ): src/rk356x/rock3a.dtb.out.h + +CHAINLOAD_SRC := src/boot.S src/asm.S src/vectors.S src/mmu.c src/lib.c src/pl011.c +CHAINLOAD_SRC += src/chainload/compat.c src/chainload/sha256.c src/chainload/fit.c +CHAINLOAD_SRC += src/chainload/tf_a.c src/chainload/loader.c src/chainload/handoff.S +CHAINLOAD_SRC += src/chainload/rk3568.c +CHAINLOAD_SRC += external/libfdt/fdt.c external/libfdt/fdt_ro.c +CHAINLOAD_SRC += external/libfdt/fdt_rw.c external/libfdt/fdt_wip.c +CHAINLOAD_CFLAGS := $(ARMCFLAGS) -Isrc/chainload -Iexternal/libfdt -Os -fdata-sections +CHAINLOAD_CFLAGS += -fno-unwind-tables -fno-asynchronous-unwind-tables +CHAINLOAD_CFLAGS += -DSTACK_TOP=0x08000000 + +define CHAINLOAD_BOARD_VARIABLES +CHAINLOAD_OBJ_$(1) := $(call chain_c_obj,$(1),$(CHAINLOAD_SRC)) $(call chain_s_obj,$(1),$(CHAINLOAD_SRC)) +CHAINLOAD_FIT_$(1) := $(call chain_get,$(1),artifacts.fit) +CHAINLOAD_BINARY_$(1) := $(call chain_get,$(1),artifacts.binary) +CHAINLOAD_IMAGE_$(1) := $(call chain_get,$(1),artifacts.image) +CHAINLOAD_IDBLOCK_$(1) := $(call chain_get,$(1),artifacts.idblock) +CHAINLOAD_SPI_$(1) := $(call chain_get,$(1),artifacts.spi_nor) +CHAINLOAD_SOURCE_$(1) := $(call chain_get,$(1),artifacts.source) +CHAINLOAD_DDR_$(1) := $(call chain_get,$(1),boot_media.ddr) +CHAINLOAD_BL31_$(1) := $(call chain_get,$(1),bl31.path) +CHAINLOAD_MEDIA_ARTIFACTS_$(1) := $$(CHAINLOAD_IMAGE_$(1)) $$(CHAINLOAD_IDBLOCK_$(1)) $$(CHAINLOAD_SPI_$(1)) +endef + +$(foreach board,$(CHAINLOAD_BOARDS),$(eval $(call CHAINLOAD_BOARD_VARIABLES,$(board)))) + DEMO_OBJ := demo/entry.o demo/main.o demo/bmp.o demo/vectors.o DEMO_OBJ := $(call convert_target_arm64,$(DEMO_OBJ)) @@ -81,9 +139,21 @@ genbook.bin: $(GENBOOK_OBJ) Linker.ld $(ARMCC)-ld $(GENBOOK_OBJ) $(ARMLDFLAGS) -o src/$@.elf $(ARMCC)-objcopy $(OBJCOPYFLAGS) -O binary src/$@.elf genbook.bin +roc3566.bin: $(ROC3566_OBJ) Linker.ld + $(ARMCC)-ld $(ROC3566_OBJ) $(ARMLDFLAGS) -o src/$@.elf + $(ARMCC)-objcopy $(OBJCOPYFLAGS) -O binary src/$@.elf $@ + +yy3568.bin: $(YY3568_OBJ) Linker.ld + $(ARMCC)-ld $(YY3568_OBJ) $(ARMLDFLAGS) -o src/$@.elf + $(ARMCC)-objcopy $(OBJCOPYFLAGS) -O binary src/$@.elf $@ + +rock3a.bin: $(ROCK3A_OBJ) Linker.ld + $(ARMCC)-ld $(ROCK3A_OBJ) $(ARMLDFLAGS) -o src/$@.elf + $(ARMCC)-objcopy $(OBJCOPYFLAGS) -O binary src/$@.elf $@ + demo.bin: $(DEMO_OBJ) $(ARMCC)-ld $(DEMO_OBJ) -Ttext=0xa00000 -o src/$@.elf - $(ARMCC)-objcopy -O binary src/$@.elf demo.bin + $(ARMCC)-objcopy --gap-fill 0 --pad-to 0x`readelf -s src/$@.elf | awk '$$8 == "_end" {print $$2}'` -O binary src/$@.elf demo.bin demo_pinebook.bin: demo.bin pinebook.bin cat pinebook.bin demo.bin > demo_pinebook.bin @@ -91,6 +161,130 @@ demo_pinebook.bin: demo.bin pinebook.bin demo_genbook.bin: demo.bin genbook.bin cat genbook.bin demo.bin > demo_genbook.bin +demo_roc3566.bin: demo.bin roc3566.bin + cat roc3566.bin demo.bin > $@ + +demo_yy3568.bin: demo.bin yy3568.bin + cat yy3568.bin demo.bin > $@ + +demo_rock3a.bin: demo.bin rock3a.bin + cat rock3a.bin demo.bin > $@ + +roc3566.img: makeboot.out img/rk3566_ddr_1056MHz_v1.25.bin roc3566.bin + ./makeboot.out --v2 --ddr img/rk3566_ddr_1056MHz_v1.25.bin --os roc3566.bin -o $@ + +demo_roc3566.img: makeboot.out img/rk3566_ddr_1056MHz_v1.25.bin demo_roc3566.bin + ./makeboot.out --v2 --ddr img/rk3566_ddr_1056MHz_v1.25.bin --os demo_roc3566.bin -o $@ + +yy3568.img: makeboot.out img/rk3568_ddr_1560MHz_v1.25.bin yy3568.bin + ./makeboot.out --v2 --ddr img/rk3568_ddr_1560MHz_v1.25.bin --os yy3568.bin -o $@ + +demo_yy3568.img: makeboot.out img/rk3568_ddr_1560MHz_v1.25.bin demo_yy3568.bin + ./makeboot.out --v2 --ddr img/rk3568_ddr_1560MHz_v1.25.bin --os demo_yy3568.bin -o $@ + +rock3a.img: makeboot.out img/rk3568_ddr_1560MHz_v1.25.bin rock3a.bin + ./makeboot.out --v2 --ddr img/rk3568_ddr_1560MHz_v1.25.bin --os rock3a.bin -o $@ + +demo_rock3a.img: makeboot.out img/rk3568_ddr_1560MHz_v1.25.bin demo_rock3a.bin + ./makeboot.out --v2 --ddr img/rk3568_ddr_1560MHz_v1.25.bin --os demo_rock3a.bin -o $@ + +define CHAINLOAD_BOARD_RULES +build/chainload/$(1)/generated/board_config.h: config/chainload/$(1).json $(CHAINLOAD_MANIFEST) + @mkdir -p $$(@D) + python3 $(CHAINLOAD_MANIFEST) generate-header $(1) $$@ + +$$(CHAINLOAD_OBJ_$(1)): build/chainload/$(1)/generated/board_config.h + +build/chainload/$(1)/obj/%.o: %.c + @mkdir -p $$(@D) + $$(ARMCC)-gcc -MMD -c $$< $$(CHAINLOAD_CFLAGS) -Ibuild/chainload/$(1)/generated -o $$@ + +build/chainload/$(1)/obj/%.o: %.S + @mkdir -p $$(@D) + $$(ARMCC)-gcc -D__ASM__ -MMD -c $$< $$(CHAINLOAD_CFLAGS) -Ibuild/chainload/$(1)/generated -o $$@ + +build/chainload/$(1)/stage.elf: $$(CHAINLOAD_OBJ_$(1)) Chainload-rk356x.ld + @mkdir -p $$(@D) + $$(ARMCC)-ld $$(CHAINLOAD_OBJ_$(1)) -T Chainload-rk356x.ld --gc-sections -o $$@ + +build/chainload/$(1)/stage.bin: build/chainload/$(1)/stage.elf + $$(ARMCC)-objcopy --gap-fill 0 --pad-to 0x`$$(ARMCC)-readelf -s $$< | awk '$$$$8 == "_end_of_image" {print $$$$2}'` -O binary $$< $$@ + +build/chainload/$(1)/.uboot-built: config/chainload/$(1).json \ + $$(shell find config/chainload/$(1) -type f) $$(CHAINLOAD_BL31_$(1)) \ + tools/build-chainload-uboot.sh $(CHAINLOAD_MANIFEST) + @mkdir -p $$(@D) + bash tools/build-chainload-uboot.sh $(1) + @touch $$@ + +$$(CHAINLOAD_FIT_$(1)) $$(CHAINLOAD_SOURCE_$(1)): build/chainload/$(1)/.uboot-built + @test -f $$@ || { echo "$$@ was not generated" >&2; exit 2; } + +$$(CHAINLOAD_BINARY_$(1)): build/chainload/$(1)/stage.bin $$(CHAINLOAD_FIT_$(1)) + cat $$^ > $$@ + +build/chainload/$(1)/.media-built: $$(CHAINLOAD_BINARY_$(1)) $$(CHAINLOAD_DDR_$(1)) \ + config/chainload/$(1).json tools/build-chainload-media.sh $(CHAINLOAD_MANIFEST) + bash tools/build-chainload-media.sh $(1) + @touch $$@ + +$$(CHAINLOAD_MEDIA_ARTIFACTS_$(1)): build/chainload/$(1)/.media-built + @if ! test -f $$@; then \ + rm -f build/chainload/$(1)/.media-built; \ + $$(MAKE) build/chainload/$(1)/.media-built; \ + fi + @test -f $$@ || { echo "$$@ was not generated" >&2; exit 2; } +endef + +$(foreach board,$(CHAINLOAD_BOARDS),$(eval $(call CHAINLOAD_BOARD_RULES,$(board)))) + +tools/chainfit.out: tools/chainfit.c src/chainload/fit.c src/chainload/sha256.c \ + src/chainload/compat.c \ + external/libfdt/fdt.c external/libfdt/fdt_ro.c external/libfdt/fdt_rw.c \ + external/libfdt/fdt_wip.c + $(CC) -std=c11 -D_POSIX_C_SOURCE=200809L -Wall -Wextra \ + -Isrc -Isrc/chainload -Iexternal/libfdt $^ -o $@ + +chainload: + @test -n "$(BOARD)" || { echo "BOARD is required" >&2; exit 2; } + @python3 $(CHAINLOAD_MANIFEST) validate "$(BOARD)" + @artifacts="$$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" artifacts.binary) $$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" artifacts.image) $$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" artifacts.idblock) $$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" artifacts.spi_nor)"; \ + $(MAKE) $$artifacts + +chainload-media: + @test -n "$(BOARD)" || { echo "BOARD is required" >&2; exit 2; } + @test -n "$(MEDIA)" || { echo "MEDIA is required" >&2; exit 2; } + @python3 $(CHAINLOAD_MANIFEST) validate "$(BOARD)" + @artifact=$$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" "boot_media.$(MEDIA).artifact" 2>/dev/null) || { echo "board '$(BOARD)' does not support media '$(MEDIA)'" >&2; exit 2; }; \ + $(MAKE) "$$artifact" + +flash-chainload: + @test -n "$(BOARD)" -a -n "$(MEDIA)" -a -n "$(BACKUP_DIR)" -a -n "$(CONFIRM)" || \ + { echo "BOARD, MEDIA, BACKUP_DIR, and CONFIRM are required" >&2; exit 2; } + $(MAKE) chainload-media BOARD="$(BOARD)" MEDIA="$(MEDIA)" + XROCK="$(XROCK)" RKDEVELOPTOOL="$(RKDEVELOPTOOL)" bash tools/flash-chainload.sh \ + flash "$(BOARD)" "$(MEDIA)" "$(BACKUP_DIR)" "$(CONFIRM)" + +restore-chainload: + @test -n "$(BACKUP)" -a -n "$(CONFIRM)" || \ + { echo "BACKUP and CONFIRM are required" >&2; exit 2; } + XROCK="$(XROCK)" RKDEVELOPTOOL="$(RKDEVELOPTOOL)" bash tools/flash-chainload.sh \ + restore "$(BACKUP)" "$(CONFIRM)" + +chainload-check: + @test -n "$(BOARD)" || { echo "BOARD is required" >&2; exit 2; } + @python3 $(CHAINLOAD_MANIFEST) validate "$(BOARD)" + @binary=$$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" artifacts.binary); \ + $(MAKE) "$$binary" tools/chainfit.out + @if test -z "$(UBOOT_ITB)"; then \ + media="$$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" artifacts.image) $$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" artifacts.idblock) $$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" artifacts.spi_nor)"; \ + $(MAKE) $$media; \ + fi + @fit=$$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" artifacts.fit); \ + args=$$(python3 $(CHAINLOAD_MANIFEST) chainfit-args "$(BOARD)"); \ + ./tools/chainfit.out "$(BOARD)" "$$fit" $$args + UBOOT_ITB="$(UBOOT_ITB)" python3 tests/check_chainload.py --board "$(BOARD)" + makeboot.out: tools/makeboot.o $(CC) tools/makeboot.o -o makeboot.out @@ -103,6 +297,10 @@ rock.out: tools/rock.o $(ARMCC)-gcc -MMD -c $< $(ARMCFLAGS) -o $@ %.arm64.o: %.S $(ARMCC)-gcc -D __ASM__ -MMD -c $< $(ARMCFLAGS) -o $@ +%.rk356x.o: %.c + $(ARMCC)-gcc -MMD -c $< $(ARMCFLAGS) -DSTACK_TOP=0x08000000 -o $@ +%.rk356x.o: %.S + $(ARMCC)-gcc -D __ASM__ -MMD -c $< $(ARMCFLAGS) -DSTACK_TOP=0x08000000 -o $@ %.dtb.out.h: %.dts set -o pipefail; cpp -nostdinc -undef -x assembler-with-cpp $< | dtc | xxd -i -n dtb_data > $@ @@ -110,8 +308,8 @@ rock.out: tools/rock.o -include $(wildcard **/*.d) clean: - find src demo tools \( -name '*.d' -o -name '*.o' -o -name '*.elf' -o -name '*.bin' -o -name '*.out.h' \) -type f -delete - rm -rf *.bin *.elf *.out *.img *.d + find src demo tools tests \( -name '*.d' -o -name '*.o' -o -name '*.elf' -o -name '*.bin' -o -name '*.out.h' -o -name '*.out' \) -type f -delete + rm -rf *.bin *.elf *.out *.img *.d *.itb *-u-boot-source.tar.xz build/chainload usb3399: rock.out pinebook-poc-ddr.bin demo_pinebook.bin ./rock.out --v1 --ddr pinebook-poc-ddr.bin --os demo_pinebook.bin @@ -119,6 +317,35 @@ usb3399: rock.out pinebook-poc-ddr.bin demo_pinebook.bin usb3588: rock.out genbook-ddr.bin demo_genbook.bin ./rock.out --v2 --ddr genbook-ddr.bin --os demo_genbook.bin +usb3566: rock.out img/rk3566_ddr_1056MHz_v1.25.bin demo_roc3566.bin + ./rock.out --v2 --ddr img/rk3566_ddr_1056MHz_v1.25.bin --os demo_roc3566.bin + +usb: + @test -n "$(BOARD)" || { echo "BOARD is required" >&2; exit 2; } + @case "$(BOARD)" in \ + roc3566) $(MAKE) usb3566 ;; \ + yy3568) $(MAKE) rock.out img/rk3568_ddr_1560MHz_v1.25.bin demo_yy3568.bin && \ + ./rock.out --v2 --ddr img/rk3568_ddr_1560MHz_v1.25.bin --os demo_yy3568.bin ;; \ + rock3a) $(MAKE) rock.out img/rk3568_ddr_1560MHz_v1.25.bin demo_rock3a.bin && \ + ./rock.out --v2 --ddr img/rk3568_ddr_1560MHz_v1.25.bin --os demo_rock3a.bin ;; \ + *) echo "board '$(BOARD)' has no RK356x USB target" >&2; exit 2 ;; \ + esac + +usb-chainload: + @test -n "$(BOARD)" || { echo "BOARD is required" >&2; exit 2; } + @python3 $(CHAINLOAD_MANIFEST) validate "$(BOARD)" + @ddr=$$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" boot_media.ddr); \ + binary=$$(python3 $(CHAINLOAD_MANIFEST) get "$(BOARD)" artifacts.binary); \ + $(MAKE) rock.out "$$ddr" "$$binary" && ./rock.out --v2 --ddr "$$ddr" --os "$$binary" + +usb3568: + @echo "warning: usb3568 is a deprecated alias for 'make usb BOARD=yy3568'" >&2 + @$(MAKE) usb BOARD=yy3568 + +usb3568-uboot: + @echo "warning: usb3568-uboot is a deprecated alias for 'make usb-chainload BOARD=yy3568'" >&2 + @$(MAKE) usb-chainload BOARD=yy3568 + dmesg: sudo dmesg -w uart: @@ -132,6 +359,36 @@ bear: maskrom3588: xrock maskrom img/rk3588_ddr_lp4_2112MHz_lp5_2400MHz_v1.16.bin img/rk3588_usbplug_v1.11.bin --rc4-off -.PHONY: usb clean dmesg uart uart2 bear maskrom3588 +maskrom3566: + $(XROCK) maskrom img/rk3566_ddr_1056MHz_v1.25.bin img/rk356x_usbplug_v1.17.bin --rc4-off + +maskrom3568: + $(XROCK) maskrom img/rk3568_ddr_1560MHz_v1.25.bin img/rk356x_usbplug_v1.17.bin --rc4-off + +check: all build/chainload/yy3568/stage.bin build/chainload/rock3a/stage.bin + $(CC) -std=c11 -Wall -Wextra -ffunction-sections -fdata-sections -Isrc \ + tests/unit.c tests/host_stubs.c src/edid.c src/input.c src/hid_keyboard.c src/ohci.c \ + -Wl,--gc-sections -o tests/unit.out + ./tests/unit.out + $(CC) -std=c11 -D_POSIX_C_SOURCE=200809L -Wall -Wextra \ + -Isrc -Isrc/chainload -Iexternal/libfdt \ + tests/chainload_unit.c src/chainload/fit.c src/chainload/sha256.c src/chainload/tf_a.c \ + src/chainload/loader.c src/chainload/compat.c \ + external/libfdt/fdt.c external/libfdt/fdt_ro.c external/libfdt/fdt_rw.c \ + external/libfdt/fdt_wip.c external/libfdt/fdt_sw.c external/libfdt/fdt_empty_tree.c \ + -o tests/chainload-unit.out + ./tests/chainload-unit.out + python3 tests/check.py + python3 tests/check_chainload.py --offline + python3 tests/check_chainload_flash.py + python3 tests/check_release.py + +release-dist: $(RELEASE_ARTIFACTS) + @test -n "$(VERSION)" || { echo "VERSION is required (vMAJOR.MINOR.PATCH)" >&2; exit 2; } + bash tools/release-dist.sh "$(VERSION)" "$(DIST_DIR)" + +.PHONY: all check release-dist clean chainload chainload-media chainload-check flash-chainload restore-chainload usb usb-chainload usb3399 usb3588 usb3566 usb3568 usb3568-uboot dmesg uart uart2 bear maskrom3588 maskrom3566 maskrom3568 + +-include $(foreach board,$(CHAINLOAD_BOARDS),$(CHAINLOAD_OBJ_$(board):.o=.d)) -include config.mk diff --git a/README.md b/README.md index fa5e24f..2c96126 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,174 @@ -# Bare Metal Rockchip Bootloader +# Bare-Metal Rockchip Firmware -This is a very minimal and bare bones bootloader/firmware for Rockchip devices based on the RK3399 and RK3588. -It brings up all hardware needed to get a framebuffer and a USB keyboard working as fast as possible. +`rk` is a small, multi-board firmware and hardware-bring-up project for +Rockchip RK3399, RK356x, and RK3588 devices. It can run a bare-metal payload +directly, provide framebuffer and keyboard firmware services, or, on explicitly +enabled RK3568 boards, act as the first stage for BL31 and U-Boot so U-Boot can +boot Linux or an EFI application from NVMe, SD, USB mass storage, or eMMC. -This project also includes some tips and info on bare metal Rockchip bringup, hopefully shedding some light on these not so well documented SoCs. +![Pinebook Pro booting rk](docs/img/DSCF2724s.JPG) -![photo of pinebook pro booting rk](docs/img/DSCF2724s.JPG) +## Choose a workflow -This project implements: -- RK3399 VOP/CRU/GPIO/timer driver -- RK3588 VOP2/CRU/IOC/GPIO/PWM/PMU driver -- Analogix eDP driver -- OHCI driver (not finished) -- DDR image wrappers -- Some PSCI commands + additional UEFI-like commands -- libusb driver for USB-OTG maskrom mode -- SD image packer for rk3588/rk3399 +| Goal | Output or command | What happens next | +| --- | --- | --- | +| Test a board without installing firmware | `make usb` | MaskROM USB loads DDR and a demo image into RAM | +| Test bare-metal firmware from SD | Board demo `.img` | Firmware exposes its implemented services and enters the included example payload | +| Develop an EL2 payload | Board `.bin` plus a payload | The firmware initializes the platform and transfers control to EL2 | +| Reach U-Boot on an enabled RK3568 board | `make usb-chainload BOARD=` | BL31 enters that board's U-Boot at EL2; U-Boot scans NVMe, SD, USB, then eMMC | +| Boot an enabled board autonomously | Install its chainloader to SPI NOR or eMMC | BootROM loads the board-qualified first stage without a USB host or SD card | +| Publish board binaries | `make release-dist VERSION=vX.Y.Z` | Reproducible, checksummed per-board archives are created | -External dependencies included: -- HDMI/eDP Combo PHY (HDPTX) driver from Linaro -- Proprietary DDR and maskrom images from Rockchip +The normal bare-metal firmware is not standalone and is not a storage +bootloader: it expects a FUEFI payload appended immediately after the firmware +and does not contain PCIe, NVMe, or filesystem drivers. A firmware-only `.img` +therefore halts with `Bad payload magic`; use a demo image for a standalone +test. Optional board-scoped chainloaders deliberately delegate storage boot to +U-Boot instead of duplicating it. -Supported devices: -- Pinebook Pro -- Cool-Pi Genbook -- (partial Orange Pi 5 support, no HDMI working yet) +U-Boot's EFI Loader can launch `EFI/BOOT/BOOTAA64.EFI`; this repository does +not build EDK2 or claim to be a complete UEFI implementation. -# Compiling -``` -sudo apt install gcc-aarch64-linux-gnu libusb-1.0-0-dev make xxd dtc cpp +## Supported boards + +| Target | SoC | Bare-metal/demo | MaskROM USB | SD image | BL31/U-Boot chainloader | +| --- | --- | --- | --- | --- | --- | +| [Pine64 Pinebook Pro](docs/devices/pinebook.md) | RK3399 | yes | `usb3399` | yes | no | +| [Cool-Pi Genbook](docs/devices/genbook.md) | RK3588 | yes | `usb3588` | yes | no | +| [Firefly ROC-RK3566-PC](docs/rk356x/boards/roc3566.md) | RK3566 | yes | `usb3566` | yes | no | +| [Youyeetoo YY3568](docs/rk356x/boards/yy3568.md) | RK3568 | yes | `usb BOARD=yy3568` | yes | SPI NOR, eMMC, SD, or USB | +| [Radxa ROCK 3A](docs/rk356x/boards/rock3a.md) | RK3568 | yes | `usb BOARD=rock3a` | yes | SPI NOR, eMMC, SD, or USB | +| [Orange Pi 5](docs/devices/orangepi.md) | RK3588 | partial | — | partial | no | + +Board support is explicit. Shared SoC code does not make arbitrary RK3566, +RK3568, RK3399, or RK3588 boards safe to use. + +## What the bare-metal firmware provides + +- DDR initialization through source-built or pinned Rockchip loaders. +- Early UART and board/SoC diagnostics. +- MMU and target-specific memory maps. +- RK3399/RK3588 display support and an RK356x VOP2/DW-HDMI platform. +- EDID-selected RK356x HDMI modes through 3840x2160p30. +- Polled USB HID boot-keyboard input on supported RK356x USB-A ports. +- DTB, framebuffer, memory-map, character-input, and reset services through the + project's FUEFI interface. +- EL3-to-EL2 payload handoff and reset-to-MaskROM recovery. +- Direct BootROM USB loading and RKNS SD-image packaging. +- Isolated, manifest-driven BL31/U-Boot variants for YY3568 and ROCK 3A. + +HDMI audio, hubs and high-speed USB host controllers, networking, storage +drivers in the bare-metal stage, authenticated boot, OP-TEE, and EDK2 remain +out of scope. + +## Build and test + +On Debian or Ubuntu: + +```sh +sudo apt install gcc-aarch64-linux-gnu libusb-1.0-0-dev make xxd \ + device-tree-compiler cpp python3 xz-utils make all +make SHELL=/bin/bash check -j"$(nproc)" ``` -There's a bunch of targets, but this is the gist: -- `pinebook-ddr.bin`: Customized DDR image for the Pinebook. -- `pinebook.bin`: Bootloader (SPL) binary, loaded to 0x0 -- `demo.bin`: Payload demo binary, bootloader jumps to this in EL2 -- `demo_pinebook.bin`: `demo.bin` appended to `pinebook.bin` -- `demo_pinebook.img`: Bootable SD card image for the Pinebook +`make all` builds the normal/demo firmware and host utilities. It does not +fetch or build U-Boot. The required RK356x binary inputs are vendored in +`img/`, so ordinary clean builds do not require sibling `rkbin` or +`Rockchip-Library` checkouts. + +Artifact naming is consistent: + +- `.bin`: firmware-only developer base; append a compatible payload. +- `demo_.bin`: firmware with the demo payload appended. +- `.img`: SD-packaged firmware-only base; not standalone. +- `demo_.img`: standalone SD demonstration image. +- `uboot_.bin`: dedicated board first stage plus its pinned U-Boot FIT. +- `uboot_.img`: board-qualified BL31/U-Boot SD image. +- `uboot__idbloader.img`: raw eMMC ID block for LBA `0x40`. +- `uboot__spi.img`: BootROM-formatted SPI-NOR image for offset zero. + +See [Getting started](docs/intro.md) for release-vs-source workflows, direct +USB loading, SD usage, serial settings, and recovery expectations. + +## RK356x: U-Boot, storage discovery, and autonomous boot + +Chainloading is enabled only for boards with validated manifests. Build and +validate one selected board without affecting its normal/demo firmware: + +```sh +make chainload BOARD=yy3568 +make chainload-check BOARD=yy3568 +make usb-chainload BOARD=yy3568 + +make chainload BOARD=rock3a +make chainload-check BOARD=rock3a +make usb-chainload BOARD=rock3a +``` + +The resulting flow is: + +```text +SPI NOR / eMMC / SD / MaskROM USB + -> RK3568 BootROM + -> Rockchip DDR loader + -> board-isolated rk RK3568 chainloader + -> BL31 + -> U-Boot EL2 + -> NVMe -> SD -> USB -> eMMC + -> extlinux, boot.scr, or EFI/BOOT/BOOTAA64.EFI +``` + +YY3568 pins its existing Radxa vendor-FIT backend; ROCK 3A pins official +mainline U-Boot v2026.04. Each manifest selects its own source, overlay, +addresses, boot policy, and artifacts. The build is opt-in and normally fetches +that pinned source commit. +Both manifests explicitly map their board aliases so `mmc1` selects the +removable SD slot, `mmc0` selects eMMC, and onboard `mmc2` SDIO is never scanned +as OS storage. +Use `UBOOT_SRC=/path/to/pinned/source` for an existing source tree, or +`UBOOT_ITB=/path/to/u-boot.itb` for an offline `.bin` development build. +Persistent-media images additionally require the pinned U-Boot `mkimage` or +an explicit `MKIMAGE=/path`. + +SPI NOR has immutable BootROM priority over eMMC, SD, and USB. Installation is +therefore guarded by exact confirmation strings, capacity checks, mandatory +backups, MBR/GPT overlap checks for eMMC, and complete readback verification. +Read the [chainloading and installation guide](docs/rk356x/chainloading.md) before +writing either device. + +## Documentation + +- [Getting started and choosing a workflow](docs/intro.md) +- [Firmware images, payloads, SD delivery, and device trees](docs/payloads.md) +- [RK356x device overview and board matrix](docs/rk356x/index.md) +- [RK356x common bare-metal firmware, memory, display, and input](docs/rk356x/bare-metal.md) +- [RK356x BL31/U-Boot, SPI/eMMC, NVMe, recovery, and future EDK2/OP-TEE integration](docs/rk356x/chainloading.md) +- [ROC-RK3566-PC board policy](docs/rk356x/boards/roc3566.md) +- [YY3568 board policy](docs/rk356x/boards/yy3568.md) +- [ROCK 3A board policy](docs/rk356x/boards/rock3a.md) +- [Binary releases and checksum verification](docs/releases.md) +- [Chainloader board-manifest and porting policy](config/chainload/README.md) +- [Rockchip binary provenance](img/README.md) +- [Bare-metal reference index](docs/index.md) + +## Binary releases + +Stable `vMAJOR.MINOR.PATCH` tags publish reproducible archives for Pinebook Pro, +Genbook, ROC-RK3566-PC, YY3568, and ROCK 3A plus a top-level `SHA256SUMS`. The partial +Orange Pi 5 target and Linux host executables are intentionally not release +assets. See the [release guide](docs/releases.md). + +## Validation status + +Host tests cover image formats, FIT parsing, address policies, EDID/input +logic, guarded SPI/eMMC installation, backup restoration, release allowlists, +and reproducibility contracts. Physical-board acceptance is separate: before +depending on a persistent image, capture the required UART transcript and +complete the hardware checklist in the relevant board guide. + +## Thanks -# Thanks - Colt Judice - Hans Jorgensen - Hannes Bredberg diff --git a/config/chainload/README.md b/config/chainload/README.md new file mode 100644 index 0000000..019f405 --- /dev/null +++ b/config/chainload/README.md @@ -0,0 +1,52 @@ +# Chainloader board manifests + +Chainloading is opt-in and board-scoped. A board is supported only when this +directory contains a manifest plus its own overlay. The manifest pins the +U-Boot and BL31 inputs, address policy, output names, and boot policy. No +fallback manifest exists: an unknown `BOARD` is rejected. + +Schema 3 currently validates two independent policies: + +| Board | Backend | BL33 load / stack | Automatic OS targets | +| --- | --- | --- | --- | +| `yy3568` | `vendor-fit` | `0x00a00000` / `0x00c00000` | `nvme0 nvme1 mmc1 usb0 mmc0` | +| `rock3a` | `mainline-fit` | `0x00800000` / `0x03f00000` | `nvme mmc1 usb mmc0` | + +YY3568 imports board material from Armbian commit +`587b6f2c0a867859ca3f323f6008bee9e3ef1553` and was checked against the local +Youyeetoo `ArmBoardBringUp` reference. ROCK 3A uses the official mainline +`rock-3a-rk3568_defconfig` and DTS at U-Boot v2026.04; the same Armbian commit's +`rock-3a.conf` is its board-selection reference. + +Run `python3 tools/chainload-manifest.py validate --all` before invoking a +board build. The validator closes the schema and rejects unknown backends, +missing or extra fields, repository path escapes, cross-board overlay/media +selection, mismatched names, artifact collisions, invalid formats/offsets, and +overlapping stage/FIT/BL33 ranges. + +Every manifest declares `boot_policy.automatic_scan` with the fixed media +order `nvme`, `sd`, `usb`, `emmc` and a board-specific target list for each +medium. The validator rejects missing groups, reordering, duplicate devices, +and MMC/USB/NVMe names in the wrong group. On both current boards `mmc1` is the +removable SD slot and `mmc0` is eMMC. Their `mmc2` alias is onboard SDIO and is +intentionally absent from OS discovery. Backend spelling remains explicit: +the vendor distro framework uses `nvme0`/`usb0`, while mainline bootstd uses +the `nvme`/`usb` classes. + +Adding a board requires all of the following: + +1. A new manifest and board-specific U-Boot overlay. +2. A manifest-generated platform descriptor and isolated + `build/chainload//` linker/object namespace. +3. A reviewed FIT staging area, BL31 ranges, BL33 load/stack bounds, and TF-A + handoff protocol. +4. Explicit boot-media entries for each physical flash type: BootROM priority, + format, detected-capacity policy, pinmux, rkdeveloptool storage ID, write + offset, partition-preservation policy, backup scope, and artifact name. +5. A board-qualified U-Boot OS scan mapping for NVMe, removable SD, USB mass + storage, and eMMC. Each `mmcN` must be checked against that board's aliases; + SDIO devices must not be included. +6. Offline parser/address/media-policy tests, a chainloader CI matrix entry, + release allowlist changes, and hardware validation of installation and + restoration. Never reuse another board's addresses, GPIOs, storage IDs, + artifacts, restore metadata, or DTS data by default. diff --git a/config/chainload/rock3a.json b/config/chainload/rock3a.json new file mode 100644 index 0000000..f373421 --- /dev/null +++ b/config/chainload/rock3a.json @@ -0,0 +1,102 @@ +{ + "schema": 3, + "board": "rock3a", + "identity": "Radxa ROCK 3A", + "soc": "rk3568", + "platform": "rk3568", + "uboot": { + "backend": "mainline-fit", + "repository": "https://github.com/u-boot/u-boot.git", + "ref": "v2026.04", + "commit": "88dc2788777babfd6322fa655df549a019aa1e69", + "defconfig": "rock-3a-rk3568_defconfig", + "overlay": "config/chainload/rock3a/overlay", + "config_fragment": "config/chainload/rock3a/overlay/configs/rock3a-chainload.config", + "armbian_repository": "https://github.com/armbian/build.git", + "armbian_commit": "587b6f2c0a867859ca3f323f6008bee9e3ef1553", + "armbian_path": "config/boards/rock-3a.conf" + }, + "bl31": { + "path": "img/rk3568_bl31_v1.46.elf", + "rkbin_repository": "https://github.com/rockchip-linux/rkbin.git", + "rkbin_commit": "ecb4fcbe954edf38b3ae037d5de6d9f5bccf81f4", + "rkbin_path": "bin/rk35/rk3568_bl31_v1.46.elf", + "size": 402376, + "sha256": "c81ac7e8e1fd727cf7f0db62a9aaea760bde2b270e34d98eb264a264b86df749" + }, + "layout": { + "stage_limit": "0x00040000", + "fit_stage_start": "0x08000000", + "fit_stage_end": "0x08400000", + "bl31_params": "0x00100000", + "bl31_entry": "0x00040000", + "bl31_ranges": [ + ["0x00040000", "0x00200000"], + ["0xfdcc0000", "0xfdcf0000"] + ], + "expected_bl31_segments": 6, + "bl33_load": "0x00800000", + "bl33_stack": "0x03f00000", + "handoff_protocol": "tf-a-v1-bl33-aarch64-el2" + }, + "artifacts": { + "fit": "rock3a-u-boot.itb", + "binary": "uboot_rock3a.bin", + "image": "uboot_rock3a.img", + "idblock": "uboot_rock3a_idbloader.img", + "spi_nor": "uboot_rock3a_spi.img", + "source": "rock3a-u-boot-source.tar.xz" + }, + "boot_media": { + "bootrom_order": ["spi-nor", "spi-nand", "nand", "emmc", "sd", "usb"], + "ddr": "img/rk3568_ddr_1560MHz_v1.25.bin", + "usbplug": "img/rk356x_usbplug_v1.17.bin", + "sd": { + "format": "rksd", + "idblock_lba": 64, + "artifact": "uboot_rock3a.img" + }, + "emmc": { + "format": "rksd", + "storage_id": 1, + "write_lba": 64, + "capacity_policy": "detected-size-must-cover-image", + "preserve_partition_table": true, + "required_pinctrl": "emmc_bus8/emmc_clk/emmc_cmd/emmc_datastrobe", + "artifact": "uboot_rock3a_idbloader.img" + }, + "spi-nor": { + "format": "rkspi", + "storage_id": 9, + "write_lba": 0, + "capacity_policy": "detected-size-must-cover-image", + "required_pinctrl": "fspi_pins", + "artifact": "uboot_rock3a_spi.img" + } + }, + "host_tools": { + "xrock": { + "repository": "https://github.com/xboot/xrock.git", + "commit": "b90d3ba8f0a48320e3888701f7e66e0e4e038bbb" + }, + "rkdeveloptool": { + "repository": "https://github.com/rockchip-linux/rkdeveloptool.git", + "commit": "304f073752fd25c854e1bcf05d8e7f925b1f4e14" + } + }, + "boot_policy": { + "automatic_scan": { + "order": ["nvme", "sd", "usb", "emmc"], + "targets": { + "nvme": ["nvme"], + "sd": ["mmc1"], + "usb": ["usb"], + "emmc": ["mmc0"] + } + }, + "boot_delay_seconds": 3, + "baud_rate": 1500000, + "interactive_only": ["spi"], + "formats": ["extlinux", "boot.scr", "EFI/BOOT/BOOTAA64.EFI"] + } +} diff --git a/config/chainload/rock3a/overlay/README.rk-chainload b/config/chainload/rock3a/overlay/README.rk-chainload new file mode 100644 index 0000000..a3f8a2c --- /dev/null +++ b/config/chainload/rock3a/overlay/README.rk-chainload @@ -0,0 +1,27 @@ +ROCK 3A rk chainloader U-Boot source +==================================== + +This tree is official U-Boot v2026.04 at commit +88dc2788777babfd6322fa655df549a019aa1e69 with the board-scoped files from +config/chainload/rock3a/overlay applied. + +To reproduce the configuration from an extracted rk release bundle: + + make CROSS_COMPILE=aarch64-linux-gnu- rock-3a-rk3568_defconfig + scripts/kconfig/merge_config.sh -m .config configs/rock3a-chainload.config + make CROSS_COMPILE=aarch64-linux-gnu- olddefconfig + BL31=../../loaders/rk3568_bl31_v1.46.elf \ + ROCKCHIP_TPL=../../loaders/rk3568_ddr_1560MHz_v1.25.bin \ + make CROSS_COMPILE=aarch64-linux-gnu- all tools/mkimage + +The board-scoped U-Boot DT override keeps upstream ROCK 3A hardware data and +forces inline FIT payload data because rk's first stage rejects external FIT +data-position/data-offset layouts. + +Mainline routes ROCK 3A through `TARGET_EVB_RK3568`. The board-isolated +`include/configs/evb_rk3568.h` overlay preserves the upstream console settings +and defines `BOOT_TARGETS` before including `rk3568_common.h`. This keeps the +common RK3568 memory variables while narrowing automatic discovery to `nvme`, +removable SD `mmc1`, USB mass storage, then eMMC `mmc0`. The upstream `mmc2` +SDIO/Wi-Fi device is not a boot target. This override exists only in +`build/chainload/rock3a/source` and the ROCK 3A corresponding-source archive. diff --git a/config/chainload/rock3a/overlay/arch/arm/dts/rk3568-rock-3a-u-boot.dtsi b/config/chainload/rock3a/overlay/arch/arm/dts/rk3568-rock-3a-u-boot.dtsi new file mode 100644 index 0000000..1dd49a4 --- /dev/null +++ b/config/chainload/rock3a/overlay/arch/arm/dts/rk3568-rock-3a-u-boot.dtsi @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2021 Rockchip Electronics Co., Ltd + * (C) Copyright 2023 Akash Gajjar + * + * Upstream ROCK 3A additions plus rk's board-scoped inline-FIT policy. + */ + +#include "rk356x-u-boot.dtsi" + +/ { + leds { + led-0 { + default-state = "on"; + }; + }; +}; + +/* The rk chainloader rejects data-position/data-offset external FITs. */ +&fit_template { + /delete-property/ fit,external-offset; +}; + +&pcie3x2 { + pinctrl-0 = <&pcie3x2_reset_h>; +}; + +&pinctrl { + pcie { + pcie3x2_reset_h: pcie3x2-reset-h { + rockchip,pins = <2 RK_PD6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; +}; + +&sdhci { + cap-mmc-highspeed; + mmc-hs200-1_8v; + mmc-hs400-1_8v; + mmc-hs400-enhanced-strobe; +}; + +&sfc { + flash@0 { + bootph-pre-ram; + bootph-some-ram; + }; +}; + +&usb_host0_ohci { + status = "disabled"; +}; diff --git a/config/chainload/rock3a/overlay/configs/rock3a-chainload.config b/config/chainload/rock3a/overlay/configs/rock3a-chainload.config new file mode 100644 index 0000000..2e37da0 --- /dev/null +++ b/config/chainload/rock3a/overlay/configs/rock3a-chainload.config @@ -0,0 +1,24 @@ +CONFIG_BOOTDELAY=3 +CONFIG_BAUDRATE=1500000 +CONFIG_BOOTSTD=y +CONFIG_BOOTSTD_FULL=y +CONFIG_BOOTSTD_DEFAULTS=y +CONFIG_BOOTMETH_DISTRO=y +CONFIG_BOOTMETH_SCRIPT=y +CONFIG_BOOTMETH_EXTLINUX=y +CONFIG_BOOTMETH_EFILOADER=y +CONFIG_CMD_BOOTFLOW=y +CONFIG_CMD_SOURCE=y +CONFIG_CMD_MMC=y +CONFIG_CMD_USB=y +CONFIG_USB_STORAGE=y +CONFIG_CMD_SF=y +CONFIG_CMD_NVME=y +CONFIG_CMD_FAT=y +CONFIG_CMD_EXT4=y +CONFIG_CMD_FS_GENERIC=y +CONFIG_PCI=y +CONFIG_NVME_PCI=y +CONFIG_EFI_LOADER=y +CONFIG_EFI_BINARY_EXEC=y +CONFIG_CMD_BOOTEFI=y diff --git a/config/chainload/rock3a/overlay/include/configs/evb_rk3568.h b/config/chainload/rock3a/overlay/include/configs/evb_rk3568.h new file mode 100644 index 0000000..c692e0b --- /dev/null +++ b/config/chainload/rock3a/overlay/include/configs/evb_rk3568.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2021 Rockchip Electronics Co., Ltd + * + * ROCK 3A uses mainline's TARGET_EVB_RK3568 board target. This file exists + * only in the board-isolated chainload source snapshot and narrows the common + * RK3568 default boot targets before rk3568_common.h builds the environment. + */ + +#ifndef __EVB_RK3568_H +#define __EVB_RK3568_H + +#define ROCKCHIP_DEVICE_SETTINGS \ + "stdout=serial,vidconsole\0" \ + "stderr=serial,vidconsole\0" + +#define BOOT_TARGETS "nvme mmc1 usb mmc0" + +#include + +#endif diff --git a/config/chainload/yy3568.json b/config/chainload/yy3568.json new file mode 100644 index 0000000..ff70aaa --- /dev/null +++ b/config/chainload/yy3568.json @@ -0,0 +1,100 @@ +{ + "schema": 3, + "board": "yy3568", + "identity": "Youyeetoo YY3568", + "soc": "rk3568", + "platform": "rk3568", + "uboot": { + "backend": "vendor-fit", + "repository": "https://github.com/radxa/u-boot.git", + "ref": "next-dev-v2024.10", + "commit": "39cd993e5d6296635438e84f4576b3a9bf76f86e", + "defconfig": "yy3568-rk3568_defconfig", + "overlay": "config/chainload/yy3568/overlay", + "armbian_repository": "https://github.com/armbian/build.git", + "armbian_commit": "587b6f2c0a867859ca3f323f6008bee9e3ef1553" + }, + "bl31": { + "path": "img/rk3568_bl31_v1.46.elf", + "rkbin_repository": "https://github.com/rockchip-linux/rkbin.git", + "rkbin_commit": "ecb4fcbe954edf38b3ae037d5de6d9f5bccf81f4", + "rkbin_path": "bin/rk35/rk3568_bl31_v1.46.elf", + "size": 402376, + "sha256": "c81ac7e8e1fd727cf7f0db62a9aaea760bde2b270e34d98eb264a264b86df749" + }, + "layout": { + "stage_limit": "0x00040000", + "fit_stage_start": "0x08000000", + "fit_stage_end": "0x08400000", + "bl31_params": "0x00100000", + "bl31_entry": "0x00040000", + "bl31_ranges": [ + ["0x00040000", "0x00200000"], + ["0xfdcc0000", "0xfdcf0000"] + ], + "expected_bl31_segments": 6, + "bl33_load": "0x00a00000", + "bl33_stack": "0x00c00000", + "handoff_protocol": "tf-a-v1-bl33-aarch64-el2" + }, + "artifacts": { + "fit": "yy3568-u-boot.itb", + "binary": "uboot_yy3568.bin", + "image": "uboot_yy3568.img", + "idblock": "uboot_yy3568_idbloader.img", + "spi_nor": "uboot_yy3568_spi.img", + "source": "yy3568-u-boot-source.tar.xz" + }, + "boot_media": { + "bootrom_order": ["spi-nor", "spi-nand", "nand", "emmc", "sd", "usb"], + "ddr": "img/rk3568_ddr_1560MHz_v1.25.bin", + "usbplug": "img/rk356x_usbplug_v1.17.bin", + "sd": { + "format": "rksd", + "idblock_lba": 64, + "artifact": "uboot_yy3568.img" + }, + "emmc": { + "format": "rksd", + "storage_id": 1, + "write_lba": 64, + "capacity_policy": "detected-size-must-cover-image", + "preserve_partition_table": true, + "required_pinctrl": "emmc_bus8/emmc_clk/emmc_cmd/emmc_datastrobe", + "artifact": "uboot_yy3568_idbloader.img" + }, + "spi-nor": { + "format": "rkspi", + "storage_id": 9, + "write_lba": 0, + "capacity_policy": "detected-size-must-cover-image", + "required_pinctrl": "fspi_pins", + "artifact": "uboot_yy3568_spi.img" + } + }, + "host_tools": { + "xrock": { + "repository": "https://github.com/xboot/xrock.git", + "commit": "b90d3ba8f0a48320e3888701f7e66e0e4e038bbb" + }, + "rkdeveloptool": { + "repository": "https://github.com/rockchip-linux/rkdeveloptool.git", + "commit": "304f073752fd25c854e1bcf05d8e7f925b1f4e14" + } + }, + "boot_policy": { + "automatic_scan": { + "order": ["nvme", "sd", "usb", "emmc"], + "targets": { + "nvme": ["nvme0", "nvme1"], + "sd": ["mmc1"], + "usb": ["usb0"], + "emmc": ["mmc0"] + } + }, + "boot_delay_seconds": 3, + "baud_rate": 1500000, + "interactive_only": ["spi"], + "formats": ["extlinux", "boot.scr", "EFI/BOOT/BOOTAA64.EFI"] + } +} diff --git a/config/chainload/yy3568/overlay/README.rk-chainload b/config/chainload/yy3568/overlay/README.rk-chainload new file mode 100644 index 0000000..a34e0bb --- /dev/null +++ b/config/chainload/yy3568/overlay/README.rk-chainload @@ -0,0 +1,26 @@ +YY3568 chainload U-Boot corresponding source +================================================ + +This snapshot is based on Radxa U-Boot commit +39cd993e5d6296635438e84f4576b3a9bf76f86e. The board material and build +policy are derived from Armbian commit +587b6f2c0a867859ca3f323f6008bee9e3ef1553. + +Armbian's RK35xx `spl-blobs` target map builds `u-boot.dtb` before +`u-boot.itb`, and its +`fix-u-boot-itb-dependency-on-u-boot-dtb.patch` makes that dependency +explicit. This project likewise builds `u-boot.dtb` first. It then invokes +`tools/mkimage -f u-boot.its u-boot.itb` without `-E`, because the dedicated +chainloader accepts inline FIT data and intentionally rejects external data. + +Set `BL31` to the packaged `rk3568_bl31_v1.46.elf`, set +`CROSS_COMPILE=aarch64-linux-gnu-`, and reproduce the configured source with: + + make yy3568-rk3568_defconfig + VENDOR_CFLAGS='-fdiagnostics-color=always -Wno-error=maybe-uninitialized -Wno-error=misleading-indentation -Wno-error=attributes -Wno-error=address-of-packed-member -Wno-error=implicit-function-declaration -Wno-error=implicit-int -Wno-error=int-conversion -Wno-error=incompatible-pointer-types -Wno-error=array-parameter' + make CFLAGS="$VENDOR_CFLAGS" KCFLAGS="$VENDOR_CFLAGS" u-boot.bin tools u-boot.its u-boot.dtb + ./tools/mkimage -f u-boot.its u-boot.itb + +The repository's `tools/build-chainload-uboot.sh` also supplies Armbian's +modern-GCC warning demotions through both `CFLAGS` and `KCFLAGS`, fixes the +build timestamp and identity, and rejects an empty DTB or zero-byte FIT image. diff --git a/config/chainload/yy3568/overlay/arch/arm/dts/rk3568-yy3568.dts b/config/chainload/yy3568/overlay/arch/arm/dts/rk3568-yy3568.dts new file mode 100644 index 0000000..eac428d --- /dev/null +++ b/config/chainload/yy3568/overlay/arch/arm/dts/rk3568-yy3568.dts @@ -0,0 +1,630 @@ +/* + * SPDX-License-Identifier: GPL-2.0+ + * Copyright (c) 2020 Rockchip Electronics Co., Ltd. + * Copyright (c) 2024 Radxa Limited + */ +/dts-v1/; +#include +#include +#include +#include "rk3568.dtsi" +#include "rk3568-u-boot.dtsi" +/ { + model = "Youyeetoo YY3568"; + compatible = "youyeetoo,yy3568", "rockchip,rk3568"; + aliases { + ethernet0 = &gmac0; + ethernet1 = &gmac1; + mmc0 = &sdhci; + mmc1 = &sdmmc0; + mmc2 = &sdmmc2; + }; + vcc12v_input: vcc12v-input { + u-boot,dm-pre-reloc; + compatible = "regulator-fixed"; + regulator-name = "vcc12v_input"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <12000000>; + regulator-max-microvolt = <12000000>; + }; + vcc5v0_sys: vcc5v0-sys { + u-boot,dm-pre-reloc; + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_sys"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + vin-supply = <&vcc12v_input>; + }; + vcc3v3_sys: vcc3v3-sys { + u-boot,dm-pre-reloc; + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_sys"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vcc12v_input>; + }; + vcc3v3_pi6c_05: vcc3v3-pi6c-05 { + u-boot,dm-pre-reloc; + compatible = "regulator-fixed"; + enable-active-high; + gpio = <&gpio0 RK_PC7 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&pcie_enable_h>; + regulator-name = "vcc3v3_pi6c_05"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vcc5v0_sys>; + }; + vcc3v3_pcie: vcc3v3-pcie { + u-boot,dm-pre-reloc; + compatible = "regulator-fixed"; + regulator-name = "vcc3v3_pcie"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + enable-active-high; + gpio = <&gpio3 RK_PC3 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&pcie30_pwr>; + startup-delay-us = <5000>; + vin-supply = <&vcc5v0_sys>; + }; + pcie30_avdd0v9: pcie30-avdd0v9 { + u-boot,dm-pre-reloc; + compatible = "regulator-fixed"; + regulator-name = "pcie30_avdd0v9"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <900000>; + vin-supply = <&vcc3v3_sys>; + }; + pcie30_avdd1v8: pcie30-avdd1v8 { + u-boot,dm-pre-reloc; + compatible = "regulator-fixed"; + regulator-name = "pcie30_avdd1v8"; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + vin-supply = <&vcc3v3_sys>; + }; + adc-keys { + compatible = "adc-keys"; + io-channels = <&saradc 0>; + io-channel-names = "buttons"; + keyup-threshold-microvolt = <1800000>; + u-boot,dm-spl; + status = "okay"; + volumeup-key { + u-boot,dm-spl; + linux,code = ; + label = "volume up"; + press-threshold-microvolt = <9>; + }; + }; + leds { + u-boot,dm-pre-reloc; + compatible = "gpio-leds"; + status = "okay"; + blue-led { + u-boot,dm-pre-reloc; + label = "blue"; + gpios = <&gpio2 RK_PB2 GPIO_ACTIVE_HIGH>; + }; + }; +}; +&crypto { + status = "okay"; +}; +&gmac0 { + u-boot,dm-pre-reloc; + phy-mode = "rgmii"; + clock_in_out = "output"; + snps,reset-gpio = <&gpio2 RK_PD3 GPIO_ACTIVE_LOW>; + snps,reset-active-low; + snps,reset-delays-us = <0 20000 100000>; + assigned-clocks = <&cru SCLK_GMAC0_RX_TX>, <&cru SCLK_GMAC0>; + assigned-clock-parents = <&cru SCLK_GMAC0_RGMII_SPEED>; + assigned-clock-rates = <0>, <125000000>; + pinctrl-names = "default"; + pinctrl-0 = <&gmac0_miim + &gmac0_tx_bus2 + &gmac0_rx_bus2 + &gmac0_rgmii_clk + &gmac0_rgmii_bus>; + phy-handle = <&rgmii_phy0>; + status = "okay"; +}; +&gmac1 { + u-boot,dm-pre-reloc; + phy-mode = "rgmii"; + clock_in_out = "output"; + snps,reset-gpio = <&gpio2 RK_PD1 GPIO_ACTIVE_LOW>; + snps,reset-active-low; + /* Reset time is 20ms, 100ms for rtl8211f */ + snps,reset-delays-us = <0 20000 100000>; + assigned-clocks = <&cru SCLK_GMAC1_RX_TX>, <&cru SCLK_GMAC1>; + assigned-clock-parents = <&cru SCLK_GMAC1_RGMII_SPEED>; + assigned-clock-rates = <0>, <125000000>; + pinctrl-names = "default"; + pinctrl-0 = <&gmac1m1_miim + &gmac1m1_tx_bus2 + &gmac1m1_rx_bus2 + &gmac1m1_rgmii_clk + &gmac1m1_rgmii_bus>; + tx_delay = <0x44>; + rx_delay = <0x26>; + phy-handle = <&rgmii_phy1>; + status = "okay"; +}; +&grf { + u-boot,dm-pre-reloc; + status = "okay"; +}; +&gpio0 { + u-boot,dm-pre-reloc; +}; +&gpio1 { + u-boot,dm-pre-reloc; +}; +&gpio2 { + u-boot,dm-pre-reloc; +}; +&gpio3 { + u-boot,dm-pre-reloc; +}; +&gpio4 { + u-boot,dm-pre-reloc; +}; +&i2c0 { + status = "okay"; + u-boot,dm-pre-reloc; + clock-frequency = <100000>; + vdd_cpu: tcs4525@1c { + u-boot,dm-pre-reloc; + compatible = "tcs,tcs452x"; + reg = <0x1c>; + vin-supply = <&vcc5v0_sys>; + regulator-compatible = "fan53555-reg"; + regulator-name = "vdd_cpu"; + regulator-min-microvolt = <712500>; + regulator-max-microvolt = <1390000>; + regulator-init-microvolt = <900000>; + regulator-ramp-delay = <2300>; + fcs,suspend-voltage-selector = <1>; + regulator-boot-on; + regulator-always-on; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-off-in-suspend; + }; + }; + rk809: pmic@20 { + u-boot,dm-pre-reloc; + status = "okay"; + compatible = "rockchip,rk809"; + reg = <0x20>; + interrupt-parent = <&gpio0>; + interrupts = <3 IRQ_TYPE_LEVEL_LOW>; + pinctrl-names = "default", "pmic-sleep", + "pmic-power-off", "pmic-reset"; + pinctrl-0 = <&pmic_int>; + pinctrl-1 = <&soc_slppin_slp>, <&rk817_slppin_slp>; + pinctrl-2 = <&soc_slppin_gpio>, <&rk817_slppin_pwrdn>; + pinctrl-3 = <&soc_slppin_gpio>, <&rk817_slppin_rst>; + rockchip,system-power-controller; + wakeup-source; + #clock-cells = <1>; + clock-output-names = "rk808-clkout1", "rk808-clkout2"; + /* 1: rst regs (default in codes), 0: rst the pmic */ + pmic-reset-func = <0>; + /* not save the PMIC_POWER_EN register in uboot */ + not-save-power-en = <1>; + vcc1-supply = <&vcc3v3_sys>; + vcc2-supply = <&vcc3v3_sys>; + vcc3-supply = <&vcc3v3_sys>; + vcc4-supply = <&vcc3v3_sys>; + vcc5-supply = <&vcc3v3_sys>; + vcc6-supply = <&vcc3v3_sys>; + vcc7-supply = <&vcc3v3_sys>; + vcc8-supply = <&vcc3v3_sys>; + vcc9-supply = <&vcc3v3_sys>; + pwrkey { + status = "okay"; + u-boot,dm-pre-reloc; + }; + pinctrl_rk8xx: pinctrl_rk8xx { + u-boot,dm-pre-reloc; + gpio-controller; + #gpio-cells = <2>; + rk817_slppin_null: rk817_slppin_null { + pins = "gpio_slp"; + function = "pin_fun0"; + u-boot,dm-pre-reloc; + }; + rk817_slppin_slp: rk817_slppin_slp { + pins = "gpio_slp"; + function = "pin_fun1"; + u-boot,dm-pre-reloc; + }; + rk817_slppin_pwrdn: rk817_slppin_pwrdn { + pins = "gpio_slp"; + function = "pin_fun2"; + u-boot,dm-pre-reloc; + }; + rk817_slppin_rst: rk817_slppin_rst { + pins = "gpio_slp"; + function = "pin_fun3"; + u-boot,dm-pre-reloc; + }; + }; + regulators { + u-boot,dm-pre-reloc; + vdd_logic: DCDC_REG1 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1350000>; + regulator-init-microvolt = <900000>; + regulator-ramp-delay = <6001>; + regulator-initial-mode = <0x2>; + regulator-name = "vdd_logic"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-on-in-suspend; + }; + }; + vdd_gpu: DCDC_REG2 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1350000>; + regulator-init-microvolt = <900000>; + regulator-ramp-delay = <6001>; + regulator-initial-mode = <0x2>; + regulator-name = "vdd_gpu"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-on-in-suspend; + }; + }; + vcc_ddr: DCDC_REG3 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-initial-mode = <0x2>; + regulator-name = "vcc_ddr"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-on-in-suspend; + }; + }; + vdd_npu: DCDC_REG4 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <1350000>; + regulator-init-microvolt = <900000>; + regulator-ramp-delay = <6001>; + regulator-initial-mode = <0x2>; + regulator-name = "vdd_npu"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-on-in-suspend; + }; + }; + vdda0v9_image: LDO_REG1 { + u-boot,dm-pre-reloc; + regulator-boot-on; + regulator-always-on; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <900000>; + regulator-name = "vdda0v9_image"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-off-in-suspend; + }; + }; + vdda_0v9: LDO_REG2 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <900000>; + regulator-name = "vdda_0v9"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-off-in-suspend; + }; + }; + vdda0v9_pmu: LDO_REG3 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <900000>; + regulator-name = "vdda0v9_pmu"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-on-in-suspend; + regulator-suspend-microvolt = <900000>; + }; + }; + vccio_acodec: LDO_REG4 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vccio_acodec"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-off-in-suspend; + }; + }; + vccio_sd: LDO_REG5 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vccio_sd"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-off-in-suspend; + }; + }; + vcc3v3_pmu: LDO_REG6 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vcc3v3_pmu"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-on-in-suspend; + regulator-suspend-microvolt = <3300000>; + }; + }; + vcca_1v8: LDO_REG7 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca_1v8"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-off-in-suspend; + }; + }; + vcca1v8_pmu: LDO_REG8 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca1v8_pmu"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-on-in-suspend; + regulator-suspend-microvolt = <1800000>; + }; + }; + vcca1v8_image: LDO_REG9 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcca1v8_image"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-off-in-suspend; + }; + }; + vcc_1v8: DCDC_REG5 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-name = "vcc_1v8"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-off-in-suspend; + }; + }; + vcc_3v3: SWITCH_REG1 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-name = "vcc_3v3"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-off-in-suspend; + }; + }; + vcc3v3_sd: SWITCH_REG2 { + u-boot,dm-pre-reloc; + regulator-always-on; + regulator-boot-on; + regulator-name = "vcc3v3_sd"; + regulator-state-mem { + u-boot,dm-pre-reloc; + regulator-off-in-suspend; + }; + }; + }; + }; +}; +&i2c0_xfer { + u-boot,dm-pre-reloc; + status = "okay"; +}; +&rgmii_phy0 { + compatible = "ethernet-phy-id001c.c916", + "ethernet-phy-ieee802.3-c22"; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio2 RK_PD3 GPIO_ACTIVE_LOW>; +}; + +&rgmii_phy1 { + compatible = "ethernet-phy-id001c.c916", + "ethernet-phy-ieee802.3-c22"; + reset-assert-us = <20000>; + reset-deassert-us = <100000>; + reset-gpios = <&gpio2 RK_PD1 GPIO_ACTIVE_LOW>; +}; +&pcfg_pull_none_smt { + u-boot,dm-pre-reloc; + status = "okay"; +}; +&pcie2x1 { + u-boot,dm-pre-reloc; + reset-gpios = <&gpio1 RK_PB2 GPIO_ACTIVE_HIGH>; + vpcie3v3-supply = <&vcc3v3_pi6c_05>; + status = "okay"; +}; +&pcie30_phy_grf { + u-boot,dm-pre-reloc; +}; +&pcie30phy { + u-boot,dm-pre-reloc; + data-lanes = <1 2>; + status = "okay"; +}; +&pcie3x2 { + u-boot,dm-pre-reloc; + num-lanes = <1>; + pinctrl-names = "default"; + pinctrl-0 = <&pcie30x2_reset_h>; + reset-gpios = <&gpio2 RK_PD6 GPIO_ACTIVE_HIGH>; + vpcie3v3-supply = <&vcc3v3_pcie>; + phys = <&pcie30phy>; + status = "okay"; +}; +&pinctrl { + u-boot,dm-spl; + pcie { + u-boot,dm-pre-reloc; + pcie30x2_reset_h: pcie30x2-reset-h { + u-boot,dm-pre-reloc; + rockchip,pins = <2 RK_PD6 RK_FUNC_GPIO &pcfg_pull_none>; + }; + pcie_enable_h: pcie-enable-h { + u-boot,dm-pre-reloc; + rockchip,pins = <0 RK_PC7 RK_FUNC_GPIO &pcfg_pull_none>; + }; + pcie30_pwr: pcie30-pwr { + u-boot,dm-pre-reloc; + rockchip,pins = <3 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; + pmic { + u-boot,dm-pre-reloc; + pmic_int: pmic_int { + u-boot,dm-pre-reloc; + rockchip,pins = + <0 RK_PA3 RK_FUNC_GPIO &pcfg_pull_up>; + }; + soc_slppin_gpio: soc_slppin_gpio { + u-boot,dm-pre-reloc; + rockchip,pins = + <0 RK_PA2 RK_FUNC_GPIO &pcfg_output_low_pull_down>; + }; + soc_slppin_slp: soc_slppin_slp { + u-boot,dm-pre-reloc; + rockchip,pins = + <0 RK_PA2 RK_FUNC_1 &pcfg_pull_up>; + }; + soc_slppin_rst: soc_slppin_rst { + u-boot,dm-pre-reloc; + rockchip,pins = + <0 RK_PA2 RK_FUNC_2 &pcfg_pull_none>; + }; + }; +}; +&pmu_io_domains { + u-boot,dm-pre-reloc; + status = "okay"; + pmuio1-supply = <&vcc3v3_pmu>; + pmuio2-supply = <&vcc3v3_pmu>; + vccio1-supply = <&vccio_acodec>; + vccio2-supply = <&vcc_1v8>; + vccio3-supply = <&vccio_sd>; + vccio4-supply = <&vcc_1v8>; + vccio5-supply = <&vcc_3v3>; + vccio6-supply = <&vcc_1v8>; + vccio7-supply = <&vcc_3v3>; +}; +&pmucru { + u-boot,dm-pre-reloc; + status = "okay"; +}; +&pmugrf { + u-boot,dm-pre-reloc; + status = "okay"; +}; +&saradc { + vref-supply = <&vcca_1v8>; + status = "okay"; +}; +&sfc { + pinctrl-names = "default"; + pinctrl-0 = <&fspi_pins>; + status = "okay"; +}; +&spi_nand { + status = "disabled"; +}; +&spi_nor { + status = "okay"; +}; +&sdhci { + bus-width = <8>; + max-frequency = <200000000>; + non-removable; + pinctrl-names = "default"; + pinctrl-0 = <&emmc_bus8 &emmc_clk &emmc_cmd &emmc_datastrobe>; + vmmc-supply = <&vcc_3v3>; + vqmmc-supply = <&vcc_1v8>; + status = "okay"; +}; +&sdmmc0 { + bus-width = <4>; + cap-sd-highspeed; + cd-gpios = <&gpio0 RK_PA4 GPIO_ACTIVE_LOW>; + disable-wp; + pinctrl-names = "default"; + pinctrl-0 = <&sdmmc0_bus4 &sdmmc0_clk &sdmmc0_cmd>; + sd-uhs-sdr104; + vmmc-supply = <&vcc3v3_sd>; + vqmmc-supply = <&vccio_sd>; + status = "okay"; +}; +&sdmmc2 { + bus-width = <4>; + cap-sd-highspeed; + cd-gpios = <&gpio3 RK_PD5 GPIO_ACTIVE_LOW>; + disable-wp; + pinctrl-names = "default"; + pinctrl-0 = <&sdmmc2m0_bus4 &sdmmc2m0_clk &sdmmc2m0_cmd>; + sd-uhs-sdr104; + status = "okay"; +}; +&uart2 { + status = "okay"; +}; diff --git a/config/chainload/yy3568/overlay/arch/arm/mach-rockchip/decode_bl31.py b/config/chainload/yy3568/overlay/arch/arm/mach-rockchip/decode_bl31.py new file mode 100644 index 0000000..1cff8c3 --- /dev/null +++ b/config/chainload/yy3568/overlay/arch/arm/mach-rockchip/decode_bl31.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 Rockchip Electronics Co., Ltd +# +# SPDX-License-Identifier: GPL-2.0+ +# +""" +A script to decode bl31.elf to binary +""" + +import os +import sys +import getopt +import logging +import struct + +def unpack_elf(filename): + with open(filename, 'rb') as file: + elf = file.read() + if elf[0:7] != b'\x7fELF\x02\x01\x01' or elf[18:20] != b'\xb7\x00': + raise ValueError("Invalid arm64 ELF file '%s'" % filename) + + e_entry, e_phoff = struct.unpack_from('<2Q', elf, 0x18) + e_phentsize, e_phnum = struct.unpack_from('<2H', elf, 0x36) + segments = [] + + for index in range(e_phnum): + offset = e_phoff + e_phentsize * index + p_type, p_flags, p_offset = struct.unpack_from(' 0: + p_data = elf[p_offset:p_offset + p_filesz] + segments.append((index, e_entry, p_paddr, p_data)) + return segments + +def generate_atf_binary(bl31_file_name): + for index, entry, paddr, data in unpack_elf(bl31_file_name): + file_name = 'bl31_0x%08x.bin' % paddr + with open(file_name, "wb") as atf: + atf.write(data) + +def main(): + if "BL31" in os.environ: + bl31_elf=os.getenv("BL31"); + elif os.path.isfile("./bl31.elf"): + bl31_elf = "./bl31.elf" + else: + os.system("echo 'int main(){}' > bl31.c") + os.system("${CROSS_COMPILE}gcc -c bl31.c -o bl31.elf") + bl31_elf = "./bl31.elf" + logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) + logging.warning(' BL31 file bl31.elf NOT found, resulting binary is non-functional') + logging.warning(' Please read Building section in doc/README.rockchip') + generate_atf_binary(bl31_elf); + +if __name__ == "__main__": + main() diff --git a/config/chainload/yy3568/overlay/arch/arm/mach-rockchip/rk3568/Kconfig b/config/chainload/yy3568/overlay/arch/arm/mach-rockchip/rk3568/Kconfig new file mode 100644 index 0000000..d08b5a1 --- /dev/null +++ b/config/chainload/yy3568/overlay/arch/arm/mach-rockchip/rk3568/Kconfig @@ -0,0 +1,23 @@ +if ROCKCHIP_RK3568 + +config TARGET_EVB_RK3568 + bool "EVB_RK3568" + select BOARD_LATE_INIT + help + RK3568 EVB is a evaluation board for Rockchp RK3568. + +config TARGET_YY3568 + bool "Youyeetoo YY3568 chainload" + select BOARD_LATE_INIT + help + YY3568 configuration used only by the manifest-driven rk chainloader. + +config SYS_SOC + default "rockchip" + +config SYS_MALLOC_F_LEN + default 0x400 + +source board/rockchip/evb_rk3568/Kconfig + +endif diff --git a/config/chainload/yy3568/overlay/board/rockchip/evb_rk3568/Kconfig b/config/chainload/yy3568/overlay/board/rockchip/evb_rk3568/Kconfig new file mode 100644 index 0000000..8ed793c --- /dev/null +++ b/config/chainload/yy3568/overlay/board/rockchip/evb_rk3568/Kconfig @@ -0,0 +1,17 @@ +if TARGET_EVB_RK3568 || TARGET_YY3568 + +config SYS_BOARD + default "yy3568" if TARGET_YY3568 + default "evb_rk3568" + +config SYS_VENDOR + default "rockchip" + +config SYS_CONFIG_NAME + default "yy3568" if TARGET_YY3568 + default "evb_rk3568" + +config BOARD_SPECIFIC_OPTIONS # dummy + def_bool y + +endif diff --git a/config/chainload/yy3568/overlay/board/rockchip/yy3568/Makefile b/config/chainload/yy3568/overlay/board/rockchip/yy3568/Makefile new file mode 100644 index 0000000..c543f9e --- /dev/null +++ b/config/chainload/yy3568/overlay/board/rockchip/yy3568/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-y += yy3568.o diff --git a/config/chainload/yy3568/overlay/board/rockchip/yy3568/yy3568.c b/config/chainload/yy3568/overlay/board/rockchip/yy3568/yy3568.c new file mode 100644 index 0000000..b059c2d --- /dev/null +++ b/config/chainload/yy3568/overlay/board/rockchip/yy3568/yy3568.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * YY3568 uses the pinned vendor RK3568 EVB board hooks. Board wiring and boot + * policy remain isolated in the YY3568 DTS, config header, and defconfig. + */ +#include "../evb_rk3568/evb_rk3568.c" diff --git a/config/chainload/yy3568/overlay/configs/yy3568-rk3568_defconfig b/config/chainload/yy3568/overlay/configs/yy3568-rk3568_defconfig new file mode 100644 index 0000000..a1ae818 --- /dev/null +++ b/config/chainload/yy3568/overlay/configs/yy3568-rk3568_defconfig @@ -0,0 +1,230 @@ +CONFIG_ARM=y +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_ARCH_ROCKCHIP=y +CONFIG_SPL_LIBCOMMON_SUPPORT=y +CONFIG_SPL_LIBGENERIC_SUPPORT=y +CONFIG_SYS_MALLOC_F_LEN=0x80000 +CONFIG_SPL_FIT_GENERATOR="arch/arm/mach-rockchip/make_fit_atf.sh" +CONFIG_ROCKCHIP_RK3568=y +CONFIG_ROCKCHIP_FIT_IMAGE=y +CONFIG_ROCKCHIP_VENDOR_PARTITION=y +CONFIG_ROCKCHIP_FIT_IMAGE_PACK=y +CONFIG_ROCKCHIP_NEW_IDB=y +CONFIG_ROCKCHIP_EMMC_IOMUX=y +CONFIG_SPL_SERIAL_SUPPORT=y +CONFIG_SPL_DRIVERS_MISC_SUPPORT=y +CONFIG_TARGET_YY3568=y +CONFIG_SPL_LIBDISK_SUPPORT=y +# CONFIG_SPL_NAND_SUPPORT is not set +CONFIG_SPL_SPI_FLASH_SUPPORT=y +CONFIG_SPL_SPI_SUPPORT=y +CONFIG_DEFAULT_DEVICE_TREE="rk3568-yy3568" +CONFIG_DEBUG_UART=y +CONFIG_FIT=y +CONFIG_FIT_IMAGE_POST_PROCESS=y +CONFIG_FIT_HW_CRYPTO=y +CONFIG_SPL_LOAD_FIT=y +CONFIG_SPL_FIT_IMAGE_POST_PROCESS=y +CONFIG_SPL_FIT_HW_CRYPTO=y +# CONFIG_SPL_SYS_DCACHE_OFF is not set +CONFIG_BOOTDELAY=3 +CONFIG_DISTRO_DEFAULTS=y +CONFIG_SYS_CONSOLE_INFO_QUIET=y +# CONFIG_DISPLAY_CPUINFO is not set +CONFIG_ANDROID_BOOTLOADER=y +CONFIG_ANDROID_AVB=y +CONFIG_ANDROID_BOOT_IMAGE_HASH=y +CONFIG_SPL_BOARD_INIT=y +# CONFIG_SPL_RAW_IMAGE_SUPPORT is not set +# CONFIG_SPL_LEGACY_IMAGE_SUPPORT is not set +CONFIG_SPL_SEPARATE_BSS=y +CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION=y +CONFIG_SPL_SHA256_SUPPORT=y +CONFIG_SPL_CRYPTO_SUPPORT=y +CONFIG_SPL_HASH_SUPPORT=y +CONFIG_SPL_MTD_SUPPORT=y +CONFIG_SPL_ATF=y +CONFIG_SPL_ATF_NO_PLATFORM_PARAM=y +CONFIG_SPL_AB=y +CONFIG_FASTBOOT_BUF_ADDR=0xc00800 +CONFIG_FASTBOOT_BUF_SIZE=0x04000000 +CONFIG_FASTBOOT_FLASH=y +CONFIG_FASTBOOT_FLASH_MMC_DEV=0 +CONFIG_CMD_BOOTZ=y +CONFIG_CMD_DTIMG=y +# CONFIG_CMD_ELF is not set +# CONFIG_CMD_IMI is not set +# CONFIG_CMD_IMLS is not set +# CONFIG_CMD_XIMG is not set +# CONFIG_CMD_LZMADEC is not set +# CONFIG_CMD_UNZIP is not set +# CONFIG_CMD_FLASH is not set +# CONFIG_CMD_FPGA is not set +CONFIG_CMD_GPT=y +# CONFIG_CMD_LOADB is not set +# CONFIG_CMD_LOADS is not set +CONFIG_CMD_BOOT_ANDROID=y +CONFIG_CMD_BOOT_ROCKCHIP=y +CONFIG_CMD_MMC=y +CONFIG_CMD_MTD=y +CONFIG_CMD_NAND=y +CONFIG_CMD_SF=y +CONFIG_CMD_PCI=y +CONFIG_CMD_NVME=y +CONFIG_CMD_FAT=y +CONFIG_CMD_EXT4=y +CONFIG_CMD_FS_GENERIC=y +CONFIG_CMD_USB=y +CONFIG_CMD_USB_MASS_STORAGE=y +# CONFIG_CMD_ITEST is not set +# CONFIG_CMD_SETEXPR is not set +CONFIG_CMD_TFTPPUT=y +CONFIG_CMD_TFTP_BOOTM=y +CONFIG_CMD_TFTP_FLASH=y +# CONFIG_CMD_MISC is not set +# CONFIG_CMD_CHARGE_DISPLAY is not set +CONFIG_CMD_MTD_BLK=y +# CONFIG_SPL_DOS_PARTITION is not set +# CONFIG_ISO_PARTITION is not set +CONFIG_EFI_PARTITION_ENTRIES_NUMBERS=64 +CONFIG_SPL_OF_CONTROL=y +CONFIG_SPL_DTB_MINIMUM=y +CONFIG_OF_LIVE=y +CONFIG_OF_SPL_REMOVE_PROPS="" +# CONFIG_NET_TFTP_VARS is not set +CONFIG_REGMAP=y +CONFIG_SPL_REGMAP=y +CONFIG_SYSCON=y +CONFIG_SPL_SYSCON=y +CONFIG_CLK=y +CONFIG_SPL_CLK=y +CONFIG_CLK_SCMI=y +CONFIG_DM_CRYPTO=y +CONFIG_SPL_DM_CRYPTO=y +CONFIG_ROCKCHIP_CRYPTO_V2=y +CONFIG_SPL_ROCKCHIP_CRYPTO_V2=y +CONFIG_DM_RNG=y +CONFIG_RNG_ROCKCHIP=y +CONFIG_SCMI_FIRMWARE=y +CONFIG_ROCKCHIP_GPIO=y +CONFIG_ROCKCHIP_GPIO_V2=y +CONFIG_SYS_I2C_ROCKCHIP=y +CONFIG_DM_KEY=y +CONFIG_RK8XX_PWRKEY=y +CONFIG_ADC_KEY=y +CONFIG_MISC=y +CONFIG_SPL_MISC=y +CONFIG_ROCKCHIP_OTP=y +CONFIG_SPL_ROCKCHIP_SECURE_OTP=y +CONFIG_MMC_DW=y +CONFIG_MMC_DW_ROCKCHIP=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_SDMA=y +CONFIG_MMC_SDHCI_ROCKCHIP=y +CONFIG_MTD=y +CONFIG_MTD_BLK=y +CONFIG_MTD_DEVICE=y +# CONFIG_NAND is not set +# CONFIG_MTD_SPI_NAND is not set +CONFIG_SPI_FLASH=y +CONFIG_SF_DEFAULT_SPEED=20000000 +CONFIG_SPI_FLASH_EON=y +CONFIG_SPI_FLASH_GIGADEVICE=y +CONFIG_SPI_FLASH_MACRONIX=y +CONFIG_SPI_FLASH_WINBOND=y +CONFIG_SPI_FLASH_XMC=y +CONFIG_SPI_FLASH_MTD=y +CONFIG_DM_ETH=y +CONFIG_DM_ETH_PHY=y +CONFIG_DWC_ETH_QOS=y +CONFIG_GMAC_ROCKCHIP=y +CONFIG_NVME=y +CONFIG_PCI=y +CONFIG_DM_PCI=y +CONFIG_DM_PCI_COMPAT=y +CONFIG_PCIE_DW_ROCKCHIP=y +CONFIG_PHY_ROCKCHIP_INNO_USB2=y +CONFIG_PHY_ROCKCHIP_NANENG_COMBOPHY=y +CONFIG_PHY_ROCKCHIP_NANENG_EDP=y +CONFIG_PHY_ROCKCHIP_SNPS_PCIE3=y +CONFIG_PINCTRL=y +CONFIG_SPL_PINCTRL=y +CONFIG_DM_FUEL_GAUGE=y +CONFIG_POWER_FG_RK817=y +CONFIG_IO_DOMAIN=y +CONFIG_ROCKCHIP_IO_DOMAIN=y +CONFIG_DM_PMIC=y +CONFIG_PMIC_RK8XX=y +CONFIG_REGULATOR_FAN53555=y +CONFIG_REGULATOR_PWM=y +CONFIG_DM_REGULATOR_FIXED=y +CONFIG_DM_REGULATOR_GPIO=y +CONFIG_REGULATOR_RK8XX=y +# CONFIG_DM_CHARGE_DISPLAY is not set +# CONFIG_CHARGE_ANIMATION is not set +CONFIG_PWM_ROCKCHIP=y +CONFIG_RAM=y +CONFIG_SPL_RAM=y +CONFIG_TPL_RAM=y +CONFIG_DM_RAMDISK=y +CONFIG_RAMDISK_RO=y +CONFIG_DM_DMC=y +CONFIG_ROCKCHIP_DMC_FSP=y +CONFIG_ROCKCHIP_SDRAM_COMMON=y +CONFIG_ROCKCHIP_TPL_INIT_DRAM_TYPE=0 +CONFIG_DM_RESET=y +CONFIG_SPL_DM_RESET=y +CONFIG_SPL_RESET_ROCKCHIP=y +CONFIG_BAUDRATE=1500000 +CONFIG_DEBUG_UART_BASE=0xFE660000 +CONFIG_DEBUG_UART_CLOCK=24000000 +CONFIG_DEBUG_UART_SHIFT=2 +CONFIG_ROCKCHIP_SFC=y +CONFIG_SYSRESET=y +CONFIG_USB=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_XHCI_DWC3=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_GENERIC=y +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_OHCI_GENERIC=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_GADGET=y +CONFIG_USB_DWC3_GENERIC=y +CONFIG_USB_STORAGE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_MANUFACTURER="Rockchip" +CONFIG_USB_GADGET_VENDOR_NUM=0x2207 +CONFIG_USB_GADGET_PRODUCT_NUM=0x350a +CONFIG_USB_GADGET_DOWNLOAD=y +CONFIG_DM_VIDEO=y +CONFIG_DISPLAY=y +CONFIG_DRM_ROCKCHIP=y +CONFIG_DRM_ROCKCHIP_DW_HDMI=y +CONFIG_DRM_ROCKCHIP_INNO_MIPI_PHY=y +CONFIG_DRM_ROCKCHIP_INNO_VIDEO_COMBO_PHY=y +CONFIG_DRM_ROCKCHIP_DW_MIPI_DSI=y +CONFIG_DRM_ROCKCHIP_ANALOGIX_DP=y +CONFIG_DRM_ROCKCHIP_LVDS=y +CONFIG_DRM_ROCKCHIP_RGB=y +CONFIG_ROCKCHIP_CUBIC_LUT_SIZE=9 +CONFIG_LCD=y +CONFIG_USE_TINY_PRINTF=y +CONFIG_SPL_TINY_MEMSET=y +CONFIG_RSA=y +CONFIG_SPL_RSA=y +CONFIG_RSA_N_SIZE=0x200 +CONFIG_RSA_E_SIZE=0x10 +CONFIG_RSA_C_SIZE=0x20 +CONFIG_XBC=y +CONFIG_SHA512=y +CONFIG_LZ4=y +CONFIG_LZMA=y +CONFIG_SPL_GZIP=y +CONFIG_ERRNO_STR=y +CONFIG_EFI_LOADER=y +CONFIG_AVB_LIBAVB=y +CONFIG_AVB_LIBAVB_AB=y +CONFIG_AVB_LIBAVB_ATX=y +CONFIG_AVB_LIBAVB_USER=y +CONFIG_RK_AVB_LIBAVB_USER=y diff --git a/config/chainload/yy3568/overlay/include/configs/yy3568.h b/config/chainload/yy3568/overlay/include/configs/yy3568.h new file mode 100644 index 0000000..3249dbf --- /dev/null +++ b/config/chainload/yy3568/overlay/include/configs/yy3568.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Board-scoped U-Boot policy for the rk YY3568 chainloader. */ +#ifndef __CONFIGS_YY3568_H +#define __CONFIGS_YY3568_H + +#include + +#ifndef CONFIG_SPL_BUILD + +#undef ROCKCHIP_DEVICE_SETTINGS +#define ROCKCHIP_DEVICE_SETTINGS \ + "stdin=serial,usbkbd\0" \ + "stdout=serial,vidconsole\0" \ + "stderr=serial,vidconsole\0" + +#define CONFIG_SYS_MMC_ENV_DEV 0 + +/* Board aliases: mmc1 is the SD slot, mmc0 is eMMC, and mmc2 is SDIO. */ +#undef CONFIG_BOOTCOMMAND +#define CONFIG_BOOTCOMMAND \ + "setenv boot_targets nvme0 nvme1 mmc1 usb0 mmc0; run distro_bootcmd" + +#endif +#endif diff --git a/demo/bmp.c b/demo/bmp.c index 65b6988..8623596 100644 --- a/demo/bmp.c +++ b/demo/bmp.c @@ -5,26 +5,35 @@ #define BACKGROUND_COLOR 0x4398D7 static uintptr_t fb_addr = 0x0; -static uint32_t screen_width = 1920; -static uint32_t screen_height = 1080; +static uint32_t screen_width; +static uint32_t screen_height; +static uint32_t screen_stride; static int last_x = 10; static int last_y = 50; static inline void font_draw_pixel(int x, int y, int col) { x *= 2; y *= 2; - ((uint64_t *)(&((uint32_t *)fb_addr)[y * screen_width + x]))[0] = (uint64_t)col << 32 | (uint64_t)col; - ((uint64_t *)(&((uint32_t *)fb_addr)[(y + 1) * screen_width + x]))[0] = (uint64_t)col << 32 | (uint64_t)col; + if ((unsigned int)(x + 1) >= screen_width || + (unsigned int)(y + 1) >= screen_height) + return; + uint32_t stride_pixels = screen_stride / 4; + ((uint64_t *)(&((uint32_t *)fb_addr)[y * stride_pixels + x]))[0] = + (uint64_t)col << 32 | (uint64_t)col; + ((uint64_t *)(&((uint32_t *)fb_addr)[(y + 1) * stride_pixels + x]))[0] = + (uint64_t)col << 32 | (uint64_t)col; } int font_print_char(int x, int y, char c) { // Loop to "null terminator character" - int match = 0; + int match = -1; for (int l = 0; font[l].letter != 0; l++) { if (font[l].letter == c) { match = l; break; } } + if (match < 0) + return 0; // Loop through 7 high 5 wide monochrome font int maxLength = 0; @@ -59,7 +68,7 @@ int font_print_string(int x, int y, const char *string) { cx += length; // wrapping - if (cx > screen_width - 20 && string[c] == ' ') { + if ((unsigned int)(cx * 2) > screen_width - 20 && string[c] == ' ') { cx = x; cy += 8; } @@ -80,23 +89,35 @@ void bmp_clear(void) { last_x = 10; last_y = 10; - uint64_t *framebuffer = (uint64_t *)fb_addr; - - for (int i = 0; i < screen_height * screen_width / 2; i++) { - framebuffer[i] = (uint64_t)BACKGROUND_COLOR | ((uint64_t)BACKGROUND_COLOR << 32); - } + uint32_t *framebuffer = (uint32_t *)fb_addr; + for (uint32_t y = 0; y < screen_height; y++) + for (uint32_t x = 0; x < screen_width; x++) + framebuffer[y * (screen_stride / 4) + x] = BACKGROUND_COLOR; } int bmp_print_char(char c) { if (c == ' ') { last_x += 5; + } else if (c == '\t') { + last_x = 10 + (((last_x - 10) / 32) + 1) * 32; + } else if (c == '\b') { + if (last_x > 10) + last_x = last_x > 17 ? last_x - 7 : 10; + for (int y = 0; y < 8; y++) + for (int x = 0; x < 7; x++) + font_draw_pixel(last_x + x, last_y + y, + BACKGROUND_COLOR); } else if (c == '\n') { last_y += 10; } else if (c == '\r') { last_x = 10; - } else { + } else if (c >= 0x20 && c <= 0x7e) { last_x += font_print_char(last_x, last_y, c) + 3; } + if ((unsigned int)(last_x * 2 + 14) >= screen_width) { + last_x = 10; + last_y += 10; + } return 1; } @@ -110,12 +131,12 @@ int bmp_print(const char *s) { int bmp_setup(void) { struct FuScreenList *list = (struct FuScreenList *)fw_handler(FU_GET_SCREEN_LIST, 0, 0, 0); - // list is in secure memory, can't access - - if (list->length != 0) { + if (list != (struct FuScreenList *)FU_ERROR && list->length != 0 && + list->type == FU_SCREEN_XRGB8888) { fb_addr = list->screens[0].framebuffer_addr; screen_width = list->screens[0].width; screen_height = list->screens[0].height; + screen_stride = list->screens[0].stride; return 0; } return 1; diff --git a/demo/entry.S b/demo/entry.S index 32c9f8b..97ed087 100644 --- a/demo/entry.S +++ b/demo/entry.S @@ -1,5 +1,6 @@ .extern entry .extern exception_table +.extern _end .global _start _start: adr x20, exception_table @@ -16,7 +17,7 @@ _start: .int 0x08008135 // magic .int 0x1 // version .int 0x1 // flags: relocate - .int 200000 // img_size: guess 200kb for now + .int _end - _start // complete payload image, including zero-filled BSS .int 0xa00000; .int 0x0 // relocation_addr .global fw_handler diff --git a/demo/main.c b/demo/main.c index ac080c1..2641a64 100644 --- a/demo/main.c +++ b/demo/main.c @@ -4,6 +4,18 @@ static int bmp_status = 1; +static uint64_t timer_count(void) { + uint64_t value; + asm volatile("mrs %0, cntpct_el0" : "=r"(value)); + return value; +} + +static uint64_t timer_frequency(void) { + uint64_t value; + asm volatile("mrs %0, cntfrq_el0" : "=r"(value)); + return value; +} + void itoa(uint64_t n, char *buffer, int base) { int i = 12; @@ -66,13 +78,15 @@ int puts(const char *s) { int entry(uintptr_t firmware_function, uintptr_t _start) { puts("Hello World from Payload"); - char buf1[64]; + char buf1[96]; char buf2[20]; uint64_t el; asm volatile("mrs %0, CurrentEl" : "=r"(el)); bmp_status = bmp_setup(); + struct FuScreenList *screens = (struct FuScreenList *) + fw_handler(FU_GET_SCREEN_LIST, 0, 0, 0); struct FuDeviceInfo *info = (struct FuDeviceInfo *)fw_handler(FU_GET_DEVICE_INFO, 0, 0, 0); @@ -82,6 +96,17 @@ int entry(uintptr_t firmware_function, uintptr_t _start) { strcat(buf1, info->product); strcat(buf1, "'"); puts(buf1); + if (screens != (struct FuScreenList *)FU_ERROR && screens->length) { + strcpy(buf1, "Video mode: "); + itoa(screens->screens[0].width, buf2, 10); + strcat(buf1, buf2); + strcat(buf1, "x"); + itoa(screens->screens[0].height, buf2, 10); + strcat(buf1, buf2); + puts(buf1); + } else { + puts("Video mode: headless"); + } strcpy(buf1, "We are in EL"); itoa(el >> 2, buf2, 10); @@ -111,8 +136,28 @@ int entry(uintptr_t firmware_function, uintptr_t _start) { puts(buf1); } - for (int i = 0x10000000; i != 0; i--) { - __asm__("nop"); + puts("Keyboard input (30 seconds):"); + uint64_t start = timer_count(); + uint64_t duration = timer_frequency() * 30; + while (timer_count() - start < duration) { + if (!fw_handler(FU_POLL_CHAR, 0, 0, 0)) + continue; + char c = (char)fw_handler(FU_GET_CHAR, 0, 0, 0); + if (!c) + continue; + if (c == '\b') { + fw_handler(FU_PRINT_CHAR, '\b', 0, 0); + fw_handler(FU_PRINT_CHAR, ' ', 0, 0); + fw_handler(FU_PRINT_CHAR, '\b', 0, 0); + } else { + fw_handler(FU_PRINT_CHAR, c, 0, 0); + } + if (!bmp_status) + bmp_print_char(c); + if (c == '\r') { + fw_handler(FU_PRINT_CHAR, '\n', 0, 0); + if (!bmp_status) bmp_print_char('\n'); + } } puts("Turning off..."); diff --git a/docs/devices/genbook.md b/docs/devices/genbook.md index cc56d63..fe24fbf 100644 --- a/docs/devices/genbook.md +++ b/docs/devices/genbook.md @@ -1,7 +1,34 @@ # Genbook / Cool-pi notebook Photos: https://www.flickr.com/photos/201609787@N08/albums/72177720322040367/ +## Bare-metal firmware status + +For a standalone SD or MaskROM test, use `genbook_demo.img` or +`demo_genbook.bin`. These contain the example EL2 payload. `genbook.img` and +`genbook.bin` contain firmware only: they expect a compatible FUEFI payload to +be appended and otherwise report `Bad payload magic` and halt. + +In this flow, RK3588 BootROM reads the RKNS image from SD and places it in RAM. +The current bare-metal firmware does not contain an RK3588 SD/MMC driver and +cannot read files or partitions from the card after entry. + +The minimal RK3588 DTS lists SDMMC and USB controller placeholders for +reference and future payload work, but listing a node does not initialize the +controller. Several required `reg` and `status` properties are absent, and the +current RK3588 firmware links no MMC, OHCI, xHCI, or USB-PHY driver. As a +result, the Genbook demo validates UART, eDP/framebuffer, DTB presence, memory +map, and EL2 handoff, but its keyboard prompt cannot receive USB input. Do not +use the minimal firmware DTB as a Linux board DTB. See +[Firmware images, payloads, and device trees](../payloads.md). + +## BootROM and legacy recovery notes + The Genbook comes with U-boot SPL on the SPI flash, you can make it unbootable like so: + +> **Warning:** the commands below intentionally corrupt or erase boot media. +> They are historical recovery notes, not guarded installers. Make verified +> off-device backups first and confirm that forced MaskROM entry works. + ``` printf '\x00\x00\x00\x00' | dd of=/dev/mtdblock0 bs=1 seek=$((0x10000)) count=4 conv=notrunc printf '\x00\x00\x00\x00' | dd of=/dev/mtdblock0 bs=1 seek=$((0x60000)) count=4 conv=notrunc @@ -26,6 +53,9 @@ xrock flash erase 0 100000 in order to erase the SPI: ``` xrock maskrom rk3588_ddr_lp4_2112MHz_lp5_2400MHz_v1.16.bin rk3588_usbplug_v1.11.bin --rc4-off -# I didn't see an option to erase loader/SPI flash, so I just wrote a random 30mb file. -rkdeveloptool wl 0 '/home/daniel/Downloads/fpupdate-output-xa2-loader.bin' ``` + +There is currently no board-qualified, backup-and-verify SPI installer for the +Genbook in this repository. Do not substitute an unrelated file or perform a +whole-device overwrite merely to invalidate SPI; add a guarded RK3588 storage +policy and tested restoration path first. diff --git a/docs/index.md b/docs/index.md index 713f6c4..6758a24 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,8 +1,28 @@ -# Rockchip bare metal reference -This is a work-in-progress resource for bare-metal bringup for Rockchip devices. +# Rockchip bare-metal reference -Source code: https://github.com/petabyt/rk +This documentation covers two related uses of the repository: -For questions: daniel (AT) futo.org +1. Learning and experimenting with low-level Rockchip hardware bring-up. +2. Building practical first-stage firmware and board-specific boot artifacts. + +Start with the page that matches your goal: + +| Goal | Documentation | +| --- | --- | +| Build or run a board for the first time | [Getting started](intro.md) | +| Understand firmware-only, demo, SD delivery, payload, and DTB roles | [Firmware images, payloads, and device trees](payloads.md) | +| Compare supported RK3566/RK3568 boards | [RK356x devices](rk356x/index.md) | +| Understand RK356x display, input, payload handoff, and memory layout | [RK356x common bare-metal firmware](rk356x/bare-metal.md) | +| Boot an enabled RK3568 board through BL31/U-Boot and storage discovery | [RK356x chainloading](rk356x/chainloading.md) | +| Install or restore board-qualified SPI NOR/eMMC firmware | [Guarded installation](rk356x/chainloading.md#guarded-emmc-and-spi-nor-installation) | +| Understand possible EDK2 and OP-TEE integration | [Future EDK2 and OP-TEE integration](rk356x/chainloading.md#future-edk2-and-op-tee-integration) | +| Download or publish binary releases | [Binary releases](releases.md) | +| Study RK3399 or RK3588 internals | [Reference topics](ref.md) | + +The repository source is available at . + +Hardware support is board-scoped. Read the board notes and recovery procedure +before writing persistent storage; a register map shared by two boards does +not imply identical GPIO, regulator, flash, or display wiring. Copyright FUTO (C) 2025 FUTO diff --git a/docs/intro.md b/docs/intro.md index fa52d4a..3dbfff3 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -1,45 +1,284 @@ # Getting started -## Development -The best way to setup a rockchip device for bare-metal development is by enabling the 'maskrom' interface that allows custom images to be loaded -over an OTG port. +## Decide what you want to run -In order to get this working, you have to make all bootable mediums un-bootable by the bootrom. This includes emmc, any SPI flash, or -sdmmc. You don't have to completely erase these devices, you just need to erase the rockchip magic at the start (`RKNS` or `RK33`) +The repository produces three intentionally separate firmware styles: -This wiki has instructions for how to do this on various rockchip devices, see Devices/ +| Style | Purpose | Storage awareness | +| --- | --- | --- | +| Normal bare-metal firmware | Developer base that initializes hardware and requires an appended EL2 payload | No PCIe/NVMe/filesystem drivers | +| Demo firmware | Standalone smoke test with the repository's example payload appended | No PCIe/NVMe/filesystem drivers | +| Board-scoped RK3568 chainloader | Validate a FIT, enter BL31, and start the selected board's U-Boot at EL2 | U-Boot—not the first stage—handles storage | -For most devices you will need a usb-c to usb-a data cable to plug it into your dev machine. In other cases you will need a usb-a to usb-a -cable. +Choose the normal/demo images when developing firmware services or an EL2 +payload. Choose a supported RK3568 chainloader when the goal is Linux, +extlinux, or an EFI application on NVMe, SD, USB mass storage, or eMMC. -Once you get your rockchip device setup and plugged in (check `dmesg -w`), you will need a tool to send the images over USB. -This repo includes a tool (`make rock.out`) that can do this. You can also use [xrock](https://github.com/xboot/xrock). +Read [Firmware images, payloads, and device trees](payloads.md) before choosing +an SD artifact. In particular, a normal firmware-only `.img` has no payload and +halts with `Bad payload magic`; it is not the standalone version of the demo. -For RK3399 devices: +## Use a release or build from source + +A release archive is the simplest option when you only need board binaries. +Download the archive and top-level `SHA256SUMS`, verify it, then verify the +archive's internal checksums after extracting: + +```sh +grep 'yy3568.tar.xz' SHA256SUMS | sha256sum -c - +tar -xJf rk-v1.2.3-yy3568.tar.xz +cd rk-v1.2.3-yy3568 +sha256sum -c SHA256SUMS +``` + +Build from source when modifying firmware, U-Boot policy, or board support: + +```sh +sudo apt install gcc-aarch64-linux-gnu libusb-1.0-0-dev make xxd \ + device-tree-compiler cpp python3 xz-utils +make all +make SHELL=/bin/bash check -j"$(nproc)" +``` + +The normal build uses vendored Rockchip inputs and is offline. The optional +chainloader build needs the pinned U-Boot source unless `UBOOT_SRC` or a +compatible `UBOOT_ITB` is supplied locally. + +## Prepare a Linux programming host + +Linux does not need the proprietary Windows Rockchip USB driver. MaskROM and +loader mode appear as vendor-specific USB devices; `xrock` and +`rkdeveloptool` communicate with them through libusb. A udev rule grants the +interactive user or self-hosted CI runner access to the USB device. + +Install the build dependencies on Debian or Ubuntu: + +```sh +sudo apt-get update +sudo apt-get install -y \ + build-essential git pkg-config libusb-1.0-0-dev libudev-dev \ + autoconf automake libtool libtool-bin dh-autoreconf usbutils +``` + +Build the revisions pinned by the RK3568 chainloader manifests. These tools +are installed separately because release archives intentionally do not bundle +host executables: + +```sh +mkdir -p rockchip-host-tools +cd rockchip-host-tools + +git clone https://github.com/xboot/xrock.git +git -C xrock checkout --detach \ + b90d3ba8f0a48320e3888701f7e66e0e4e038bbb +make -C xrock -j"$(nproc)" +sudo install -m 0755 xrock/xrock /usr/local/bin/xrock + +git clone https://github.com/rockchip-linux/rkdeveloptool.git +git -C rkdeveloptool checkout --detach \ + 304f073752fd25c854e1bcf05d8e7f925b1f4e14 +cd rkdeveloptool +./autogen.sh +./configure +make -j"$(nproc)" +sudo install -m 0755 rkdeveloptool /usr/local/bin/rkdeveloptool +cd .. ``` -xrock -./rock.out --v1 --ddr --os + +Do not install the upstream world-writable udev rules on a shared host. Create +a dedicated group and a repository-scoped rule instead. The three product IDs +below cover the RK3399, RK356x, and RK3588 devices understood by `rock.out`: + +```sh +sudo groupadd --force rockchip +sudo usermod -aG rockchip "$USER" + +sudo tee /etc/udev/rules.d/70-rk-project.rules >/dev/null <<'EOF' +SUBSYSTEM!="usb", GOTO="rk_project_end" + +ATTRS{idVendor}=="2207", ATTRS{idProduct}=="330c", MODE="0660", GROUP="rockchip", TEST=="power/autosuspend", ATTR{power/autosuspend}="-1" +ATTRS{idVendor}=="2207", ATTRS{idProduct}=="350a", MODE="0660", GROUP="rockchip", TEST=="power/autosuspend", ATTR{power/autosuspend}="-1" +ATTRS{idVendor}=="2207", ATTRS{idProduct}=="350b", MODE="0660", GROUP="rockchip", TEST=="power/autosuspend", ATTR{power/autosuspend}="-1" + +LABEL="rk_project_end" +EOF + +sudo udevadm control --reload-rules +sudo udevadm trigger --subsystem-match=usb +``` + +Log out and back in, then reconnect the board. A self-hosted GitHub Actions +runner service must also be restarted so it receives the new supplementary +group. Find its exact unit name before restarting it: + +```sh +systemctl list-unit-files 'actions.runner*' +sudo systemctl restart actions.runner..service +``` + +Confirm that the pinned tools are on the runner's `PATH` and that +`rkdeveloptool` provides the storage-selection command required by the guarded +installer: + +```sh +command -v xrock +command -v rkdeveloptool +rkdeveloptool -h | grep ChangeStorage ``` -For RK3588 devices: + +Force an RK356x board into MaskROM using its documented recovery control and +confirm access without `sudo`: + +```sh +lsusb -d 2207:350a +rkdeveloptool ld ``` -xrock --rc4-off -./rock.out --v2 --ddr --os + +Exactly one device should be present and report PID `0x350a` with +`Mode=Maskrom`. Test the MaskROM-to-loader transition without writing storage: + +```sh +cd /path/to/rk +make maskrom3568 +rkdeveloptool ld ``` -## How to program Rockchip hardware +The second listing should report `Mode=Loader`. Read-only probes such as +`rkdeveloptool rci`, `rkdeveloptool cs 9`, `rkdeveloptool rid`, and +`rkdeveloptool rfi` can then confirm SPI-NOR access; use `cs 1` for eMMC. +Do not run `ef` or `wl` while validating the host environment. Persistent +writes should go through the guarded board-qualified installer described in +[RK356x chainloading](rk356x/chainloading.md#guarded-emmc-and-spi-nor-installation). + +When the runner is a virtual machine or container, pass through the physical +USB device and keep the mapping active across the MaskROM-to-loader USB +re-enumeration. A runner installed directly as a host service is simpler for +hardware testing. + +## Start with RAM-only MaskROM loading + +Direct USB loading is the safest first hardware test because it does not write +SPI, eMMC, SD, or NVMe. Connect the board's OTG port to the build host, enter +MaskROM using the board's documented recovery control, and verify that exactly +one Rockchip device appears. + +Do not erase every boot medium as a first step. Rockchip BootROM normally tries +persistent media before USB, but supported boards provide a hardware recovery +method for forcing MaskROM. Invalidation or restoration of persistent firmware +should be a deliberate recovery action, not routine setup. + +Build and load the demo matching the board: + +```sh +make usb3399 # Pinebook Pro +make usb3588 # Genbook +make usb3566 # ROC-RK3566-PC +make usb BOARD=yy3568 +make usb BOARD=rock3a +``` + +`rock.out` sends the DDR image through BootROM command `0x471` and the firmware +or demo through `0x472`. The image runs from RAM and disappears after reset. +The `maskrom3566` and `maskrom3568` targets provide the alternative xrock +USB-plug flow. + +## Connect serial before debugging display or boot + +Serial output is the primary evidence for early firmware: + +| Target | UART setting | +| --- | --- | +| RK3566/RK3568 normal and chainloader firmware | UART2 M0, 1,500,000 baud, 8N1 | +| Existing RK3399/RK3588 helpers | 115,200 baud unless the board guide says otherwise | + +For RK356x: + +```sh +make uart2 +# equivalent: screen /dev/ttyUSB0 1500000 +``` + +Expected RK3568 chainloader output includes board/SoC identity, +`source=usb`, `source=sd`, `source=emmc`, or `source=spi-nor`, validation +status, and the BL31/U-Boot transition. + +## Boot a normal or demo SD image + +For a standalone first boot, build the demo for the selected board: + +```sh +make demo_roc3566.img +make demo_yy3568.img +make demo_rock3a.img +``` + +Write the complete `.img` with a trusted imaging tool. The destination is the +whole SD device, not a partition, and existing contents will be overwritten. +Verify the device identity and size with `lsblk` before writing. + +Rockchip BootROM reads the RKNS image, runs its DDR loader, and copies the +firmware plus any appended payload into RAM. This does not require an SD/MMC +driver in the firmware, and the firmware cannot read files from the card after +BootROM hands over control. + +Demo images append the repository's example EL2 payload and are the correct +initial hardware test. Normal `.bin` and `.img` artifacts contain firmware +only. They unconditionally look for an appended FUEFI payload, report +`Bad payload magic`, and halt when run alone. To use one, append a compatible +payload to the `.bin` and regenerate the RKNS image so its header covers the +combined binary. See [the custom payload workflow](payloads.md#build-a-custom-payload-image). + +## Boot an enabled RK3568 board into U-Boot + +For a first chainloader test, stay RAM-only: + +```sh +make chainload BOARD=rock3a +make chainload-check BOARD=rock3a +make usb-chainload BOARD=rock3a +``` + +At the U-Boot prompt, basic evidence is: + +```text +pci enum +nvme scan +nvme info +part list nvme 0 +``` + +Once that works, choose SD, eMMC, or SPI NOR for autonomous first-stage boot. +Replace `rock3a` with `yy3568` to select that board's independent manifest. +Read [RK356x chainloading](rk356x/chainloading.md) before installation. SPI NOR has +higher immutable BootROM priority than eMMC, SD, and ordinary USB fallback. + +## Recovery model -The best way to learn how to program certain Rockchip hardware is study the its device tree file. -A device tree describes how all the hardware in a device is wired and setup, and how to configure it (turning on GPIO pins, changing iomux functions) -It will map devices such as fans, LEDs, I2C devices, screens, any other non-enumerable hardware. +- Direct MaskROM runs only in RAM and cannot damage persistent storage. +- An invalid chainloader FIT logs an error and requests reset-to-MaskROM. +- The guarded installer requires a new backup directory before any write. +- eMMC installation preserves LBA 0-63 and writes only the ID block beginning + at LBA `0x40`, after rejecting MBR/GPT partition overlap. +- SPI installation backs up the complete detected NOR and writes only the + required range at offset zero. +- Every persistent write is read back and SHA-256 verified before reset. -For example, here's the DTS file for the Genbook: https://github.com/torvalds/linux/blob/master/arch/arm64/boot/dts/rockchip/rk3588-coolpi-cm5-genbook.dts +Keep backups outside the target device. A backup stored only on the device +being modified is not a recovery copy. -Schematics, if available, are also just as useful. +## Learn from DTS and schematics -## Memory +Device trees describe non-enumerable board wiring: GPIOs, pinmux, regulators, +clocks, buses, displays, LEDs, and resets. Schematics and vendor DTS files are +the starting point for a new board, but they must be checked against actual +hardware and the relevant TRM. -On the RK3588, physical memory from `0x0`-`0xa00000` appears to be reserved for -secure world only. If you try and execute code in non-secure state (EL0-EL2) then -you'll get a '32-bit instruction trap' exception brought up in EL3. +A DT node is descriptive data, not a driver. BootROM does not consume this +project's DTB, and listing a USB or MMC controller does not make it operational +in the firmware. A payload may rely on a node only when the node is complete, +the firmware has prepared the hardware as required, and the payload has a +matching driver. The minimal firmware DTBs are not Linux board DTBs. -The RK3399 has the same situation but the range is `0x0`-`0x200000`. +Shared SoC support is not enough to enable a board. A new target needs an +explicit descriptor or chainloader manifest, reviewed memory ranges and +pinmux, isolated objects, tests, and physical hardware sign-off. diff --git a/docs/payloads.md b/docs/payloads.md new file mode 100644 index 0000000..4cfa38e --- /dev/null +++ b/docs/payloads.md @@ -0,0 +1,113 @@ +# Firmware images, payloads, and device trees + +This repository produces several files that look bootable but have different +roles. The distinction matters most when choosing an SD image. + +## The three execution models + +| Model | Standalone? | Final program | Runtime storage drivers | +| --- | --- | --- | --- | +| Firmware-only `.bin` or `.img` | No | A separately appended FUEFI payload | No | +| Demo `.bin` or `.img` | Yes | The repository's example EL2 payload | No | +| Board-enabled RK3568 chainloader | Yes | BL31 and U-Boot at EL2 | U-Boot provides them | + +The normal bare-metal firmware is a hardware-initialization and service layer, +not an operating system or conventional storage bootloader. It initializes the +supported parts of the board, exposes FUEFI calls, and unconditionally looks +for a `FuPayloadHeader` immediately after the firmware image. A firmware-only +artifact has no such payload: when run by itself it reports `Bad payload magic` +and halts. It is distributed as a base for payload developers. + +The demo artifact is the corresponding firmware with `demo.bin` appended. It +is the correct standalone image for initial hardware testing. The demo reports +the board, current exception level, video mode, DTB status, and memory map. It +also exercises input on boards where a USB keyboard driver is implemented. + +The optional RK3568 chainloader is a separate build variant. It does not use +the FUEFI payload path and deliberately excludes display, USB HID, and demo +code. It validates a U-Boot FIT and enters BL31, which then starts U-Boot. + +## What SD boot means + +An SD image does not imply that the bare-metal firmware contains an SD/MMC +driver. For the normal and demo flows, the immutable Rockchip BootROM reads the +RKNS image from SD before this project's code starts: + +```text +SD card + -> Rockchip BootROM reads the RKNS image + -> BootROM runs the image's DDR loader + -> BootROM loads the firmware or firmware+payload into RAM + -> this project's firmware initializes the supported hardware + -> jump to the appended payload, if present +``` + +Once control reaches this firmware, the SD card is only the delivery medium. +The normal/demo firmware cannot mount its partitions or load another file from +it. The same principle applies to direct MaskROM USB loading: USB transports +the image into RAM, but it does not automatically provide a runtime USB host +stack. + +## Build a custom payload image + +A FUEFI payload begins with the packed header described in `src/firmware.h`. +The supplied demo requests relocation to `0x00a00000` and runs at EL2. A custom +payload must use the selected board's reviewed memory map and must not overlap +the firmware, stacks, shared/DMA arena, framebuffer, or MMIO. + +The basic source-tree workflow is: + +```sh +# Build the board firmware and your FUEFI-compatible payload first. +cat rock3a.bin my_payload.bin > my_rock3a.bin + +# Package the combined binary, not the firmware-only binary. +./makeboot.out --v2 \ + --ddr img/rk3568_ddr_1560MHz_v1.25.bin \ + --os my_rock3a.bin -o my_rock3a.img +``` + +Use the DDR loader and RKNS version appropriate for the selected board. The +existing `demo_.bin` and demo-image rules are the reference packaging +implementation. + +## What a device tree does—and does not do + +A DTB is data passed to a payload. It can describe addresses, interrupts, +GPIOs, regulators, clocks, and board identity, but it does not initialize a +controller and it does not add a driver to the firmware. Rockchip BootROM does +not read this project's DTB. + +There are therefore three separate questions for every device: + +1. Is it described accurately in the selected board DTB? +2. Does this bare-metal firmware initialize it and expose a service for it? +3. Does the eventual payload or operating system contain a compatible driver? + +All three are required before a payload can rely on a DT-described peripheral. +The minimal DTBs in this repository document the firmware contract; they are +not replacements for the complete upstream Linux board DTBs. + +The distinction is especially visible on RK3588. Its minimal SoC DTS lists +SDMMC and several USB controller placeholders, but several `reg` and `status` +properties are intentionally absent or commented out, and the current RK3588 +firmware links no MMC, OHCI, xHCI, or USB-PHY driver. Those nodes do not provide +runtime SD or USB support. The Genbook demo is useful for UART, eDP/framebuffer, +DTB, memory-map, and EL2 validation; its keyboard prompt cannot currently +receive USB input. + +RK356x normal firmware has a broader implemented service set: HDMI/EDID, +framebuffer, polled OHCI boot-keyboard input, DTB, memory-map services, and +reset-to-MaskROM. It still has no filesystem or runtime SD/eMMC/NVMe driver. +See [RK356x common bare-metal firmware](rk356x/bare-metal.md) for its exact +memory, display, and input contract. + +## Which image should I choose? + +- Use a demo `.img` for a standalone first boot and hardware smoke test. +- Use a firmware-only `.bin` as the base for a custom appended payload. +- Repackage the combined firmware and payload before writing it to SD. +- Use a board-enabled chainloader when the goal is U-Boot, Linux, extlinux, or + an EFI application from NVMe, SD, USB mass storage, or eMMC. +- Use a complete Linux board DTB when booting Linux; do not substitute one of + the minimal firmware DTBs. diff --git a/docs/ref.md b/docs/ref.md index fb048b3..b20c966 100644 --- a/docs/ref.md +++ b/docs/ref.md @@ -8,6 +8,16 @@ - Dynamic Memory Controller: https://www.kernel.org/doc/Documentation/devicetree/bindings/devfreq/rk3399_dmc.txt - ARM GIC architecture specification: https://developer.arm.com/documentation/ihi0069/latest/ +## RK356x + +- [U-Boot RK3568 initialization](https://github.com/u-boot/u-boot/blob/master/arch/arm/mach-rockchip/rk3568/rk3568.c) +- [U-Boot Rockchip SDRAM handling](https://github.com/u-boot/u-boot/blob/master/arch/arm/mach-rockchip/sdram.c) +- [U-Boot Rockchip media packaging](https://docs.u-boot.org/en/latest/board/rockchip/rockchip.html) +- [Linux ROCK 3A device tree](https://github.com/torvalds/linux/blob/master/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts) +- [xrock RK356x MaskROM loader](https://github.com/xboot/xrock) +- [TF-A firmware design](https://trustedfirmware-a.readthedocs.io/en/latest/design/firmware-design.html) +- [OP-TEE Rockchip platform](https://github.com/OP-TEE/optee_os/tree/master/core/arch/arm/plat-rockchip) + ## RK3588 - edk2 uefi monorepo: https://gitlab.com/rk3588_linux/rk/uefi-monorepo (Lots of early bare-metal rk3588 code written by rockchip employees, good reference for when the TRM is missing info) diff --git a/docs/releases.md b/docs/releases.md new file mode 100644 index 0000000..f845c5c --- /dev/null +++ b/docs/releases.md @@ -0,0 +1,186 @@ +# Binary releases + +GitHub Releases provide one archive for each supported board. Releases are +built from stable tags named `vMAJOR.MINOR.PATCH`; the tag is the only project +version source. The exact board assets are `pinebook-pro`, `genbook`, +`roc3566`, `yy3568`, and `rock3a`, plus top-level `SHA256SUMS`. + +## Downloading and verifying + +Download `SHA256SUMS` and the archive for the board, then verify the download +before extracting it: + +```sh +sha256sum -c SHA256SUMS +tar -xJf rk-v1.2.3-roc3566.tar.xz +``` + +The checksum command reports missing archives if only one board archive was +downloaded. That is harmless as long as the selected archive reports `OK`; to +verify just one file, use: + +```sh +grep 'roc3566.tar.xz' SHA256SUMS | sha256sum -c - +``` + +Replace `v1.2.3` with the downloaded release version. Each archive has one +versioned top-level directory and another `SHA256SUMS` covering its extracted +contents. + +## Bundle contents + +Every bundle contains the project README and license, board documentation, +`BUILD-INFO.txt`, and these directories: + +- `firmware/`: a firmware-only developer base and the same firmware with the + demo payload appended. The firmware-only `.bin` is not standalone; append a + compatible FUEFI payload before loading it. +- `images/`: complete RKNS images to write to an SD card. Files beginning with + `demo_`, plus the historically named `genbook_demo.img`, start the included + demo payload and are standalone bring-up tests. The other `.img` contains + firmware only, expects an appended payload, and halts with + `Bad payload magic` when run unchanged. +- `loaders/`: the board's DDR loader. RK356x bundles also carry the shared + USB-plug blob for the optional `xrock maskrom ... --rc4-off` flow. + +The YY3568 and ROCK 3A bundles additionally carry their own optional +BL31/U-Boot chainloader, SD image, raw eMMC ID block, SPI-NOR image, guarded +install/restore scripts, verified BL31 ELF and provenance, manifest, and +patched corresponding-source U-Boot archive. The installer requires separately +installed xrock and rkdeveloptool; no host executable is bundled. Each archive +contains only its own board's manifest, U-Boot source, and artifacts. + +The RK356x archives include Rockchip's binary license and the pinned rkbin +provenance. Write an `.img` to the whole SD device with a trusted imaging tool, +carefully checking the destination because writing an image replaces the +device's existing partition table and data. + +SD is an image-delivery medium in the normal/demo flow: Rockchip BootROM reads +the image before the firmware starts. Its presence does not mean the +bare-metal firmware can mount the SD card. See +[Firmware images, payloads, and device trees](payloads.md) for the execution +model and custom-payload packaging example. + +## Using an RK3568 chainloader bundle + +The YY3568 and ROCK 3A archives keep persistent-media files under `chainload/` +and the guarded utility under `install/`. After checking the internal +`SHA256SUMS`, a RAM-only first test can use +`chainload/uboot_.bin` with an installed MaskROM loader. Persistent +installation uses the packaged utility and separately installed +xrock/rkdeveloptool: + +```sh +./install/flash-chainload.sh flash yy3568 emmc \ + /absolute/path/yy3568-emmc-backup yy3568:emmc + +./install/flash-chainload.sh flash yy3568 spi-nor \ + /absolute/path/yy3568-spi-backup yy3568:spi-nor +``` + +Use `rock3a` in all four board-qualified positions for a ROCK 3A archive. +Cross-board backup restoration is rejected using the board name, selected +medium, manifest hash, detected capacity, and saved range. + +Do not pass the SD image to the eMMC installer: eMMC expects the raw +`uboot__idbloader.img` at LBA `0x40`, while +`uboot_.img` already contains 64 leading sectors for whole-SD imaging. +SPI NOR uses `uboot__spi.img` at offset zero. The utility resolves these +files from the bundle and selects the correct artifact from its manifest. + +Read `chainloading.md` in the archive before writing persistent storage. In +particular, valid SPI firmware has priority over eMMC, SD, and ordinary USB +fallback. + +After U-Boot starts, its board manifest applies the separate OS discovery +order NVMe, removable SD, USB mass storage, then eMMC. This is independent of +which medium supplied the chainloader. + +Linux host programs (`rock.out` and `makeboot.out`) and the partial Orange Pi 5 +target are intentionally not release assets. They can still be built from the +source tree. + +## Creating a release + +### Triggering the workflow + +The sole release trigger is a pushed Git tag whose name is exactly +`vMAJOR.MINOR.PATCH`. Prerelease suffixes, ordinary `master` pushes, and pull +requests do not trigger a release. The workflow intentionally has no manual +`workflow_dispatch` entry point. + +Before tagging, merge the intended commit into `master` and confirm that the +`build and check` workflow succeeds. Create and push one annotated stable tag: + +```sh +git switch master +git pull --ff-only +git status --short +git tag -a v1.2.3 -m 'rk v1.2.3' +git push origin v1.2.3 +``` + +Push only the intended tag rather than using `git push --tags`. The tag push +starts the `release` workflow automatically. Follow it under **Actions -> +release** in the GitHub web interface. + +Do not use **Releases -> Draft a new release** to trigger CI. That interface +creates a GitHub Release object before the workflow starts, so the immutability +guard treats it as an existing release. The web interface is used to monitor or +retry the workflow; Git creates the release tag. + +The release workflow verifies the tag format and confirms its commit is +contained in `origin/master`. It then runs the full test suite, creates +reproducible archives, checks their file allowlists and hashes, uploads an +unpublished draft, verifies the remote assets, and finally publishes it as the +latest release. A failed build never creates a public release, and an existing +published release is never overwritten. If a tag workflow is retried after its +release was published, CI downloads the existing assets, checks their exact +name/size allowlist and SHA-256 manifest against the clean rebuild, and succeeds +only when they match. A different or incomplete published release remains a +hard failure. Drafts use GitHub's temporary `untagged-*` web URL; CI discovers +their numeric release ID through GitHub's authenticated GraphQL pending-tag +lookup before checking assets. This follows the GitHub CLI's draft-resolution +path; the REST endpoint that looks up a release by tag is explicitly limited to +published releases. Do not move or reuse a published tag. + +### Retrying a release + +For a transient runner or network failure, open **Actions -> release**, select +the failed tag run, and choose **Re-run jobs -> Re-run failed jobs**. A retry +uses the workflow and repository contents from the tagged commit: + +- If the failed run left an unpublished draft for that tag, the workflow removes + that draft and recreates it before uploading the verified assets. +- If the release is already public and its six assets match the reproducible + rebuild, the retry succeeds without modifying the release. +- If a public release is empty, incomplete, or different, the retry fails. Do + not upload over it automatically; investigate how it was created and use a + new SemVer tag when it may already have been consumed. +- If fixing the failure required a repository commit, rerunning the old tag + cannot use that fix. Merge the correction into `master` and push the next + SemVer tag instead. + +There is no **Run workflow** button for `release.yml`; that is intentional. +Pushing a new valid tag is the only way to start a new release run. + +`master` pushes, documentation deployment, and tag releases use the repository +variable `RK_RUNNER_LABELS`. If the variable is absent, workflows select the +repository runner with `[self-hosted, Linux, X64]`. To use a GitHub-hosted +runner instead, set the variable to `["ubuntu-latest"]`. Pull requests do not +trigger a workflow. A self-hosted Debian account must provide noninteractive +`sudo apt-get`, outbound GitHub access, and enough disk space for two isolated +U-Boot source builds. Release dependencies include the GitHub `gh` CLI; the +same dependency installation remains compatible with `ubuntu-latest`. + +The same distribution can be inspected locally after building the toolchain: + +```sh +make check +make chainload BOARD=yy3568 +make chainload BOARD=rock3a +make release-dist VERSION=v1.2.3 +``` + +`DIST_DIR` defaults to `dist`. The packager refuses a non-empty destination; +use a new directory instead of overwriting an earlier distribution. diff --git a/docs/rk356x/bare-metal.md b/docs/rk356x/bare-metal.md new file mode 100644 index 0000000..5f4a4ff --- /dev/null +++ b/docs/rk356x/bare-metal.md @@ -0,0 +1,113 @@ +# RK356x common bare-metal firmware + +This page describes behavior shared by the normal and demo firmware variants +for the explicitly supported RK356x boards. Consult the selected [board +page](index.md#documentation-map) for its GPIOs, enabled USB controllers, DDR +loader, artifacts, and validation status. + +## Boot and execution model + +RK356x BootROM is responsible for reading the RKNS image from SD or receiving +it through MaskROM USB. It runs the packaged Rockchip DDR loader, copies this +project's image into RAM, and transfers control at EL3. The firmware does not +need an SD, eMMC, or USB-storage driver for this delivery step. + +Every normal RK356x target ends common initialization by calling +`jump_to_payload()`. It expects a FUEFI payload header immediately after the +linked firmware image, relocates the payload to `0x00a00000` when requested, +and enters it at EL2. A normal `.bin` or `.img` is therefore a +developer base, not a standalone program. Without an appended payload it logs +`Bad payload magic` and halts. + +A `demo_.bin` or `demo_.img` appends the repository's example +payload. Use the demo for standalone UART, display, input, DTB, memory-map, EL2 +handoff, and reset testing. Booting a demo from SD still does not give the +payload runtime access to the card. + +The optional BL31/U-Boot variant is a separately linked program and does not +call `jump_to_payload()`. See [RK356x chainloading](chainloading.md). + +## Common initialization + +The normal firmware initializes the selected board's: + +- UART2 M0 at 1,500,000 baud for early diagnostics. +- DRAM accounting and target-specific MMU map. +- VOP2 VP0, an eSmart XRGB8888 plane, DW-HDMI, DDC, and HDMI PHY. +- Board-enabled USB2 PHY and OHCI companions for polled HID input. +- Minimal firmware DTB and FUEFI service table. +- Reset-to-MaskROM path. + +It intentionally has no runtime SD, eMMC, PCIe, NVMe, filesystem, networking, +USB hub, EHCI, or xHCI implementation. + +## Memory layout + +The normal firmware uses these RK356x low-memory reservations: + +| Range | Use | Mapping | +| --- | --- | --- | +| `0x001fe000–0x00200000` | validated DDR ATAG area | reserved | +| `0x00a00000` | requested FUEFI payload relocation | cacheable | +| `0x07ff0000–0x08000000` | EL3/EL2 split stack | cacheable | +| `0x08000000–0x08400000` | OHCI DMA, DTB, and FUEFI exchange | non-cacheable | +| `0x10000000–0x12000000` | maximum framebuffer arena | non-cacheable | +| `0xf0000000–0xffffffff` | MMIO | device | + +DRAM size comes first from a checksummed `ATAG_DDR_MEM` record, then from +PMUGRF DDR geometry, and finally from a conservative 1 GiB fallback. FUEFI +reports the loaded image, payload, stacks, USB/FUEFI arena, framebuffer, free +RAM, and MMIO as disjoint ranges. `FU_GET_MEM_CHUNK` returns the largest +remaining free range. + +The chainloader has a separate linker and address policy. Do not reuse these +normal-firmware reservations as a board's BL31/BL33 policy. + +## HDMI and framebuffer + +HDMI detection and EDID selection run once during boot. The firmware accepts +progressive RGB 8-bpc timings up to 3840×2160p30 and a 297 MHz pixel/TMDS +clock. It rejects interlaced, YUV-only, 4:2:0-required, and higher-clock modes. + +The preferred or native supported timing wins. Otherwise the firmware selects +the supported timing with the greatest pixel area and then refresh rate. With +HPD and unusable EDID it falls back to 1280×720p60. Without HPD it continues +headless and reports an empty screen list. Runtime hotplug and retraining are +out of scope. + +`FU_GET_SCREEN_LIST` reports the selected address, dimensions, 64-byte-aligned +stride, and XRGB8888 type. The fatal-screen path and demo renderer use the +selected geometry rather than assuming 1920×1080. + +## USB keyboard input + +Each OHCI companion enabled by the board descriptor is initialized and polled. +The implementation supports directly attached low/full-speed HID boot +keyboards, including boot-keyboard interfaces in composite devices. It handles +detach and later reattachment without blocking firmware. + +US-ANSI key-down events produce printable ASCII plus Enter, Backspace, Tab, +Escape, Shift, and Caps Lock behavior. Held-key duplicates are suppressed. +Hubs, non-boot reports, arrows, function keys, Alt, EHCI, and xHCI are outside +the implemented contract. + +`FU_POLL_CHAR` and `FU_GET_CHAR` service OHCI before reading the input ring. +Polling is nonblocking and `FU_GET_CHAR` returns zero when no character is +available. + +## Build and validation + +Build all normal/demo artifacts without fetching U-Boot: + +```sh +make all +make SHELL=/bin/bash check -j"$(nproc)" +``` + +Typical RAM-only and SD demo targets are listed on each board page. Hardware +acceptance requires UART evidence of board/SoC identity, DRAM bytes, EDID +result and selected mode, USB/keyboard status, payload EL, and +reset-to-MaskROM. Host tests do not replace that per-board sign-off. + +For custom payload packaging, see [Firmware images, payloads, and device +trees](../payloads.md). diff --git a/docs/rk356x/boards/roc3566.md b/docs/rk356x/boards/roc3566.md new file mode 100644 index 0000000..c429451 --- /dev/null +++ b/docs/rk356x/boards/roc3566.md @@ -0,0 +1,41 @@ +# Firefly ROC-RK3566-PC + +| Property | Value | +| --- | --- | +| Build identifier | `roc3566` | +| SoC | RK3566 | +| Model | Firefly ROC-RK3566-PC | +| Compatible | `firefly,roc-rk3566-pc`, `rockchip,rk3566` | +| DDR loader | `rk3566_ddr_1056MHz_v1.25.bin` | +| UART | UART2 M0, 1,500,000 baud, 8N1 | +| User LED | GPIO0_D3, active high | +| USB host power | GPIO0_C5, active high | +| Bare-metal USB host | OHCI0 | +| Optional BL31/U-Boot chainloader | not enabled | + +The board descriptor enables the USB-A companion path through OHCI0. The +separate GPIO0_C6 OTG rail is outside the supported host path and is not +driven. Support for this board does not imply support for another RK3566 +design. + +## Artifacts and first test + +- `roc3566.bin`: normal firmware base requiring an appended FUEFI payload. +- `demo_roc3566.bin`: normal firmware plus the example payload. +- `roc3566.img`: RKNS v2 SD delivery image for the firmware-only base. +- `demo_roc3566.img`: standalone RKNS v2 SD demonstration image. + +Start with a RAM-only or demo-SD test: + +```sh +make usb3566 +make demo_roc3566.img +make maskrom3566 # optional xrock USB-plug loader flow +``` + +The current board policy has no manifest-driven BL31/U-Boot variant, SPI-NOR +installer, eMMC installer, or automatic OS-storage discovery. Those +capabilities must not be inferred from the RK3568 boards. + +See [common bare-metal behavior](../bare-metal.md) for payload handoff, memory, +HDMI, input, and reset semantics. diff --git a/docs/rk356x/boards/rock3a.md b/docs/rk356x/boards/rock3a.md new file mode 100644 index 0000000..2d7f685 --- /dev/null +++ b/docs/rk356x/boards/rock3a.md @@ -0,0 +1,65 @@ +# Radxa ROCK 3A + +| Property | Value | +| --- | --- | +| Build identifier | `rock3a` | +| SoC | RK3568 | +| Model | Radxa ROCK 3A | +| Compatible | `radxa,rock3a`, `rockchip,rk3568` | +| DDR loader | `rk3568_ddr_1560MHz_v1.25.bin` | +| UART | UART2 M0, 1,500,000 baud, 8N1 | +| User LED | GPIO0_B7, active high | +| USB host power | GPIO0_A6 host and GPIO0_D5 hub, active high | +| Bare-metal USB host | OHCI0 and OHCI1 | +| Chainloader backend | official mainline U-Boot FIT | +| BL33 load / stack limit | `0x00800000` / `0x03f00000` | +| First-stage media | MaskROM USB, SD, optional eMMC user area, SPI NOR | + +The normal board descriptor enables both USB2 OHCI companions and the onboard +hub power rail. The OTG rail is outside the bare-metal USB-host contract and is +left untouched. + +ROCK 3A identity and wiring are based on the upstream Linux/U-Boot device trees +and Armbian's `rock-3a.conf` at commit +`587b6f2c0a867859ca3f323f6008bee9e3ef1553`. + +## Normal and demo artifacts + +- `rock3a.bin`: normal firmware base requiring an appended FUEFI payload. +- `demo_rock3a.bin`: normal firmware plus the example payload. +- `rock3a.img`: RKNS v2 SD delivery image for the firmware-only base. +- `demo_rock3a.img`: standalone RKNS v2 SD demonstration image. + +```sh +make usb BOARD=rock3a +make demo_rock3a.img +make maskrom3568 # SoC-wide optional xrock USB-plug flow +``` + +## Board-scoped U-Boot variant + +The manifest pins official mainline U-Boot v2026.04 at commit +`88dc2788777babfd6322fa655df549a019aa1e69`, upstream +`rock-3a-rk3568_defconfig` and DTS, the board config fragment, Rockchip BL31 +v1.46, and ROCK 3A-specific address and media policy. It does not modify +`rock3a.bin` or `demo_rock3a.bin`. + +```sh +make chainload BOARD=rock3a +make chainload-check BOARD=rock3a +make usb-chainload BOARD=rock3a +``` + +After its three-second UART interruption window, mainline bootstd searches all +NVMe devices, removable SD (`mmc1`), USB mass storage, and eMMC (`mmc0`) in +that order. Onboard `mmc2` SDIO/Wi-Fi is excluded. SPI commands remain +interactive; SPI NOR can supply the first stage but is not an automatic OS +target. SATA variants are not enabled. + +The eMMC is optional hardware. Installation must abort safely when it is not +populated. Read the common [chainloading and guarded installation +guide](../chainloading.md) before writing eMMC or SPI NOR. The exact +confirmation must use `rock3a`, and cross-board restores are rejected. + +See [common bare-metal behavior](../bare-metal.md) for payload handoff, memory, +HDMI, input, and reset semantics. diff --git a/docs/rk356x/boards/yy3568.md b/docs/rk356x/boards/yy3568.md new file mode 100644 index 0000000..1ddd9c6 --- /dev/null +++ b/docs/rk356x/boards/yy3568.md @@ -0,0 +1,64 @@ +# Youyeetoo YY3568 + +| Property | Value | +| --- | --- | +| Build identifier | `yy3568` | +| SoC | RK3568 | +| Model | Youyeetoo YY3568 | +| Compatible | `youyeetoo,yy3568`, `rockchip,rk3568` | +| DDR loader | `rk3568_ddr_1560MHz_v1.25.bin` | +| UART | UART2 M0, 1,500,000 baud, 8N1 | +| User LEDs | GPIO3_A4 and GPIO2_B2, active high | +| USB host power | GPIO0_D6 `vcc5v0_host`, active high | +| Bare-metal USB host | OHCI0 and OHCI1 | +| Chainloader backend | pinned Radxa vendor FIT | +| BL33 load / stack limit | `0x00a00000` / `0x00c00000` | +| First-stage media | MaskROM USB, SD, eMMC user area, SPI NOR | + +The vendor and `ArmBoardBringUp` trees disagree about the separate OTG VBUS +GPIO (GPIO0_A5 versus GPIO0_B7). Both describe the supported OHCI host +connectors through GPIO0_D6, so the bare-metal descriptor drives only that +common rail and leaves the out-of-scope OTG rail untouched. + +The wiring was cross-checked against the vendor U-Boot and kernel DTS material +under `Rockchip-Library/RK356x/YY3568`. `ArmBoardBringUp` is a provenance and +wiring reference, not a second board identity or build target. + +## Normal and demo artifacts + +- `yy3568.bin`: normal firmware base requiring an appended FUEFI payload. +- `demo_yy3568.bin`: normal firmware plus the example payload. +- `yy3568.img`: RKNS v2 SD delivery image for the firmware-only base. +- `demo_yy3568.img`: standalone RKNS v2 SD demonstration image. + +```sh +make usb BOARD=yy3568 +make demo_yy3568.img +make maskrom3568 # SoC-wide optional xrock USB-plug flow +``` + +## Board-scoped U-Boot variant + +The manifest pins Radxa U-Boot commit +`39cd993e5d6296635438e84f4576b3a9bf76f86e`, the YY3568 overlay, Rockchip +BL31 v1.46, board-specific addresses, media IDs, and automatic boot targets. +It does not modify `yy3568.bin` or `demo_yy3568.bin`. + +```sh +make chainload BOARD=yy3568 +make chainload-check BOARD=yy3568 +make usb-chainload BOARD=yy3568 +``` + +After its three-second UART interruption window, U-Boot searches NVMe devices, +removable SD (`mmc1`), USB mass storage, and eMMC (`mmc0`) in that order. +Onboard `mmc2` SDIO/Wi-Fi is never treated as removable storage. SPI commands +remain interactive; SPI NOR can supply the first stage but is not an automatic +OS target. + +Read the common [chainloading and guarded installation guide](../chainloading.md) +before writing eMMC or SPI NOR. The exact confirmation must use `yy3568`, and a +backup from another board is rejected. + +See [common bare-metal behavior](../bare-metal.md) for payload handoff, memory, +HDMI, input, and reset semantics. diff --git a/docs/rk356x/chainloading.md b/docs/rk356x/chainloading.md new file mode 100644 index 0000000..ca84dce --- /dev/null +++ b/docs/rk356x/chainloading.md @@ -0,0 +1,458 @@ +# RK356x BL31/U-Boot chainloading + +The optional chainloader turns the minimal RK firmware into a practical first +stage without teaching it PCIe, NVMe, filesystems, Linux boot, or EFI. U-Boot +owns those jobs: + +```text +RK3568 BootROM -> Rockchip DDR blob -> board chainloader -> BL31 -> U-Boot EL2 + ^ SPI NOR, eMMC, SD, or MaskROM USB -> NVMe / SD / USB / eMMC + -> Linux or EFI app +``` + +This is a board-scoped variant. Normal `yy3568.bin`, `rock3a.bin`, and demo +images keep their FUEFI payload behavior. ROC3566, RK3399, and RK3588 targets +do not inherit RK3568 storage, address, GPIO, or U-Boot policy. + +## Supported chainloader boards + +| `BOARD` | U-Boot backend | Source pin | BL33 load / stack limit | Automatic OS targets | +| --- | --- | --- | --- | --- | +| [`yy3568`](boards/yy3568.md) | Radxa vendor FIT | `39cd993e5d6296635438e84f4576b3a9bf76f86e` | `0x00a00000` / `0x00c00000` | `nvme0 nvme1` -> `mmc1` -> `usb0` -> `mmc0` | +| [`rock3a`](boards/rock3a.md) | official mainline FIT | v2026.04, `88dc2788777babfd6322fa655df549a019aa1e69` | `0x00800000` / `0x03f00000` | `nvme` -> `mmc1` -> `usb` -> `mmc0` | + +Both use Rockchip BL31 v1.46 from rkbin commit +`ecb4fcbe954edf38b3ae037d5de6d9f5bccf81f4`. ROCK 3A uses upstream +`rock-3a-rk3568_defconfig` and DTS; Armbian commit +`587b6f2c0a867859ca3f323f6008bee9e3ef1553` records the board-selection +reference. YY3568 retains its vendor backend and locally reviewed board +overlay. A backend change for either board is a separate port, not a shared +default. + +## Building one board + +Use the generic interface and always name the board: + +```sh +make chainload BOARD=rock3a +make chainload-check BOARD=rock3a +make usb-chainload BOARD=rock3a +``` + +Replace `rock3a` with `yy3568` for that board. `usb3568-uboot` remains a +deprecated YY3568 compatibility alias; new scripts should use the qualified +command. + +Each build creates: + +- `-u-boot.itb`: U-Boot, its control DTB, and split BL31 segments. +- `uboot_.bin`: dedicated first stage followed by that FIT. +- `uboot_.img`: whole-SD image with its ID block at LBA `0x40`. +- `uboot__idbloader.img`: raw RKNS v2 ID block for eMMC LBA `0x40`. +- `uboot__spi.img`: BootROM SPI-NOR layout for offset zero. +- `-u-boot-source.tar.xz`: patched, buildable corresponding source. + +The default build fetches the manifest-pinned U-Boot commit. To use an existing +Git tree containing that exact commit: + +```sh +make chainload BOARD=rock3a UBOOT_SRC=/path/to/u-boot +``` + +The source is never patched in place. A clean snapshot is exported to +`build/chainload//source`, and only that board's overlay is applied. +Generated headers, objects, tools, source snapshots, links, and stamps stay +under the same board namespace. + +For offline FIT iteration, an existing compatible FIT can build just the +combined binary: + +```sh +make uboot_rock3a.bin UBOOT_ITB=/path/to/u-boot.itb +``` + +Persistent-media generation also needs the pinned source build's `mkimage` or +an explicitly supplied compatible one: + +```sh +make chainload-media BOARD=rock3a MEDIA=emmc MKIMAGE=/path/to/tools/mkimage +make chainload-media BOARD=rock3a MEDIA=spi-nor MKIMAGE=/path/to/tools/mkimage +``` + +U-Boot `rksd` creates the SHA-256 RKNS v2 ID block. `rkspi` spreads those +bytes into the first 2 KiB of each 4 KiB SPI region. These rules do not use or +modify the normal firmware's `makeboot.out` path. + +## BootROM priority and source evidence + +RK3568 BootROM searches immutable media in this order: + +```text +SPI NOR -> SPI NAND -> parallel NAND -> eMMC -> SD -> MaskROM USB +``` + +This project supports SPI NOR and the eMMC user area for the two boards above; +SPI NAND, parallel NAND, and eMMC boot partitions are out of scope. Valid SPI +firmware wins over valid eMMC or SD firmware and prevents normal USB fallback. +Restore or invalidate SPI before testing eMMC fallback. + +The common RK3568 stage reads the BootROM source word at `0xfdcc0010` before +BL31 can reuse that SRAM and logs `source=spi-nor`, `source=emmc`, `source=sd`, +or `source=usb` over UART2 at 1.5 Mbaud. + +Direct MaskROM loading remains the safest development and recovery path: + +```sh +make usb-chainload BOARD=yy3568 +make usb-chainload BOARD=rock3a +``` + +Use the selected board's documented recovery control when valid persistent +firmware prevents ordinary USB discovery. Confirm exactly one RK356x device +before continuing. + +## Guarded eMMC and SPI-NOR installation + +The installer is compatible with xrock commit +`b90d3ba8f0a48320e3888701f7e66e0e4e038bbb` and rkdeveloptool commit +`304f073752fd25c854e1bcf05d8e7f925b1f4e14`. Install host tools separately; +release archives never bundle them. Follow +[Prepare a Linux programming host](../intro.md#prepare-a-linux-programming-host) +to build the pinned tools, configure restricted udev access, and verify the +MaskROM-to-loader transition before attempting a persistent write. + +### SPI NOR to NVMe walkthrough + +SPI NOR contains the RK3568 first-stage firmware, BL31, and U-Boot. It does +not contain Linux or a root filesystem. Before installing it, prepare the NVMe +with extlinux, `boot.scr`, or an AArch64 EFI application as described in +[U-Boot automatic OS discovery](#u-boot-automatic-os-discovery). + +#### 1. Build and validate the SPI image + +For ROCK 3A: + +```sh +make chainload BOARD=rock3a -j"$(nproc)" +make chainload-check BOARD=rock3a +``` + +This produces the SPI image and its two principal inputs: + +```text +uboot_rock3a_spi.img +uboot_rock3a.bin +rock3a-u-boot.itb +``` + +For YY3568: + +```sh +make chainload BOARD=yy3568 -j"$(nproc)" +make chainload-check BOARD=yy3568 +``` + +The corresponding outputs are `uboot_yy3568_spi.img`, +`uboot_yy3568.bin`, and `yy3568-u-boot.itb`. + +#### 2. Put the board into MaskROM mode + +Power off the board and use its documented recovery/MaskROM control while +connecting the appropriate USB OTG port to the host. Confirm detection before +writing anything: + +```sh +rkdeveloptool ld +``` + +There must be exactly one device similar to: + +```text +Vid=0x2207 Pid=0x350a Mode=Maskrom +``` + +If valid firmware already exists in SPI NOR, BootROM selects it before eMMC, +SD, or USB. In that case, force MaskROM with the board's recovery control. The +installer also accepts a single RK356x device that is already in loader mode. + +#### 3. Flash SPI NOR with a complete backup + +The backup path must be absolute, new or empty, and have enough free space for +the complete SPI NOR contents. For ROCK 3A: + +```sh +make flash-chainload \ + BOARD=rock3a \ + MEDIA=spi-nor \ + BACKUP_DIR=/absolute/path/rock3a-spi-backup \ + CONFIRM=rock3a:spi-nor +``` + +For YY3568: + +```sh +make flash-chainload \ + BOARD=yy3568 \ + MEDIA=spi-nor \ + BACKUP_DIR=/absolute/path/yy3568-spi-backup \ + CONFIRM=yy3568:spi-nor +``` + +From a release archive, invoke the packaged utility directly, for example: + +```sh +./install/flash-chainload.sh flash rock3a spi-nor \ + /absolute/path/rock3a-spi-backup rock3a:spi-nor +``` + +The guarded installer: + +1. Verifies that exactly one RK356x device is attached. +2. Loads the manifest-selected DDR and USB-plug helpers into RAM. +3. Selects SPI NOR with `rkdeveloptool cs 9`. +4. Validates the JEDEC identification and detected capacity. +5. Backs up the complete chip as `complete-spi-nor.bin`. +6. Writes only `uboot__spi.img` at offset zero. +7. Reads the written range back and compares it byte-for-byte. +8. Resets the board only after successful verification. + +Do not use `rkdeveloptool ef`; the installer intentionally avoids erasing the +complete chip. A verification mismatch leaves the board in loader mode and +prints the exact restore command. Keep `backup.json`, the binary backups, and +`SHA256SUMS` together. + +#### 4. Confirm NVMe boot + +Connect UART2 at 1,500,000 baud, 8N1. After reset, the first stage should log: + +```text +source=spi-nor +``` + +U-Boot then provides a three-second interruption window and searches: + +```text +NVMe -> removable SD card -> USB mass storage -> eMMC +``` + +If U-Boot starts but does not find the operating system, use `pci enum`, +`nvme scan`, `nvme info`, and `part list nvme 0` at its prompt. That indicates +an NVMe discovery or boot-content problem rather than an SPI installation +problem. + +### eMMC installation + +Use a new, empty, absolute backup directory and an exact board-qualified +confirmation. For ROCK 3A: + +```sh +make flash-chainload BOARD=rock3a MEDIA=emmc \ + BACKUP_DIR=/absolute/path/rock3a-emmc-backup CONFIRM=rock3a:emmc +``` + +The same command works for `BOARD=yy3568` with a matching path and +`CONFIRM=yy3568:emmc`. From a release archive, call +`install/flash-chainload.sh flash` with the same four arguments. + +The utility loads the manifest-selected DDR and USB-plug helpers, requires +exactly one RK356x device, and selects eMMC with `rkdeveloptool cs 1`. + +It backs up LBA 0-63 and the complete destination range, parses MBR and GPT +metadata, rejects a partition crossing the ID block, and writes only the raw +ID block at LBA `0x40`. It never replaces the partition table. Every write is +read back sector-for-sector and compared byte-for-byte before reset. + +### Restoring a backup + +Restore using the board and medium recorded in `backup.json`: + +```sh +make restore-chainload BACKUP=/absolute/path/rock3a-spi-backup \ + CONFIRM=restore:rock3a:spi-nor +``` + +Restoration rejects a backup whose board, medium, manifest SHA-256, storage ID, +write range, or detected capacity differs from the selected target. This stops +a YY3568 backup from being restored to ROCK 3A, or vice versa. + +## FIT and handoff policy + +The common first stage copies the FIT to `0x08000000-0x08400000` before +loading a segment. It requires inline, uncompressed ARM64 images with SHA-256, +rejects external data and BL32/OP-TEE, and permits only: + +- BL31 DRAM segments in `0x00040000-0x00200000`. +- BL31 SRAM segments in `0xfdcc0000-0xfdcf0000`. +- BL33 at the selected board's manifest address, ending before that board's + initial stack. + +The stage reproduces U-Boot FIT metadata and control-DTB placement, constructs +TF-A v1 parameters for a non-secure AArch64 EL2 BL33, cleans loaded ranges, +tears down EL3 caches/MMU, and branches to BL31. Validation or preparation +errors are logged and reset to MaskROM. + +It deliberately omits HDMI, framebuffer, OHCI/HID, FUEFI, storage, and demo +code. SHA-256 detects accidental corruption; this is not authenticated boot. + +## U-Boot automatic OS discovery + +Each board gets a three-second UART interruption window and then scans OS media +in this order: + +```text +NVMe -> removable SD card -> USB mass storage -> eMMC +``` + +The order is common policy, but the U-Boot targets are board-scoped. YY3568's +vendor distro backend uses `nvme0 nvme1 mmc1 usb0 mmc0`. ROCK 3A's mainline +bootstd backend uses `nvme mmc1 usb mmc0`, where the unnumbered class names +scan all devices in that class. On both boards `mmc1` is the removable SD slot +and `mmc0` is eMMC. `mmc2` is onboard SDIO/Wi-Fi and is intentionally not +treated as boot storage. + +Each target searches supported partitions for extlinux, `boot.scr`, or +`EFI/BOOT/BOOTAA64.EFI` and falls through when no valid bootflow succeeds. +USB mass-storage support is enabled for both backends. SPI commands remain +interactive, and automatic network or SATA boot is not added. + +This U-Boot OS order is separate from the immutable BootROM firmware-source +order described above. For example, SPI NOR may supply the chainloader while +U-Boot subsequently loads Linux from SD, USB, or eMMC. ROCK 3A retains upstream +PCIe supplies/resets, JEDEC NOR through SFC, and 8-bit/HS200 eMMC definitions. +YY3568 retains its reviewed vendor definitions. Armbian's ROCK 3A SATA variants +remain excluded. + +The artifacts do not contain Linux or a root filesystem. Prepare any automatic +medium with a kernel `Image`, the Linux DTB for the actual board, and optionally +an initramfs plus `/extlinux/extlinux.conf`, or place an AArch64 EFI application +at `EFI/BOOT/BOOTAA64.EFI` on a FAT EFI System Partition. Never substitute the +minimal bare-metal DTB or U-Boot control DTB for the Linux board DTB. + +Useful prompt checks are: + +```text +pci enum +nvme scan +nvme info +part list nvme 0 +sf probe +mmc list +mmc info +``` + +## Future EDK2 and OP-TEE integration + +EDK2 and OP-TEE have different roles in the trusted-firmware boot model. EDK2 +is normal-world boot firmware and would replace U-Boot as BL33. OP-TEE is an +optional secure-world operating system loaded as BL32 beneath BL31: + +```text +RK3568 BootROM + -> Rockchip DDR blob + -> board-isolated rk chainloader + -> BL31 at EL3 + |-> optional OP-TEE BL32 at Secure EL1 + `-> EDK2 or U-Boot BL33 at Non-secure EL2 + -> EFI application or Linux +``` + +Neither option is implemented by the current chainloader. Its FIT policy +rejects BL32, constructs TF-A parameters with BL32 absent, and accepts only the +manifest-selected U-Boot as BL33. The normal firmware's `jump_to_payload()` is +also not the integration point for either component: it is the FUEFI EL2 +payload path, not a complete TF-A secure-world handoff. + +### EDK2 as an alternative BL33 + +An EDK2 port would be a new, explicitly selected chainloader backend, not a +change to the existing U-Boot variants. It would need its own board manifest, +firmware-volume artifacts, load and stack policy, memory map, and nonvolatile +variable-storage policy. At minimum, a useful port needs UART, PSCI through +BL31, a full board DTB, PCIe/NVMe Block I/O, FAT, EFI boot management, and +reset services. USB, MMC, SPI, and HDMI GOP remain separate board-driver work. + +The community [Quartz64 UEFI project](https://github.com/jaredmcneill/quartz64_uefi) +is a useful RK3566/RK3568 reference and includes ROC-RK3566-PC, but an image or +GPIO policy from one board must never be inherited by YY3568, ROCK 3A, or +another RK356x board. The official +[TianoCore platform collection](https://github.com/tianocore/edk2-platforms/tree/master/Platform) +does not currently provide a generic RK3568 target. + +U-Boot's EFI Loader already supports the repository's practical requirement of +starting `EFI/BOOT/BOOTAA64.EFI`. EDK2 is most useful when a native UEFI +environment, EFI Shell, persistent variables, or a future UEFI Secure Boot +policy is required; U-Boot remains the smaller path for NVMe Linux boot. + +### OP-TEE as optional BL32 + +OP-TEE must run in the secure world and cannot be appended as a normal FUEFI +payload or substituted for BL33. A board port would require all of the +following: + +- A manifest opt-in that pins the OP-TEE source or reviewed binary, hash, + entry point, secure DRAM carveout, and shared-memory range. +- BL31 configured with a compatible OP-TEE dispatcher, plus TF-A parameters + that describe BL32 and the normal-world BL33 independently. +- RK3568 security-controller and memory-firewall programming that protects + secure RAM, with those ranges excluded from the chainloader, EDK2/U-Boot, + DTB, and Linux memory maps. +- A Linux `/firmware/optee` DT node, `CONFIG_TEE` and `CONFIG_OPTEE`; userspace + trusted applications additionally need the OP-TEE client and supplicant. +- Per-board tests for secure/non-secure entry state, SMC behavior, address + overlap, reset, suspend, and failure recovery. + +The official [OP-TEE Rockchip platform](https://github.com/OP-TEE/optee_os/tree/master/core/arch/arm/plat-rockchip) +does not currently include an RK3568 platform flavor. The rkbin collection has +an `rk3568_bl32_v2.16.bin`, but its implementation, source correspondence, +license, ABI, and memory policy must be established before it can be treated as +a supported OP-TEE input. Addresses from RK3399 or RK3588 OP-TEE ports must not +be reused on RK3568. + +### Trust boundary and recommended order + +FIT SHA-256 hashes detect corruption but do not authenticate firmware. Loading +OP-TEE therefore does not by itself establish a trusted device: an attacker +who can replace unauthenticated boot media can replace BL31, BL32, or BL33. +Authenticated BootROM loading, signed manifests or FITs, rollback protection, +and secure storage are distinct future features. + +The preferred development order is: + +1. Retain U-Boot as the default BL33. +2. Add and validate EDK2 as a board-scoped alternative BL33. +3. Add an independently selectable RK3568 OP-TEE port and secure-memory policy. +4. Add authenticated boot only when an actual security boundary is required. + +Any EDK2 or OP-TEE implementation must preserve the existing board/variant +object namespaces and require explicit manifest policy. Enabling it for one +board must not change another board's U-Boot, address map, GPIOs, storage, or +release contents. TF-A defines the relevant +[BL31/BL32/BL33 model](https://trustedfirmware-a.readthedocs.io/en/latest/design/firmware-design.html) +and [secure-partition interfaces](https://trustedfirmware-a.readthedocs.io/en/latest/components/secure-partition-manager.html). + +## Adding another board without collisions + +A new board is unsupported until it provides a schema-validated manifest and +board overlay. The manifest must pin its U-Boot backend/repository/ref/commit, +BL31, artifact names, FIT staging, TF-A parameters, BL31 ranges, BL33 load and +stack boundary, boot targets, physical flash, pinmux, storage IDs, offsets, +capacity policy, and backup behavior. + +Its automatic OS policy must map that board's NVMe devices, removable SD +slots, USB mass-storage class, and eMMC device in the required order. MMC +indices must be verified from that board's U-Boot aliases; an SDIO/Wi-Fi +controller must never be copied into the SD-card group. + +The validator rejects unknown boards/backends, missing or extra policy fields, +path escapes, cross-board overlays/artifacts, mismatched names, and overlapping +memory ranges. Builds must use `build/chainload//`; copying another +board's DTS, GPIOs, addresses, storage IDs, or restore metadata is forbidden. +A port also needs CI matrix coverage, a deliberate release decision, memory-map +review, and hardware sign-off. + +Hardware acceptance for each board covers USB and SD entry, eMMC boot with SPI +blank, SPI priority, eMMC fallback, source logging, storage probes, BL31/U-Boot, +PCIe/NVMe, and automatic OS discovery from NVMe, SD, USB, and eMMC in the +declared fallback order. Test both extlinux Linux and `BOOTAA64.EFI`, plus +invalid-FIT recovery, backup restore, and complete UART transcripts. Optional +ROCK 3A eMMC must abort safely when it is not populated. diff --git a/docs/rk356x/index.md b/docs/rk356x/index.md new file mode 100644 index 0000000..de882c9 --- /dev/null +++ b/docs/rk356x/index.md @@ -0,0 +1,52 @@ +# RK356x devices + +The RK356x implementation supports three explicit board targets: + +- `roc3566`: Firefly ROC-RK3566-PC (`firefly,roc-rk3566-pc`). +- `yy3568`: Youyeetoo YY3568 (`youyeetoo,yy3568`). +- `rock3a`: Radxa ROCK 3A (`radxa,rock3a`). + +The SoC code is shared in `src/rk356x`; board descriptors contain only identity, +LED and USB VBUS wiring, enabled OHCI companions, and connector notes. This does +not claim support for arbitrary RK3566/RK3568 boards. + +YY3568 and ROCK 3A also have optional, isolated BL31/U-Boot variants for +handing PCIe, NVMe, removable SD, USB mass storage, eMMC, Linux, and EFI boot +to U-Boot. Their board manifests map the automatic order as NVMe, SD, USB, then +eMMC without treating onboard SDIO as an SD card. They do not change the +normal/demo images; ROC3566 does not inherit that support. See the +[chainloading guide](chainloading.md). + +That optional variant can be packaged for the eMMC user area or onboard SPI +NOR without adding storage drivers to the bare-metal stage. RK3568 BootROM +loads the DDR blob and chainloader directly from the selected medium. SPI NOR +has immutable priority over eMMC, SD, and USB, so media installation and +restoration are deliberately guarded and remain board-qualified. + +## Capability matrix + +| Board | SoC | Normal/demo | USB-A OHCI | Chainloader | Persistent first-stage media | +| --- | --- | --- | --- | --- | --- | +| [ROC-RK3566-PC](boards/roc3566.md) | RK3566 | yes | OHCI0 | no | SD (normal/demo) | +| [YY3568](boards/yy3568.md) | RK3568 | yes | OHCI0, OHCI1 | vendor-FIT U-Boot | SD, eMMC, SPI NOR | +| [ROCK 3A](boards/rock3a.md) | RK3568 | yes | OHCI0, OHCI1 | mainline-FIT U-Boot | SD, optional eMMC, SPI NOR | + +Shared RK356x drivers do not imply shared GPIO, storage, U-Boot, or memory +policy. Those properties are selected only by the board descriptor or the +validated chainloader manifest. + +## Documentation map + +| Topic | Page | +| --- | --- | +| Linux host dependencies, pinned USB tools, udev permissions, and non-destructive detection | [Prepare a Linux programming host](../intro.md#prepare-a-linux-programming-host) | +| BootROM delivery, normal/demo firmware, `jump_to_payload()`, memory, HDMI, and USB HID | [Common bare-metal firmware](bare-metal.md) | +| BL31/U-Boot FIT handoff, boot media, installation, OS discovery, EDK2, and OP-TEE | [Chainloading](chainloading.md) | +| Firefly-specific target, GPIOs, USB topology, artifacts, and validation | [ROC-RK3566-PC](boards/roc3566.md) | +| Youyeetoo-specific target, wiring provenance, U-Boot backend, and storage policy | [YY3568](boards/yy3568.md) | +| Radxa-specific target, mainline U-Boot backend, optional eMMC, and storage policy | [ROCK 3A](boards/rock3a.md) | + +The generic pages describe only behavior implemented by shared RK356x code. +The board pages are authoritative for identity, connectors, GPIOs, supported +boot media, U-Boot policy, and hardware acceptance. A capability is not +inherited by another board merely because both use RK3566 or RK3568. diff --git a/external/libfdt/README.md b/external/libfdt/README.md new file mode 100644 index 0000000..a3b5421 --- /dev/null +++ b/external/libfdt/README.md @@ -0,0 +1,7 @@ +# libfdt + +These files are the BSD-licensed libfdt sources from the pinned Radxa U-Boot +tree (`39cd993e5d6296635438e84f4576b3a9bf76f86e`), originally maintained by +the Device Tree Compiler project. They are compiled only for chainloader +variants and their host tests. Each source carries the complete dual-license +notice; this project selects the BSD license option. diff --git a/external/libfdt/fdt.c b/external/libfdt/fdt.c new file mode 100644 index 0000000..22286a1 --- /dev/null +++ b/external/libfdt/fdt.c @@ -0,0 +1,251 @@ +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) 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. + * + * 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 OWNER 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. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +int fdt_check_header(const void *fdt) +{ + if (fdt_magic(fdt) == FDT_MAGIC) { + /* Complete tree */ + if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) + return -FDT_ERR_BADVERSION; + if (fdt_last_comp_version(fdt) > FDT_LAST_SUPPORTED_VERSION) + return -FDT_ERR_BADVERSION; + } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { + /* Unfinished sequential-write blob */ + if (fdt_size_dt_struct(fdt) == 0) + return -FDT_ERR_BADSTATE; + } else { + return -FDT_ERR_BADMAGIC; + } + + return 0; +} + +const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len) +{ + unsigned absoffset = offset + fdt_off_dt_struct(fdt); + + if ((absoffset < offset) + || ((absoffset + len) < absoffset) + || (absoffset + len) > fdt_totalsize(fdt)) + return NULL; + + if (fdt_version(fdt) >= 0x11) + if (((offset + len) < offset) + || ((offset + len) > fdt_size_dt_struct(fdt))) + return NULL; + + return _fdt_offset_ptr(fdt, offset); +} + +uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset) +{ + const fdt32_t *tagp, *lenp; + uint32_t tag; + int offset = startoffset; + const char *p; + + *nextoffset = -FDT_ERR_TRUNCATED; + tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE); + if (!tagp) + return FDT_END; /* premature end */ + tag = fdt32_to_cpu(*tagp); + offset += FDT_TAGSIZE; + + *nextoffset = -FDT_ERR_BADSTRUCTURE; + switch (tag) { + case FDT_BEGIN_NODE: + /* skip name */ + do { + p = fdt_offset_ptr(fdt, offset++, 1); + } while (p && (*p != '\0')); + if (!p) + return FDT_END; /* premature end */ + break; + + case FDT_PROP: + lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp)); + if (!lenp) + return FDT_END; /* premature end */ + /* skip-name offset, length and value */ + offset += sizeof(struct fdt_property) - FDT_TAGSIZE + + fdt32_to_cpu(*lenp); + break; + + case FDT_END: + case FDT_END_NODE: + case FDT_NOP: + break; + + default: + return FDT_END; + } + + if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset)) + return FDT_END; /* premature end */ + + *nextoffset = FDT_TAGALIGN(offset); + return tag; +} + +int _fdt_check_node_offset(const void *fdt, int offset) +{ + if ((offset < 0) || (offset % FDT_TAGSIZE) + || (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE)) + return -FDT_ERR_BADOFFSET; + + return offset; +} + +int _fdt_check_prop_offset(const void *fdt, int offset) +{ + if ((offset < 0) || (offset % FDT_TAGSIZE) + || (fdt_next_tag(fdt, offset, &offset) != FDT_PROP)) + return -FDT_ERR_BADOFFSET; + + return offset; +} + +int fdt_next_node(const void *fdt, int offset, int *depth) +{ + int nextoffset = 0; + uint32_t tag; + + if (offset >= 0) + if ((nextoffset = _fdt_check_node_offset(fdt, offset)) < 0) + return nextoffset; + + do { + offset = nextoffset; + tag = fdt_next_tag(fdt, offset, &nextoffset); + + switch (tag) { + case FDT_PROP: + case FDT_NOP: + break; + + case FDT_BEGIN_NODE: + if (depth) + (*depth)++; + break; + + case FDT_END_NODE: + if (depth && ((--(*depth)) < 0)) + return nextoffset; + break; + + case FDT_END: + if ((nextoffset >= 0) + || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth)) + return -FDT_ERR_NOTFOUND; + else + return nextoffset; + } + } while (tag != FDT_BEGIN_NODE); + + return offset; +} + +int fdt_first_subnode(const void *fdt, int offset) +{ + int depth = 0; + + offset = fdt_next_node(fdt, offset, &depth); + if (offset < 0 || depth != 1) + return -FDT_ERR_NOTFOUND; + + return offset; +} + +int fdt_next_subnode(const void *fdt, int offset) +{ + int depth = 1; + + /* + * With respect to the parent, the depth of the next subnode will be + * the same as the last. + */ + do { + offset = fdt_next_node(fdt, offset, &depth); + if (offset < 0 || depth < 1) + return -FDT_ERR_NOTFOUND; + } while (depth > 1); + + return offset; +} + +const char *_fdt_find_string(const char *strtab, int tabsize, const char *s) +{ + int len = strlen(s) + 1; + const char *last = strtab + tabsize - len; + const char *p; + + for (p = strtab; p <= last; p++) + if (memcmp(p, s, len) == 0) + return p; + return NULL; +} + +int fdt_move(const void *fdt, void *buf, int bufsize) +{ + FDT_CHECK_HEADER(fdt); + + if (fdt_totalsize(fdt) > bufsize) + return -FDT_ERR_NOSPACE; + + memmove(buf, fdt, fdt_totalsize(fdt)); + return 0; +} diff --git a/external/libfdt/fdt.h b/external/libfdt/fdt.h new file mode 100644 index 0000000..526aedb --- /dev/null +++ b/external/libfdt/fdt.h @@ -0,0 +1,111 @@ +#ifndef _FDT_H +#define _FDT_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * Copyright 2012 Kim Phillips, Freescale Semiconductor. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) 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. + * + * 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 OWNER 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. + */ + +#ifndef __ASSEMBLY__ + +struct fdt_header { + fdt32_t magic; /* magic word FDT_MAGIC */ + fdt32_t totalsize; /* total size of DT block */ + fdt32_t off_dt_struct; /* offset to structure */ + fdt32_t off_dt_strings; /* offset to strings */ + fdt32_t off_mem_rsvmap; /* offset to memory reserve map */ + fdt32_t version; /* format version */ + fdt32_t last_comp_version; /* last compatible version */ + + /* version 2 fields below */ + fdt32_t boot_cpuid_phys; /* Which physical CPU id we're + booting on */ + /* version 3 fields below */ + fdt32_t size_dt_strings; /* size of the strings block */ + + /* version 17 fields below */ + fdt32_t size_dt_struct; /* size of the structure block */ +}; + +struct fdt_reserve_entry { + fdt64_t address; + fdt64_t size; +}; + +struct fdt_node_header { + fdt32_t tag; + char name[0]; +}; + +struct fdt_property { + fdt32_t tag; + fdt32_t len; + fdt32_t nameoff; + char data[0]; +}; + +#endif /* !__ASSEMBLY */ + +#define FDT_MAGIC 0xd00dfeed /* 4: version, 4: total size */ +#define FDT_TAGSIZE sizeof(fdt32_t) + +#define FDT_BEGIN_NODE 0x1 /* Start node: full name */ +#define FDT_END_NODE 0x2 /* End node */ +#define FDT_PROP 0x3 /* Property: name off, + size, content */ +#define FDT_NOP 0x4 /* nop */ +#define FDT_END 0x9 + +#define FDT_V1_SIZE (7*sizeof(fdt32_t)) +#define FDT_V2_SIZE (FDT_V1_SIZE + sizeof(fdt32_t)) +#define FDT_V3_SIZE (FDT_V2_SIZE + sizeof(fdt32_t)) +#define FDT_V16_SIZE FDT_V3_SIZE +#define FDT_V17_SIZE (FDT_V16_SIZE + sizeof(fdt32_t)) + +#endif /* _FDT_H */ diff --git a/external/libfdt/fdt_empty_tree.c b/external/libfdt/fdt_empty_tree.c new file mode 100644 index 0000000..f2ae9b7 --- /dev/null +++ b/external/libfdt/fdt_empty_tree.c @@ -0,0 +1,83 @@ +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2012 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) 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. + * + * 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 OWNER 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. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +int fdt_create_empty_tree(void *buf, int bufsize) +{ + int err; + + err = fdt_create(buf, bufsize); + if (err) + return err; + + err = fdt_finish_reservemap(buf); + if (err) + return err; + + err = fdt_begin_node(buf, ""); + if (err) + return err; + + err = fdt_end_node(buf); + if (err) + return err; + + err = fdt_finish(buf); + if (err) + return err; + + return fdt_open_into(buf, buf, bufsize); +} diff --git a/external/libfdt/fdt_ro.c b/external/libfdt/fdt_ro.c new file mode 100644 index 0000000..08de2cc --- /dev/null +++ b/external/libfdt/fdt_ro.c @@ -0,0 +1,703 @@ +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) 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. + * + * 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 OWNER 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. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +static int _fdt_nodename_eq(const void *fdt, int offset, + const char *s, int len) +{ + const char *p = fdt_offset_ptr(fdt, offset + FDT_TAGSIZE, len+1); + + if (!p) + /* short match */ + return 0; + + if (memcmp(p, s, len) != 0) + return 0; + + if (p[len] == '\0') + return 1; + else if (!memchr(s, '@', len) && (p[len] == '@')) + return 1; + else + return 0; +} + +const char *fdt_string(const void *fdt, int stroffset) +{ + return (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset; +} + +static int _fdt_string_eq(const void *fdt, int stroffset, + const char *s, int len) +{ + const char *p = fdt_string(fdt, stroffset); + + return (strlen(p) == len) && (memcmp(p, s, len) == 0); +} + +uint32_t fdt_get_max_phandle(const void *fdt) +{ + uint32_t max_phandle = 0; + int offset; + + for (offset = fdt_next_node(fdt, -1, NULL);; + offset = fdt_next_node(fdt, offset, NULL)) { + uint32_t phandle; + + if (offset == -FDT_ERR_NOTFOUND) + return max_phandle; + + if (offset < 0) + return (uint32_t)-1; + + phandle = fdt_get_phandle(fdt, offset); + if (phandle == (uint32_t)-1) + continue; + + if (phandle > max_phandle) + max_phandle = phandle; + } + + return 0; +} + +int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size) +{ + FDT_CHECK_HEADER(fdt); + *address = fdt64_to_cpu(_fdt_mem_rsv(fdt, n)->address); + *size = fdt64_to_cpu(_fdt_mem_rsv(fdt, n)->size); + return 0; +} + +int fdt_num_mem_rsv(const void *fdt) +{ + int i = 0; + + while (fdt64_to_cpu(_fdt_mem_rsv(fdt, i)->size) != 0) + i++; + return i; +} + +static int _nextprop(const void *fdt, int offset) +{ + uint32_t tag; + int nextoffset; + + do { + tag = fdt_next_tag(fdt, offset, &nextoffset); + + switch (tag) { + case FDT_END: + if (nextoffset >= 0) + return -FDT_ERR_BADSTRUCTURE; + else + return nextoffset; + + case FDT_PROP: + return offset; + } + offset = nextoffset; + } while (tag == FDT_NOP); + + return -FDT_ERR_NOTFOUND; +} + +int fdt_subnode_offset_namelen(const void *fdt, int offset, + const char *name, int namelen) +{ + int depth; + + FDT_CHECK_HEADER(fdt); + + for (depth = 0; + (offset >= 0) && (depth >= 0); + offset = fdt_next_node(fdt, offset, &depth)) + if ((depth == 1) + && _fdt_nodename_eq(fdt, offset, name, namelen)) + return offset; + + if (depth < 0) + return -FDT_ERR_NOTFOUND; + return offset; /* error */ +} + +int fdt_subnode_offset(const void *fdt, int parentoffset, + const char *name) +{ + return fdt_subnode_offset_namelen(fdt, parentoffset, name, strlen(name)); +} + +int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen) +{ + const char *end = path + namelen; + const char *p = path; + int offset = 0; + + FDT_CHECK_HEADER(fdt); + + /* see if we have an alias */ + if (*path != '/') { + const char *q = memchr(path, '/', end - p); + + if (!q) + q = end; + + p = fdt_get_alias_namelen(fdt, p, q - p); + if (!p) + return -FDT_ERR_BADPATH; + offset = fdt_path_offset(fdt, p); + + p = q; + } + + while (p < end) { + const char *q; + + while (*p == '/') { + p++; + if (p == end) + return offset; + } + q = memchr(p, '/', end - p); + if (! q) + q = end; + + offset = fdt_subnode_offset_namelen(fdt, offset, p, q-p); + if (offset < 0) + return offset; + + p = q; + } + + return offset; +} + +int fdt_path_offset(const void *fdt, const char *path) +{ + return fdt_path_offset_namelen(fdt, path, strlen(path)); +} + +const char *fdt_get_name(const void *fdt, int nodeoffset, int *len) +{ + const struct fdt_node_header *nh = _fdt_offset_ptr(fdt, nodeoffset); + int err; + + if (((err = fdt_check_header(fdt)) != 0) + || ((err = _fdt_check_node_offset(fdt, nodeoffset)) < 0)) + goto fail; + + if (len) + *len = strlen(nh->name); + + return nh->name; + + fail: + if (len) + *len = err; + return NULL; +} + +int fdt_first_property_offset(const void *fdt, int nodeoffset) +{ + int offset; + + if ((offset = _fdt_check_node_offset(fdt, nodeoffset)) < 0) + return offset; + + return _nextprop(fdt, offset); +} + +int fdt_next_property_offset(const void *fdt, int offset) +{ + if ((offset = _fdt_check_prop_offset(fdt, offset)) < 0) + return offset; + + return _nextprop(fdt, offset); +} + +const struct fdt_property *fdt_get_property_by_offset(const void *fdt, + int offset, + int *lenp) +{ + int err; + const struct fdt_property *prop; + + if ((err = _fdt_check_prop_offset(fdt, offset)) < 0) { + if (lenp) + *lenp = err; + return NULL; + } + + prop = _fdt_offset_ptr(fdt, offset); + + if (lenp) + *lenp = fdt32_to_cpu(prop->len); + + return prop; +} + +const struct fdt_property *fdt_get_property_namelen(const void *fdt, + int offset, + const char *name, + int namelen, int *lenp) +{ + for (offset = fdt_first_property_offset(fdt, offset); + (offset >= 0); + (offset = fdt_next_property_offset(fdt, offset))) { + const struct fdt_property *prop; + + if (!(prop = fdt_get_property_by_offset(fdt, offset, lenp))) { + offset = -FDT_ERR_INTERNAL; + break; + } + if (_fdt_string_eq(fdt, fdt32_to_cpu(prop->nameoff), + name, namelen)) + return prop; + } + + if (lenp) + *lenp = offset; + return NULL; +} + +const struct fdt_property *fdt_get_property(const void *fdt, + int nodeoffset, + const char *name, int *lenp) +{ + return fdt_get_property_namelen(fdt, nodeoffset, name, + strlen(name), lenp); +} + +const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, + const char *name, int namelen, int *lenp) +{ + const struct fdt_property *prop; + + prop = fdt_get_property_namelen(fdt, nodeoffset, name, namelen, lenp); + if (!prop) + return NULL; + + return prop->data; +} + +const void *fdt_getprop_by_offset(const void *fdt, int offset, + const char **namep, int *lenp) +{ + const struct fdt_property *prop; + + prop = fdt_get_property_by_offset(fdt, offset, lenp); + if (!prop) + return NULL; + if (namep) + *namep = fdt_string(fdt, fdt32_to_cpu(prop->nameoff)); + return prop->data; +} + +const void *fdt_getprop(const void *fdt, int nodeoffset, + const char *name, int *lenp) +{ + return fdt_getprop_namelen(fdt, nodeoffset, name, strlen(name), lenp); +} + +uint32_t fdt_get_phandle(const void *fdt, int nodeoffset) +{ + const fdt32_t *php; + int len; + + /* FIXME: This is a bit sub-optimal, since we potentially scan + * over all the properties twice. */ + php = fdt_getprop(fdt, nodeoffset, "phandle", &len); + if (!php || (len != sizeof(*php))) { + php = fdt_getprop(fdt, nodeoffset, "linux,phandle", &len); + if (!php || (len != sizeof(*php))) + return 0; + } + + return fdt32_to_cpu(*php); +} + +const char *fdt_get_alias_namelen(const void *fdt, + const char *name, int namelen) +{ + int aliasoffset; + + aliasoffset = fdt_path_offset(fdt, "/aliases"); + if (aliasoffset < 0) + return NULL; + + return fdt_getprop_namelen(fdt, aliasoffset, name, namelen, NULL); +} + +const char *fdt_get_alias(const void *fdt, const char *name) +{ + return fdt_get_alias_namelen(fdt, name, strlen(name)); +} + +int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen) +{ + int pdepth = 0, p = 0; + int offset, depth, namelen; + const char *name; + + FDT_CHECK_HEADER(fdt); + + if (buflen < 2) + return -FDT_ERR_NOSPACE; + + for (offset = 0, depth = 0; + (offset >= 0) && (offset <= nodeoffset); + offset = fdt_next_node(fdt, offset, &depth)) { + while (pdepth > depth) { + do { + p--; + } while (buf[p-1] != '/'); + pdepth--; + } + + if (pdepth >= depth) { + name = fdt_get_name(fdt, offset, &namelen); + if (!name) + return namelen; + if ((p + namelen + 1) <= buflen) { + memcpy(buf + p, name, namelen); + p += namelen; + buf[p++] = '/'; + pdepth++; + } + } + + if (offset == nodeoffset) { + if (pdepth < (depth + 1)) + return -FDT_ERR_NOSPACE; + + if (p > 1) /* special case so that root path is "/", not "" */ + p--; + buf[p] = '\0'; + return 0; + } + } + + if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) + return -FDT_ERR_BADOFFSET; + else if (offset == -FDT_ERR_BADOFFSET) + return -FDT_ERR_BADSTRUCTURE; + + return offset; /* error from fdt_next_node() */ +} + +int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, + int supernodedepth, int *nodedepth) +{ + int offset, depth; + int supernodeoffset = -FDT_ERR_INTERNAL; + + FDT_CHECK_HEADER(fdt); + + if (supernodedepth < 0) + return -FDT_ERR_NOTFOUND; + + for (offset = 0, depth = 0; + (offset >= 0) && (offset <= nodeoffset); + offset = fdt_next_node(fdt, offset, &depth)) { + if (depth == supernodedepth) + supernodeoffset = offset; + + if (offset == nodeoffset) { + if (nodedepth) + *nodedepth = depth; + + if (supernodedepth > depth) + return -FDT_ERR_NOTFOUND; + else + return supernodeoffset; + } + } + + if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) + return -FDT_ERR_BADOFFSET; + else if (offset == -FDT_ERR_BADOFFSET) + return -FDT_ERR_BADSTRUCTURE; + + return offset; /* error from fdt_next_node() */ +} + +int fdt_node_depth(const void *fdt, int nodeoffset) +{ + int nodedepth; + int err; + + err = fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, &nodedepth); + if (err) + return (err < 0) ? err : -FDT_ERR_INTERNAL; + return nodedepth; +} + +int fdt_parent_offset(const void *fdt, int nodeoffset) +{ + int nodedepth = fdt_node_depth(fdt, nodeoffset); + + if (nodedepth < 0) + return nodedepth; + return fdt_supernode_atdepth_offset(fdt, nodeoffset, + nodedepth - 1, NULL); +} + +int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, + const char *propname, + const void *propval, int proplen) +{ + int offset; + const void *val; + int len; + + FDT_CHECK_HEADER(fdt); + + /* FIXME: The algorithm here is pretty horrible: we scan each + * property of a node in fdt_getprop(), then if that didn't + * find what we want, we scan over them again making our way + * to the next node. Still it's the easiest to implement + * approach; performance can come later. */ + for (offset = fdt_next_node(fdt, startoffset, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + val = fdt_getprop(fdt, offset, propname, &len); + if (val && (len == proplen) + && (memcmp(val, propval, len) == 0)) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} + +int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle) +{ + int offset; + + if ((phandle == 0) || (phandle == -1)) + return -FDT_ERR_BADPHANDLE; + + FDT_CHECK_HEADER(fdt); + + /* FIXME: The algorithm here is pretty horrible: we + * potentially scan each property of a node in + * fdt_get_phandle(), then if that didn't find what + * we want, we scan over them again making our way to the next + * node. Still it's the easiest to implement approach; + * performance can come later. */ + for (offset = fdt_next_node(fdt, -1, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + if (fdt_get_phandle(fdt, offset) == phandle) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} + +int fdt_stringlist_contains(const char *strlist, int listlen, const char *str) +{ + int len = strlen(str); + const char *p; + + while (listlen >= len) { + if (memcmp(str, strlist, len+1) == 0) + return 1; + p = memchr(strlist, '\0', listlen); + if (!p) + return 0; /* malformed strlist.. */ + listlen -= (p-strlist) + 1; + strlist = p + 1; + } + return 0; +} + +int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property) +{ + const char *list, *end; + int length, count = 0; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) + return length; + + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) + return -FDT_ERR_BADVALUE; + + list += length; + count++; + } + + return count; +} + +int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, + const char *string) +{ + int length, len, idx = 0; + const char *list, *end; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) + return length; + + len = strlen(string) + 1; + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) + return -FDT_ERR_BADVALUE; + + if (length == len && memcmp(list, string, length) == 0) + return idx; + + list += length; + idx++; + } + + return -FDT_ERR_NOTFOUND; +} + +const char *fdt_stringlist_get(const void *fdt, int nodeoffset, + const char *property, int idx, + int *lenp) +{ + const char *list, *end; + int length; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) { + if (lenp) + *lenp = length; + + return NULL; + } + + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) { + if (lenp) + *lenp = -FDT_ERR_BADVALUE; + + return NULL; + } + + if (idx == 0) { + if (lenp) + *lenp = length - 1; + + return list; + } + + list += length; + idx--; + } + + if (lenp) + *lenp = -FDT_ERR_NOTFOUND; + + return NULL; +} + +int fdt_node_check_compatible(const void *fdt, int nodeoffset, + const char *compatible) +{ + const void *prop; + int len; + + prop = fdt_getprop(fdt, nodeoffset, "compatible", &len); + if (!prop) + return len; + + return !fdt_stringlist_contains(prop, len, compatible); +} + +int fdt_node_offset_by_compatible(const void *fdt, int startoffset, + const char *compatible) +{ + int offset, err; + + FDT_CHECK_HEADER(fdt); + + /* FIXME: The algorithm here is pretty horrible: we scan each + * property of a node in fdt_node_check_compatible(), then if + * that didn't find what we want, we scan over them again + * making our way to the next node. Still it's the easiest to + * implement approach; performance can come later. */ + for (offset = fdt_next_node(fdt, startoffset, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + err = fdt_node_check_compatible(fdt, offset, compatible); + if ((err < 0) && (err != -FDT_ERR_NOTFOUND)) + return err; + else if (err == 0) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} diff --git a/external/libfdt/fdt_rw.c b/external/libfdt/fdt_rw.c new file mode 100644 index 0000000..5c3a2bb --- /dev/null +++ b/external/libfdt/fdt_rw.c @@ -0,0 +1,505 @@ +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) 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. + * + * 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 OWNER 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. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +static int _fdt_blocks_misordered(const void *fdt, + int mem_rsv_size, int struct_size) +{ + return (fdt_off_mem_rsvmap(fdt) < FDT_ALIGN(sizeof(struct fdt_header), 8)) + || (fdt_off_dt_struct(fdt) < + (fdt_off_mem_rsvmap(fdt) + mem_rsv_size)) + || (fdt_off_dt_strings(fdt) < + (fdt_off_dt_struct(fdt) + struct_size)) + || (fdt_totalsize(fdt) < + (fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt))); +} + +static int _fdt_rw_check_header(void *fdt) +{ + FDT_CHECK_HEADER(fdt); + + if (fdt_version(fdt) < 17) + return -FDT_ERR_BADVERSION; + if (_fdt_blocks_misordered(fdt, sizeof(struct fdt_reserve_entry), + fdt_size_dt_struct(fdt))) + return -FDT_ERR_BADLAYOUT; + if (fdt_version(fdt) > 17) + fdt_set_version(fdt, 17); + + return 0; +} + +#define FDT_RW_CHECK_HEADER(fdt) \ + { \ + int __err; \ + if ((__err = _fdt_rw_check_header(fdt)) != 0) \ + return __err; \ + } + +static inline int _fdt_data_size(void *fdt) +{ + return fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt); +} + +static int _fdt_splice(void *fdt, void *splicepoint, int oldlen, int newlen) +{ + char *p = splicepoint; + char *end = (char *)fdt + _fdt_data_size(fdt); + + if (((p + oldlen) < p) || ((p + oldlen) > end)) + return -FDT_ERR_BADOFFSET; + if ((p < (char *)fdt) || ((end - oldlen + newlen) < (char *)fdt)) + return -FDT_ERR_BADOFFSET; + if ((end - oldlen + newlen) > ((char *)fdt + fdt_totalsize(fdt))) + return -FDT_ERR_NOSPACE; + memmove(p + newlen, p + oldlen, end - p - oldlen); + return 0; +} + +static int _fdt_splice_mem_rsv(void *fdt, struct fdt_reserve_entry *p, + int oldn, int newn) +{ + int delta = (newn - oldn) * sizeof(*p); + int err; + err = _fdt_splice(fdt, p, oldn * sizeof(*p), newn * sizeof(*p)); + if (err) + return err; + fdt_set_off_dt_struct(fdt, fdt_off_dt_struct(fdt) + delta); + fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta); + return 0; +} + +static int _fdt_splice_struct(void *fdt, void *p, + int oldlen, int newlen) +{ + int delta = newlen - oldlen; + int err; + + if ((err = _fdt_splice(fdt, p, oldlen, newlen))) + return err; + + fdt_set_size_dt_struct(fdt, fdt_size_dt_struct(fdt) + delta); + fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta); + return 0; +} + +static int _fdt_splice_string(void *fdt, int newlen) +{ + void *p = (char *)fdt + + fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt); + int err; + + if ((err = _fdt_splice(fdt, p, 0, newlen))) + return err; + + fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) + newlen); + return 0; +} + +static int _fdt_find_add_string(void *fdt, const char *s) +{ + char *strtab = (char *)fdt + fdt_off_dt_strings(fdt); + const char *p; + char *new; + int len = strlen(s) + 1; + int err; + + p = _fdt_find_string(strtab, fdt_size_dt_strings(fdt), s); + if (p) + /* found it */ + return (p - strtab); + + new = strtab + fdt_size_dt_strings(fdt); + err = _fdt_splice_string(fdt, len); + if (err) + return err; + + memcpy(new, s, len); + return (new - strtab); +} + +int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size) +{ + struct fdt_reserve_entry *re; + int err; + + FDT_RW_CHECK_HEADER(fdt); + + re = _fdt_mem_rsv_w(fdt, fdt_num_mem_rsv(fdt)); + err = _fdt_splice_mem_rsv(fdt, re, 0, 1); + if (err) + return err; + + re->address = cpu_to_fdt64(address); + re->size = cpu_to_fdt64(size); + return 0; +} + +int fdt_del_mem_rsv(void *fdt, int n) +{ + struct fdt_reserve_entry *re = _fdt_mem_rsv_w(fdt, n); + + FDT_RW_CHECK_HEADER(fdt); + + if (n >= fdt_num_mem_rsv(fdt)) + return -FDT_ERR_NOTFOUND; + + return _fdt_splice_mem_rsv(fdt, re, 1, 0); +} + +static int _fdt_resize_property(void *fdt, int nodeoffset, const char *name, + int len, struct fdt_property **prop) +{ + int oldlen; + int err; + + *prop = fdt_get_property_w(fdt, nodeoffset, name, &oldlen); + if (!*prop) + return oldlen; + + if ((err = _fdt_splice_struct(fdt, (*prop)->data, FDT_TAGALIGN(oldlen), + FDT_TAGALIGN(len)))) + return err; + + (*prop)->len = cpu_to_fdt32(len); + return 0; +} + +static int _fdt_add_property(void *fdt, int nodeoffset, const char *name, + int len, struct fdt_property **prop) +{ + int proplen; + int nextoffset; + int namestroff; + int err; + + if ((nextoffset = _fdt_check_node_offset(fdt, nodeoffset)) < 0) + return nextoffset; + + namestroff = _fdt_find_add_string(fdt, name); + if (namestroff < 0) + return namestroff; + + *prop = _fdt_offset_ptr_w(fdt, nextoffset); + proplen = sizeof(**prop) + FDT_TAGALIGN(len); + + err = _fdt_splice_struct(fdt, *prop, 0, proplen); + if (err) + return err; + + (*prop)->tag = cpu_to_fdt32(FDT_PROP); + (*prop)->nameoff = cpu_to_fdt32(namestroff); + (*prop)->len = cpu_to_fdt32(len); + return 0; +} + +int fdt_set_name(void *fdt, int nodeoffset, const char *name) +{ + char *namep; + int oldlen, newlen; + int err; + + FDT_RW_CHECK_HEADER(fdt); + + namep = (char *)(uintptr_t)fdt_get_name(fdt, nodeoffset, &oldlen); + if (!namep) + return oldlen; + + newlen = strlen(name); + + err = _fdt_splice_struct(fdt, namep, FDT_TAGALIGN(oldlen+1), + FDT_TAGALIGN(newlen+1)); + if (err) + return err; + + memcpy(namep, name, newlen+1); + return 0; +} + +int fdt_setprop_placeholder(void *fdt, int nodeoffset, const char *name, + int len, void **prop_data) +{ + struct fdt_property *prop; + int err; + + FDT_RW_CHECK_HEADER(fdt); + + err = _fdt_resize_property(fdt, nodeoffset, name, len, &prop); + if (err == -FDT_ERR_NOTFOUND) + err = _fdt_add_property(fdt, nodeoffset, name, len, &prop); + if (err) + return err; + + *prop_data = prop->data; + return 0; +} + +int fdt_setprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + void *prop_data; + int err; + + err = fdt_setprop_placeholder(fdt, nodeoffset, name, len, &prop_data); + if (err) + return err; + + if (len) + memcpy(prop_data, val, len); + return 0; +} + +int fdt_appendprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + struct fdt_property *prop; + int err, oldlen, newlen; + + FDT_RW_CHECK_HEADER(fdt); + + prop = fdt_get_property_w(fdt, nodeoffset, name, &oldlen); + if (prop) { + newlen = len + oldlen; + err = _fdt_splice_struct(fdt, prop->data, + FDT_TAGALIGN(oldlen), + FDT_TAGALIGN(newlen)); + if (err) + return err; + prop->len = cpu_to_fdt32(newlen); + memcpy(prop->data + oldlen, val, len); + } else { + err = _fdt_add_property(fdt, nodeoffset, name, len, &prop); + if (err) + return err; + memcpy(prop->data, val, len); + } + return 0; +} + +int fdt_delprop(void *fdt, int nodeoffset, const char *name) +{ + struct fdt_property *prop; + int len, proplen; + + FDT_RW_CHECK_HEADER(fdt); + + prop = fdt_get_property_w(fdt, nodeoffset, name, &len); + if (!prop) + return len; + + proplen = sizeof(*prop) + FDT_TAGALIGN(len); + return _fdt_splice_struct(fdt, prop, proplen, 0); +} + +int fdt_add_subnode_namelen(void *fdt, int parentoffset, + const char *name, int namelen) +{ + struct fdt_node_header *nh; + int offset, nextoffset; + int nodelen; + int err; + uint32_t tag; + fdt32_t *endtag; + + FDT_RW_CHECK_HEADER(fdt); + + offset = fdt_subnode_offset_namelen(fdt, parentoffset, name, namelen); + if (offset >= 0) + return -FDT_ERR_EXISTS; + else if (offset != -FDT_ERR_NOTFOUND) + return offset; + + /* Try to place the new node after the parent's properties */ + fdt_next_tag(fdt, parentoffset, &nextoffset); /* skip the BEGIN_NODE */ + do { + offset = nextoffset; + tag = fdt_next_tag(fdt, offset, &nextoffset); + } while ((tag == FDT_PROP) || (tag == FDT_NOP)); + + nh = _fdt_offset_ptr_w(fdt, offset); + nodelen = sizeof(*nh) + FDT_TAGALIGN(namelen+1) + FDT_TAGSIZE; + + err = _fdt_splice_struct(fdt, nh, 0, nodelen); + if (err) + return err; + + nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); + memset(nh->name, 0, FDT_TAGALIGN(namelen+1)); + memcpy(nh->name, name, namelen); + endtag = (fdt32_t *)((char *)nh + nodelen - FDT_TAGSIZE); + *endtag = cpu_to_fdt32(FDT_END_NODE); + + return offset; +} + +int fdt_add_subnode(void *fdt, int parentoffset, const char *name) +{ + return fdt_add_subnode_namelen(fdt, parentoffset, name, strlen(name)); +} + +int fdt_del_node(void *fdt, int nodeoffset) +{ + int endoffset; + + FDT_RW_CHECK_HEADER(fdt); + + endoffset = _fdt_node_end_offset(fdt, nodeoffset); + if (endoffset < 0) + return endoffset; + + return _fdt_splice_struct(fdt, _fdt_offset_ptr_w(fdt, nodeoffset), + endoffset - nodeoffset, 0); +} + +static void _fdt_packblocks(const char *old, char *new, + int mem_rsv_size, int struct_size) +{ + int mem_rsv_off, struct_off, strings_off; + + mem_rsv_off = FDT_ALIGN(sizeof(struct fdt_header), 8); + struct_off = mem_rsv_off + mem_rsv_size; + strings_off = struct_off + struct_size; + + memmove(new + mem_rsv_off, old + fdt_off_mem_rsvmap(old), mem_rsv_size); + fdt_set_off_mem_rsvmap(new, mem_rsv_off); + + memmove(new + struct_off, old + fdt_off_dt_struct(old), struct_size); + fdt_set_off_dt_struct(new, struct_off); + fdt_set_size_dt_struct(new, struct_size); + + memmove(new + strings_off, old + fdt_off_dt_strings(old), + fdt_size_dt_strings(old)); + fdt_set_off_dt_strings(new, strings_off); + fdt_set_size_dt_strings(new, fdt_size_dt_strings(old)); +} + +int fdt_open_into(const void *fdt, void *buf, int bufsize) +{ + int err; + int mem_rsv_size, struct_size; + int newsize; + const char *fdtstart = fdt; + const char *fdtend = fdtstart + fdt_totalsize(fdt); + char *tmp; + + FDT_CHECK_HEADER(fdt); + + mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) + * sizeof(struct fdt_reserve_entry); + + if (fdt_version(fdt) >= 17) { + struct_size = fdt_size_dt_struct(fdt); + } else { + struct_size = 0; + while (fdt_next_tag(fdt, struct_size, &struct_size) != FDT_END) + ; + if (struct_size < 0) + return struct_size; + } + + if (!_fdt_blocks_misordered(fdt, mem_rsv_size, struct_size)) { + /* no further work necessary */ + err = fdt_move(fdt, buf, bufsize); + if (err) + return err; + fdt_set_version(buf, 17); + fdt_set_size_dt_struct(buf, struct_size); + fdt_set_totalsize(buf, bufsize); + return 0; + } + + /* Need to reorder */ + newsize = FDT_ALIGN(sizeof(struct fdt_header), 8) + mem_rsv_size + + struct_size + fdt_size_dt_strings(fdt); + + if (bufsize < newsize) + return -FDT_ERR_NOSPACE; + + /* First attempt to build converted tree at beginning of buffer */ + tmp = buf; + /* But if that overlaps with the old tree... */ + if (((tmp + newsize) > fdtstart) && (tmp < fdtend)) { + /* Try right after the old tree instead */ + tmp = (char *)(uintptr_t)fdtend; + if ((tmp + newsize) > ((char *)buf + bufsize)) + return -FDT_ERR_NOSPACE; + } + + _fdt_packblocks(fdt, tmp, mem_rsv_size, struct_size); + memmove(buf, tmp, newsize); + + fdt_set_magic(buf, FDT_MAGIC); + fdt_set_totalsize(buf, bufsize); + fdt_set_version(buf, 17); + fdt_set_last_comp_version(buf, 16); + fdt_set_boot_cpuid_phys(buf, fdt_boot_cpuid_phys(fdt)); + + return 0; +} + +int fdt_pack(void *fdt) +{ + int mem_rsv_size; + + FDT_RW_CHECK_HEADER(fdt); + + mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) + * sizeof(struct fdt_reserve_entry); + _fdt_packblocks(fdt, fdt, mem_rsv_size, fdt_size_dt_struct(fdt)); + fdt_set_totalsize(fdt, _fdt_data_size(fdt)); + + return 0; +} diff --git a/external/libfdt/fdt_strerror.c b/external/libfdt/fdt_strerror.c new file mode 100644 index 0000000..9677a18 --- /dev/null +++ b/external/libfdt/fdt_strerror.c @@ -0,0 +1,102 @@ +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) 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. + * + * 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 OWNER 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. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +struct fdt_errtabent { + const char *str; +}; + +#define FDT_ERRTABENT(val) \ + [(val)] = { .str = #val, } + +static struct fdt_errtabent fdt_errtable[] = { + FDT_ERRTABENT(FDT_ERR_NOTFOUND), + FDT_ERRTABENT(FDT_ERR_EXISTS), + FDT_ERRTABENT(FDT_ERR_NOSPACE), + + FDT_ERRTABENT(FDT_ERR_BADOFFSET), + FDT_ERRTABENT(FDT_ERR_BADPATH), + FDT_ERRTABENT(FDT_ERR_BADPHANDLE), + FDT_ERRTABENT(FDT_ERR_BADSTATE), + + FDT_ERRTABENT(FDT_ERR_TRUNCATED), + FDT_ERRTABENT(FDT_ERR_BADMAGIC), + FDT_ERRTABENT(FDT_ERR_BADVERSION), + FDT_ERRTABENT(FDT_ERR_BADSTRUCTURE), + FDT_ERRTABENT(FDT_ERR_BADLAYOUT), + FDT_ERRTABENT(FDT_ERR_INTERNAL), + FDT_ERRTABENT(FDT_ERR_BADNCELLS), + FDT_ERRTABENT(FDT_ERR_BADVALUE), + FDT_ERRTABENT(FDT_ERR_BADOVERLAY), + FDT_ERRTABENT(FDT_ERR_NOPHANDLES), +}; +#define FDT_ERRTABSIZE (sizeof(fdt_errtable) / sizeof(fdt_errtable[0])) + +const char *fdt_strerror(int errval) +{ + if (errval > 0) + return ""; + else if (errval == 0) + return ""; + else if (errval > -FDT_ERRTABSIZE) { + const char *s = fdt_errtable[-errval].str; + + if (s) + return s; + } + + return ""; +} diff --git a/external/libfdt/fdt_sw.c b/external/libfdt/fdt_sw.c new file mode 100644 index 0000000..2bd15e7 --- /dev/null +++ b/external/libfdt/fdt_sw.c @@ -0,0 +1,300 @@ +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) 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. + * + * 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 OWNER 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. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +static int _fdt_sw_check_header(void *fdt) +{ + if (fdt_magic(fdt) != FDT_SW_MAGIC) + return -FDT_ERR_BADMAGIC; + /* FIXME: should check more details about the header state */ + return 0; +} + +#define FDT_SW_CHECK_HEADER(fdt) \ + { \ + int err; \ + if ((err = _fdt_sw_check_header(fdt)) != 0) \ + return err; \ + } + +static void *_fdt_grab_space(void *fdt, size_t len) +{ + int offset = fdt_size_dt_struct(fdt); + int spaceleft; + + spaceleft = fdt_totalsize(fdt) - fdt_off_dt_struct(fdt) + - fdt_size_dt_strings(fdt); + + if ((offset + len < offset) || (offset + len > spaceleft)) + return NULL; + + fdt_set_size_dt_struct(fdt, offset + len); + return _fdt_offset_ptr_w(fdt, offset); +} + +int fdt_create(void *buf, int bufsize) +{ + void *fdt = buf; + + if (bufsize < sizeof(struct fdt_header)) + return -FDT_ERR_NOSPACE; + + memset(buf, 0, bufsize); + + fdt_set_magic(fdt, FDT_SW_MAGIC); + fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); + fdt_set_last_comp_version(fdt, FDT_FIRST_SUPPORTED_VERSION); + fdt_set_totalsize(fdt, bufsize); + + fdt_set_off_mem_rsvmap(fdt, FDT_ALIGN(sizeof(struct fdt_header), + sizeof(struct fdt_reserve_entry))); + fdt_set_off_dt_struct(fdt, fdt_off_mem_rsvmap(fdt)); + fdt_set_off_dt_strings(fdt, bufsize); + + return 0; +} + +int fdt_resize(void *fdt, void *buf, int bufsize) +{ + size_t headsize, tailsize; + char *oldtail, *newtail; + + FDT_SW_CHECK_HEADER(fdt); + + headsize = fdt_off_dt_struct(fdt); + tailsize = fdt_size_dt_strings(fdt); + + if ((headsize + tailsize) > bufsize) + return -FDT_ERR_NOSPACE; + + oldtail = (char *)fdt + fdt_totalsize(fdt) - tailsize; + newtail = (char *)buf + bufsize - tailsize; + + /* Two cases to avoid clobbering data if the old and new + * buffers partially overlap */ + if (buf <= fdt) { + memmove(buf, fdt, headsize); + memmove(newtail, oldtail, tailsize); + } else { + memmove(newtail, oldtail, tailsize); + memmove(buf, fdt, headsize); + } + + fdt_set_off_dt_strings(buf, bufsize); + fdt_set_totalsize(buf, bufsize); + + return 0; +} + +int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size) +{ + struct fdt_reserve_entry *re; + int offset; + + FDT_SW_CHECK_HEADER(fdt); + + if (fdt_size_dt_struct(fdt)) + return -FDT_ERR_BADSTATE; + + offset = fdt_off_dt_struct(fdt); + if ((offset + sizeof(*re)) > fdt_totalsize(fdt)) + return -FDT_ERR_NOSPACE; + + re = (struct fdt_reserve_entry *)((char *)fdt + offset); + re->address = cpu_to_fdt64(addr); + re->size = cpu_to_fdt64(size); + + fdt_set_off_dt_struct(fdt, offset + sizeof(*re)); + + return 0; +} + +int fdt_finish_reservemap(void *fdt) +{ + return fdt_add_reservemap_entry(fdt, 0, 0); +} + +int fdt_begin_node(void *fdt, const char *name) +{ + struct fdt_node_header *nh; + int namelen = strlen(name) + 1; + + FDT_SW_CHECK_HEADER(fdt); + + nh = _fdt_grab_space(fdt, sizeof(*nh) + FDT_TAGALIGN(namelen)); + if (! nh) + return -FDT_ERR_NOSPACE; + + nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); + memcpy(nh->name, name, namelen); + return 0; +} + +int fdt_end_node(void *fdt) +{ + fdt32_t *en; + + FDT_SW_CHECK_HEADER(fdt); + + en = _fdt_grab_space(fdt, FDT_TAGSIZE); + if (! en) + return -FDT_ERR_NOSPACE; + + *en = cpu_to_fdt32(FDT_END_NODE); + return 0; +} + +static int _fdt_find_add_string(void *fdt, const char *s) +{ + char *strtab = (char *)fdt + fdt_totalsize(fdt); + const char *p; + int strtabsize = fdt_size_dt_strings(fdt); + int len = strlen(s) + 1; + int struct_top, offset; + + p = _fdt_find_string(strtab - strtabsize, strtabsize, s); + if (p) + return p - strtab; + + /* Add it */ + offset = -strtabsize - len; + struct_top = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + if (fdt_totalsize(fdt) + offset < struct_top) + return 0; /* no more room :( */ + + memcpy(strtab + offset, s, len); + fdt_set_size_dt_strings(fdt, strtabsize + len); + return offset; +} + +int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp) +{ + struct fdt_property *prop; + int nameoff; + + FDT_SW_CHECK_HEADER(fdt); + + nameoff = _fdt_find_add_string(fdt, name); + if (nameoff == 0) + return -FDT_ERR_NOSPACE; + + prop = _fdt_grab_space(fdt, sizeof(*prop) + FDT_TAGALIGN(len)); + if (! prop) + return -FDT_ERR_NOSPACE; + + prop->tag = cpu_to_fdt32(FDT_PROP); + prop->nameoff = cpu_to_fdt32(nameoff); + prop->len = cpu_to_fdt32(len); + *valp = prop->data; + return 0; +} + +int fdt_property(void *fdt, const char *name, const void *val, int len) +{ + void *ptr; + int ret; + + ret = fdt_property_placeholder(fdt, name, len, &ptr); + if (ret) + return ret; + memcpy(ptr, val, len); + return 0; +} + +int fdt_finish(void *fdt) +{ + char *p = (char *)fdt; + fdt32_t *end; + int oldstroffset, newstroffset; + uint32_t tag; + int offset, nextoffset; + + FDT_SW_CHECK_HEADER(fdt); + + /* Add terminator */ + end = _fdt_grab_space(fdt, sizeof(*end)); + if (! end) + return -FDT_ERR_NOSPACE; + *end = cpu_to_fdt32(FDT_END); + + /* Relocate the string table */ + oldstroffset = fdt_totalsize(fdt) - fdt_size_dt_strings(fdt); + newstroffset = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + memmove(p + newstroffset, p + oldstroffset, fdt_size_dt_strings(fdt)); + fdt_set_off_dt_strings(fdt, newstroffset); + + /* Walk the structure, correcting string offsets */ + offset = 0; + while ((tag = fdt_next_tag(fdt, offset, &nextoffset)) != FDT_END) { + if (tag == FDT_PROP) { + struct fdt_property *prop = + _fdt_offset_ptr_w(fdt, offset); + int nameoff; + + nameoff = fdt32_to_cpu(prop->nameoff); + nameoff += fdt_size_dt_strings(fdt); + prop->nameoff = cpu_to_fdt32(nameoff); + } + offset = nextoffset; + } + if (nextoffset < 0) + return nextoffset; + + /* Finally, adjust the header */ + fdt_set_totalsize(fdt, newstroffset + fdt_size_dt_strings(fdt)); + fdt_set_magic(fdt, FDT_MAGIC); + return 0; +} diff --git a/external/libfdt/fdt_wip.c b/external/libfdt/fdt_wip.c new file mode 100644 index 0000000..5e85919 --- /dev/null +++ b/external/libfdt/fdt_wip.c @@ -0,0 +1,139 @@ +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) 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. + * + * 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 OWNER 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. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +int fdt_setprop_inplace_namelen_partial(void *fdt, int nodeoffset, + const char *name, int namelen, + uint32_t idx, const void *val, + int len) +{ + void *propval; + int proplen; + + propval = fdt_getprop_namelen_w(fdt, nodeoffset, name, namelen, + &proplen); + if (!propval) + return proplen; + + if (proplen < (len + idx)) + return -FDT_ERR_NOSPACE; + + memcpy((char *)propval + idx, val, len); + return 0; +} + +int fdt_setprop_inplace(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + const void *propval; + int proplen; + + propval = fdt_getprop(fdt, nodeoffset, name, &proplen); + if (!propval) + return proplen; + + if (proplen != len) + return -FDT_ERR_NOSPACE; + + return fdt_setprop_inplace_namelen_partial(fdt, nodeoffset, name, + strlen(name), 0, + val, len); +} + +static void _fdt_nop_region(void *start, int len) +{ + fdt32_t *p; + + for (p = start; (char *)p < ((char *)start + len); p++) + *p = cpu_to_fdt32(FDT_NOP); +} + +int fdt_nop_property(void *fdt, int nodeoffset, const char *name) +{ + struct fdt_property *prop; + int len; + + prop = fdt_get_property_w(fdt, nodeoffset, name, &len); + if (!prop) + return len; + + _fdt_nop_region(prop, len + sizeof(*prop)); + + return 0; +} + +int _fdt_node_end_offset(void *fdt, int offset) +{ + int depth = 0; + + while ((offset >= 0) && (depth >= 0)) + offset = fdt_next_node(fdt, offset, &depth); + + return offset; +} + +int fdt_nop_node(void *fdt, int nodeoffset) +{ + int endoffset; + + endoffset = _fdt_node_end_offset(fdt, nodeoffset); + if (endoffset < 0) + return endoffset; + + _fdt_nop_region(fdt_offset_ptr_w(fdt, nodeoffset, 0), + endoffset - nodeoffset); + return 0; +} diff --git a/external/libfdt/libfdt.h b/external/libfdt/libfdt.h new file mode 100644 index 0000000..7f83023 --- /dev/null +++ b/external/libfdt/libfdt.h @@ -0,0 +1,1899 @@ +#ifndef _LIBFDT_H +#define _LIBFDT_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) 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. + * + * 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 OWNER 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. + */ + +#include "libfdt_env.h" +#include "fdt.h" + +#define FDT_FIRST_SUPPORTED_VERSION 0x10 +#define FDT_LAST_SUPPORTED_VERSION 0x11 + +/* Error codes: informative error codes */ +#define FDT_ERR_NOTFOUND 1 + /* FDT_ERR_NOTFOUND: The requested node or property does not exist */ +#define FDT_ERR_EXISTS 2 + /* FDT_ERR_EXISTS: Attempted to create a node or property which + * already exists */ +#define FDT_ERR_NOSPACE 3 + /* FDT_ERR_NOSPACE: Operation needed to expand the device + * tree, but its buffer did not have sufficient space to + * contain the expanded tree. Use fdt_open_into() to move the + * device tree to a buffer with more space. */ + +/* Error codes: codes for bad parameters */ +#define FDT_ERR_BADOFFSET 4 + /* FDT_ERR_BADOFFSET: Function was passed a structure block + * offset which is out-of-bounds, or which points to an + * unsuitable part of the structure for the operation. */ +#define FDT_ERR_BADPATH 5 + /* FDT_ERR_BADPATH: Function was passed a badly formatted path + * (e.g. missing a leading / for a function which requires an + * absolute path) */ +#define FDT_ERR_BADPHANDLE 6 + /* FDT_ERR_BADPHANDLE: Function was passed an invalid phandle. + * This can be caused either by an invalid phandle property + * length, or the phandle value was either 0 or -1, which are + * not permitted. */ +#define FDT_ERR_BADSTATE 7 + /* FDT_ERR_BADSTATE: Function was passed an incomplete device + * tree created by the sequential-write functions, which is + * not sufficiently complete for the requested operation. */ + +/* Error codes: codes for bad device tree blobs */ +#define FDT_ERR_TRUNCATED 8 + /* FDT_ERR_TRUNCATED: Structure block of the given device tree + * ends without an FDT_END tag. */ +#define FDT_ERR_BADMAGIC 9 + /* FDT_ERR_BADMAGIC: Given "device tree" appears not to be a + * device tree at all - it is missing the flattened device + * tree magic number. */ +#define FDT_ERR_BADVERSION 10 + /* FDT_ERR_BADVERSION: Given device tree has a version which + * can't be handled by the requested operation. For + * read-write functions, this may mean that fdt_open_into() is + * required to convert the tree to the expected version. */ +#define FDT_ERR_BADSTRUCTURE 11 + /* FDT_ERR_BADSTRUCTURE: Given device tree has a corrupt + * structure block or other serious error (e.g. misnested + * nodes, or subnodes preceding properties). */ +#define FDT_ERR_BADLAYOUT 12 + /* FDT_ERR_BADLAYOUT: For read-write functions, the given + * device tree has it's sub-blocks in an order that the + * function can't handle (memory reserve map, then structure, + * then strings). Use fdt_open_into() to reorganize the tree + * into a form suitable for the read-write operations. */ + +/* "Can't happen" error indicating a bug in libfdt */ +#define FDT_ERR_INTERNAL 13 + /* FDT_ERR_INTERNAL: libfdt has failed an internal assertion. + * Should never be returned, if it is, it indicates a bug in + * libfdt itself. */ + +/* Errors in device tree content */ +#define FDT_ERR_BADNCELLS 14 + /* FDT_ERR_BADNCELLS: Device tree has a #address-cells, #size-cells + * or similar property with a bad format or value */ + +#define FDT_ERR_BADVALUE 15 + /* FDT_ERR_BADVALUE: Device tree has a property with an unexpected + * value. For example: a property expected to contain a string list + * is not NUL-terminated within the length of its value. */ + +#define FDT_ERR_BADOVERLAY 16 + /* FDT_ERR_BADOVERLAY: The device tree overlay, while + * correctly structured, cannot be applied due to some + * unexpected or missing value, property or node. */ + +#define FDT_ERR_NOPHANDLES 17 + /* FDT_ERR_NOPHANDLES: The device tree doesn't have any + * phandle available anymore without causing an overflow */ + +#define FDT_ERR_MAX 17 + +/**********************************************************************/ +/* Low-level functions (you probably don't need these) */ +/**********************************************************************/ + +#ifndef SWIG /* This function is not useful in Python */ +const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int checklen); +#endif +static inline void *fdt_offset_ptr_w(void *fdt, int offset, int checklen) +{ + return (void *)(uintptr_t)fdt_offset_ptr(fdt, offset, checklen); +} + +uint32_t fdt_next_tag(const void *fdt, int offset, int *nextoffset); + +/**********************************************************************/ +/* Traversal functions */ +/**********************************************************************/ + +int fdt_next_node(const void *fdt, int offset, int *depth); + +/** + * fdt_first_subnode() - get offset of first direct subnode + * + * @fdt: FDT blob + * @offset: Offset of node to check + * @return offset of first subnode, or -FDT_ERR_NOTFOUND if there is none + */ +int fdt_first_subnode(const void *fdt, int offset); + +/** + * fdt_next_subnode() - get offset of next direct subnode + * + * After first calling fdt_first_subnode(), call this function repeatedly to + * get direct subnodes of a parent node. + * + * @fdt: FDT blob + * @offset: Offset of previous subnode + * @return offset of next subnode, or -FDT_ERR_NOTFOUND if there are no more + * subnodes + */ +int fdt_next_subnode(const void *fdt, int offset); + +/** + * fdt_for_each_subnode - iterate over all subnodes of a parent + * + * @node: child node (int, lvalue) + * @fdt: FDT blob (const void *) + * @parent: parent node (int) + * + * This is actually a wrapper around a for loop and would be used like so: + * + * fdt_for_each_subnode(node, fdt, parent) { + * Use node + * ... + * } + * + * if ((node < 0) && (node != -FDT_ERR_NOT_FOUND)) { + * Error handling + * } + * + * Note that this is implemented as a macro and @node is used as + * iterator in the loop. The parent variable be constant or even a + * literal. + * + */ +#define fdt_for_each_subnode(node, fdt, parent) \ + for (node = fdt_first_subnode(fdt, parent); \ + node >= 0; \ + node = fdt_next_subnode(fdt, node)) + +/**********************************************************************/ +/* General functions */ +/**********************************************************************/ +#define fdt_get_header(fdt, field) \ + (fdt32_to_cpu(((const struct fdt_header *)(fdt))->field)) +#define fdt_magic(fdt) (fdt_get_header(fdt, magic)) +#define fdt_totalsize(fdt) (fdt_get_header(fdt, totalsize)) +#define fdt_off_dt_struct(fdt) (fdt_get_header(fdt, off_dt_struct)) +#define fdt_off_dt_strings(fdt) (fdt_get_header(fdt, off_dt_strings)) +#define fdt_off_mem_rsvmap(fdt) (fdt_get_header(fdt, off_mem_rsvmap)) +#define fdt_version(fdt) (fdt_get_header(fdt, version)) +#define fdt_last_comp_version(fdt) (fdt_get_header(fdt, last_comp_version)) +#define fdt_boot_cpuid_phys(fdt) (fdt_get_header(fdt, boot_cpuid_phys)) +#define fdt_size_dt_strings(fdt) (fdt_get_header(fdt, size_dt_strings)) +#define fdt_size_dt_struct(fdt) (fdt_get_header(fdt, size_dt_struct)) + +#define __fdt_set_hdr(name) \ + static inline void fdt_set_##name(void *fdt, uint32_t val) \ + { \ + struct fdt_header *fdth = (struct fdt_header *)fdt; \ + fdth->name = cpu_to_fdt32(val); \ + } +__fdt_set_hdr(magic); +__fdt_set_hdr(totalsize); +__fdt_set_hdr(off_dt_struct); +__fdt_set_hdr(off_dt_strings); +__fdt_set_hdr(off_mem_rsvmap); +__fdt_set_hdr(version); +__fdt_set_hdr(last_comp_version); +__fdt_set_hdr(boot_cpuid_phys); +__fdt_set_hdr(size_dt_strings); +__fdt_set_hdr(size_dt_struct); +#undef __fdt_set_hdr + +/** + * fdt_check_header - sanity check a device tree or possible device tree + * @fdt: pointer to data which might be a flattened device tree + * + * fdt_check_header() checks that the given buffer contains what + * appears to be a flattened device tree with sane information in its + * header. + * + * returns: + * 0, if the buffer appears to contain a valid device tree + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings, as above + */ +int fdt_check_header(const void *fdt); + +/** + * fdt_move - move a device tree around in memory + * @fdt: pointer to the device tree to move + * @buf: pointer to memory where the device is to be moved + * @bufsize: size of the memory space at buf + * + * fdt_move() relocates, if possible, the device tree blob located at + * fdt to the buffer at buf of size bufsize. The buffer may overlap + * with the existing device tree blob at fdt. Therefore, + * fdt_move(fdt, fdt, fdt_totalsize(fdt)) + * should always succeed. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, bufsize is insufficient to contain the device tree + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_move(const void *fdt, void *buf, int bufsize); + +/**********************************************************************/ +/* Read-only functions */ +/**********************************************************************/ + +/** + * fdt_string - retrieve a string from the strings block of a device tree + * @fdt: pointer to the device tree blob + * @stroffset: offset of the string within the strings block (native endian) + * + * fdt_string() retrieves a pointer to a single string from the + * strings block of the device tree blob at fdt. + * + * returns: + * a pointer to the string, on success + * NULL, if stroffset is out of bounds + */ +const char *fdt_string(const void *fdt, int stroffset); + +/** + * fdt_get_max_phandle - retrieves the highest phandle in a tree + * @fdt: pointer to the device tree blob + * + * fdt_get_max_phandle retrieves the highest phandle in the given + * device tree. This will ignore badly formatted phandles, or phandles + * with a value of 0 or -1. + * + * returns: + * the highest phandle on success + * 0, if no phandle was found in the device tree + * -1, if an error occurred + */ +uint32_t fdt_get_max_phandle(const void *fdt); + +/** + * fdt_num_mem_rsv - retrieve the number of memory reserve map entries + * @fdt: pointer to the device tree blob + * + * Returns the number of entries in the device tree blob's memory + * reservation map. This does not include the terminating 0,0 entry + * or any other (0,0) entries reserved for expansion. + * + * returns: + * the number of entries + */ +int fdt_num_mem_rsv(const void *fdt); + +/** + * fdt_get_mem_rsv - retrieve one memory reserve map entry + * @fdt: pointer to the device tree blob + * @address, @size: pointers to 64-bit variables + * + * On success, *address and *size will contain the address and size of + * the n-th reserve map entry from the device tree blob, in + * native-endian format. + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size); + +/** + * fdt_subnode_offset_namelen - find a subnode based on substring + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * @namelen: number of characters of name to consider + * + * Identical to fdt_subnode_offset(), but only examine the first + * namelen characters of name for matching the subnode name. This is + * useful for finding subnodes based on a portion of a larger string, + * such as a full path. + */ +#ifndef SWIG /* Not available in Python */ +int fdt_subnode_offset_namelen(const void *fdt, int parentoffset, + const char *name, int namelen); +#endif +/** + * fdt_subnode_offset - find a subnode of a given node + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * + * fdt_subnode_offset() finds a subnode of the node at structure block + * offset parentoffset with the given name. name may include a unit + * address, in which case fdt_subnode_offset() will find the subnode + * with that unit address, or the unit address may be omitted, in + * which case fdt_subnode_offset() will find an arbitrary subnode + * whose name excluding unit address matches the given name. + * + * returns: + * structure block offset of the requested subnode (>=0), on success + * -FDT_ERR_NOTFOUND, if the requested subnode does not exist + * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_subnode_offset(const void *fdt, int parentoffset, const char *name); + +/** + * fdt_path_offset_namelen - find a tree node by its full path + * @fdt: pointer to the device tree blob + * @path: full path of the node to locate + * @namelen: number of characters of path to consider + * + * Identical to fdt_path_offset(), but only consider the first namelen + * characters of path as the path name. + */ +#ifndef SWIG /* Not available in Python */ +int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen); +#endif + +/** + * fdt_path_offset - find a tree node by its full path + * @fdt: pointer to the device tree blob + * @path: full path of the node to locate + * + * fdt_path_offset() finds a node of a given path in the device tree. + * Each path component may omit the unit address portion, but the + * results of this are undefined if any such path component is + * ambiguous (that is if there are multiple nodes at the relevant + * level matching the given component, differentiated only by unit + * address). + * + * returns: + * structure block offset of the node with the requested path (>=0), on + * success + * -FDT_ERR_BADPATH, given path does not begin with '/' or is invalid + * -FDT_ERR_NOTFOUND, if the requested node does not exist + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_path_offset(const void *fdt, const char *path); + +/** + * fdt_get_name - retrieve the name of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of the starting node + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_name() retrieves the name (including unit address) of the + * device tree node at structure block offset nodeoffset. If lenp is + * non-NULL, the length of this name is also returned, in the integer + * pointed to by lenp. + * + * returns: + * pointer to the node's name, on success + * If lenp is non-NULL, *lenp contains the length of that name + * (>=0) + * NULL, on error + * if lenp is non-NULL *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +const char *fdt_get_name(const void *fdt, int nodeoffset, int *lenp); + +/** + * fdt_first_property_offset - find the offset of a node's first property + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of a node + * + * fdt_first_property_offset() finds the first property of the node at + * the given structure block offset. + * + * returns: + * structure block offset of the property (>=0), on success + * -FDT_ERR_NOTFOUND, if the requested node has no properties + * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_first_property_offset(const void *fdt, int nodeoffset); + +/** + * fdt_next_property_offset - step through a node's properties + * @fdt: pointer to the device tree blob + * @offset: structure block offset of a property + * + * fdt_next_property_offset() finds the property immediately after the + * one at the given structure block offset. This will be a property + * of the same node as the given property. + * + * returns: + * structure block offset of the next property (>=0), on success + * -FDT_ERR_NOTFOUND, if the given property is the last in its node + * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_next_property_offset(const void *fdt, int offset); + +/** + * fdt_for_each_property_offset - iterate over all properties of a node + * + * @property_offset: property offset (int, lvalue) + * @fdt: FDT blob (const void *) + * @node: node offset (int) + * + * This is actually a wrapper around a for loop and would be used like so: + * + * fdt_for_each_property_offset(property, fdt, node) { + * Use property + * ... + * } + * + * if ((property < 0) && (property != -FDT_ERR_NOT_FOUND)) { + * Error handling + * } + * + * Note that this is implemented as a macro and property is used as + * iterator in the loop. The node variable can be constant or even a + * literal. + */ +#define fdt_for_each_property_offset(property, fdt, node) \ + for (property = fdt_first_property_offset(fdt, node); \ + property >= 0; \ + property = fdt_next_property_offset(fdt, property)) + +/** + * fdt_get_property_by_offset - retrieve the property at a given offset + * @fdt: pointer to the device tree blob + * @offset: offset of the property to retrieve + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_property_by_offset() retrieves a pointer to the + * fdt_property structure within the device tree blob at the given + * offset. If lenp is non-NULL, the length of the property value is + * also returned, in the integer pointed to by lenp. + * + * returns: + * pointer to the structure representing the property + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const struct fdt_property *fdt_get_property_by_offset(const void *fdt, + int offset, + int *lenp); + +/** + * fdt_get_property_namelen - find a property based on substring + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @namelen: number of characters of name to consider + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * Identical to fdt_get_property(), but only examine the first namelen + * characters of name for matching the property name. + */ +#ifndef SWIG /* Not available in Python */ +const struct fdt_property *fdt_get_property_namelen(const void *fdt, + int nodeoffset, + const char *name, + int namelen, int *lenp); +#endif + +/** + * fdt_get_property - find a given property in a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_property() retrieves a pointer to the fdt_property + * structure within the device tree blob corresponding to the property + * named 'name' of the node at offset nodeoffset. If lenp is + * non-NULL, the length of the property value is also returned, in the + * integer pointed to by lenp. + * + * returns: + * pointer to the structure representing the property + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_NOTFOUND, node does not have named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const struct fdt_property *fdt_get_property(const void *fdt, int nodeoffset, + const char *name, int *lenp); +static inline struct fdt_property *fdt_get_property_w(void *fdt, int nodeoffset, + const char *name, + int *lenp) +{ + return (struct fdt_property *)(uintptr_t) + fdt_get_property(fdt, nodeoffset, name, lenp); +} + +/** + * fdt_getprop_by_offset - retrieve the value of a property at a given offset + * @fdt: pointer to the device tree blob + * @ffset: offset of the property to read + * @namep: pointer to a string variable (will be overwritten) or NULL + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_getprop_by_offset() retrieves a pointer to the value of the + * property at structure block offset 'offset' (this will be a pointer + * to within the device blob itself, not a copy of the value). If + * lenp is non-NULL, the length of the property value is also + * returned, in the integer pointed to by lenp. If namep is non-NULL, + * the property's namne will also be returned in the char * pointed to + * by namep (this will be a pointer to within the device tree's string + * block, not a new copy of the name). + * + * returns: + * pointer to the property's value + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * if namep is non-NULL *namep contiains a pointer to the property + * name. + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#ifndef SWIG /* This function is not useful in Python */ +const void *fdt_getprop_by_offset(const void *fdt, int offset, + const char **namep, int *lenp); +#endif + +/** + * fdt_getprop_namelen - get property value based on substring + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @namelen: number of characters of name to consider + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * Identical to fdt_getprop(), but only examine the first namelen + * characters of name for matching the property name. + */ +#ifndef SWIG /* Not available in Python */ +const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, + const char *name, int namelen, int *lenp); +static inline void *fdt_getprop_namelen_w(void *fdt, int nodeoffset, + const char *name, int namelen, + int *lenp) +{ + return (void *)(uintptr_t)fdt_getprop_namelen(fdt, nodeoffset, name, + namelen, lenp); +} +#endif + +/** + * fdt_getprop - retrieve the value of a given property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_getprop() retrieves a pointer to the value of the property + * named 'name' of the node at offset nodeoffset (this will be a + * pointer to within the device blob itself, not a copy of the value). + * If lenp is non-NULL, the length of the property value is also + * returned, in the integer pointed to by lenp. + * + * returns: + * pointer to the property's value + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_NOTFOUND, node does not have named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const void *fdt_getprop(const void *fdt, int nodeoffset, + const char *name, int *lenp); +static inline void *fdt_getprop_w(void *fdt, int nodeoffset, + const char *name, int *lenp) +{ + return (void *)(uintptr_t)fdt_getprop(fdt, nodeoffset, name, lenp); +} + +/** + * fdt_get_phandle - retrieve the phandle of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of the node + * + * fdt_get_phandle() retrieves the phandle of the device tree node at + * structure block offset nodeoffset. + * + * returns: + * the phandle of the node at nodeoffset, on success (!= 0, != -1) + * 0, if the node has no phandle, or another error occurs + */ +uint32_t fdt_get_phandle(const void *fdt, int nodeoffset); + +/** + * fdt_get_alias_namelen - get alias based on substring + * @fdt: pointer to the device tree blob + * @name: name of the alias th look up + * @namelen: number of characters of name to consider + * + * Identical to fdt_get_alias(), but only examine the first namelen + * characters of name for matching the alias name. + */ +#ifndef SWIG /* Not available in Python */ +const char *fdt_get_alias_namelen(const void *fdt, + const char *name, int namelen); +#endif + +/** + * fdt_get_alias - retrieve the path referenced by a given alias + * @fdt: pointer to the device tree blob + * @name: name of the alias th look up + * + * fdt_get_alias() retrieves the value of a given alias. That is, the + * value of the property named 'name' in the node /aliases. + * + * returns: + * a pointer to the expansion of the alias named 'name', if it exists + * NULL, if the given alias or the /aliases node does not exist + */ +const char *fdt_get_alias(const void *fdt, const char *name); + +/** + * fdt_get_path - determine the full path of a node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose path to find + * @buf: character buffer to contain the returned path (will be overwritten) + * @buflen: size of the character buffer at buf + * + * fdt_get_path() computes the full path of the node at offset + * nodeoffset, and records that path in the buffer at buf. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * 0, on success + * buf contains the absolute path of the node at + * nodeoffset, as a NUL-terminated string. + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_NOSPACE, the path of the given node is longer than (bufsize-1) + * characters and will not fit in the given buffer. + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen); + +/** + * fdt_supernode_atdepth_offset - find a specific ancestor of a node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * @supernodedepth: depth of the ancestor to find + * @nodedepth: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_supernode_atdepth_offset() finds an ancestor of the given node + * at a specific depth from the root (where the root itself has depth + * 0, its immediate subnodes depth 1 and so forth). So + * fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, NULL); + * will always return 0, the offset of the root node. If the node at + * nodeoffset has depth D, then: + * fdt_supernode_atdepth_offset(fdt, nodeoffset, D, NULL); + * will return nodeoffset itself. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * structure block offset of the node at node offset's ancestor + * of depth supernodedepth (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_NOTFOUND, supernodedepth was greater than the depth of + * nodeoffset + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, + int supernodedepth, int *nodedepth); + +/** + * fdt_node_depth - find the depth of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * + * fdt_node_depth() finds the depth of a given node. The root node + * has depth 0, its immediate subnodes depth 1 and so forth. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * depth of the node at nodeoffset (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_depth(const void *fdt, int nodeoffset); + +/** + * fdt_parent_offset - find the parent of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * + * fdt_parent_offset() locates the parent node of a given node (that + * is, it finds the offset of the node which contains the node at + * nodeoffset as a subnode). + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset, *twice*. + * + * returns: + * structure block offset of the parent of the node at nodeoffset + * (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_parent_offset(const void *fdt, int nodeoffset); + +/** + * fdt_node_offset_by_prop_value - find nodes with a given property value + * @fdt: pointer to the device tree blob + * @startoffset: only find nodes after this offset + * @propname: property name to check + * @propval: property value to search for + * @proplen: length of the value in propval + * + * fdt_node_offset_by_prop_value() returns the offset of the first + * node after startoffset, which has a property named propname whose + * value is of length proplen and has value equal to propval; or if + * startoffset is -1, the very first such node in the tree. + * + * To iterate through all nodes matching the criterion, the following + * idiom can be used: + * offset = fdt_node_offset_by_prop_value(fdt, -1, propname, + * propval, proplen); + * while (offset != -FDT_ERR_NOTFOUND) { + * // other code here + * offset = fdt_node_offset_by_prop_value(fdt, offset, propname, + * propval, proplen); + * } + * + * Note the -1 in the first call to the function, if 0 is used here + * instead, the function will never locate the root node, even if it + * matches the criterion. + * + * returns: + * structure block offset of the located node (>= 0, >startoffset), + * on success + * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the + * tree after startoffset + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, + const char *propname, + const void *propval, int proplen); + +/** + * fdt_node_offset_by_phandle - find the node with a given phandle + * @fdt: pointer to the device tree blob + * @phandle: phandle value + * + * fdt_node_offset_by_phandle() returns the offset of the node + * which has the given phandle value. If there is more than one node + * in the tree with the given phandle (an invalid tree), results are + * undefined. + * + * returns: + * structure block offset of the located node (>= 0), on success + * -FDT_ERR_NOTFOUND, no node with that phandle exists + * -FDT_ERR_BADPHANDLE, given phandle value was invalid (0 or -1) + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle); + +/** + * fdt_node_check_compatible: check a node's compatible property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @compatible: string to match against + * + * + * fdt_node_check_compatible() returns 0 if the given node contains a + * 'compatible' property with the given string as one of its elements, + * it returns non-zero otherwise, or on error. + * + * returns: + * 0, if the node has a 'compatible' property listing the given string + * 1, if the node has a 'compatible' property, but it does not list + * the given string + * -FDT_ERR_NOTFOUND, if the given node has no 'compatible' property + * -FDT_ERR_BADOFFSET, if nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_check_compatible(const void *fdt, int nodeoffset, + const char *compatible); + +/** + * fdt_node_offset_by_compatible - find nodes with a given 'compatible' value + * @fdt: pointer to the device tree blob + * @startoffset: only find nodes after this offset + * @compatible: 'compatible' string to match against + * + * fdt_node_offset_by_compatible() returns the offset of the first + * node after startoffset, which has a 'compatible' property which + * lists the given compatible string; or if startoffset is -1, the + * very first such node in the tree. + * + * To iterate through all nodes matching the criterion, the following + * idiom can be used: + * offset = fdt_node_offset_by_compatible(fdt, -1, compatible); + * while (offset != -FDT_ERR_NOTFOUND) { + * // other code here + * offset = fdt_node_offset_by_compatible(fdt, offset, compatible); + * } + * + * Note the -1 in the first call to the function, if 0 is used here + * instead, the function will never locate the root node, even if it + * matches the criterion. + * + * returns: + * structure block offset of the located node (>= 0, >startoffset), + * on success + * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the + * tree after startoffset + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_compatible(const void *fdt, int startoffset, + const char *compatible); + +/** + * fdt_stringlist_contains - check a string list property for a string + * @strlist: Property containing a list of strings to check + * @listlen: Length of property + * @str: String to search for + * + * This is a utility function provided for convenience. The list contains + * one or more strings, each terminated by \0, as is found in a device tree + * "compatible" property. + * + * @return: 1 if the string is found in the list, 0 not found, or invalid list + */ +int fdt_stringlist_contains(const char *strlist, int listlen, const char *str); + +/** + * fdt_stringlist_count - count the number of strings in a string list + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * @return: + * the number of strings in the given property + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist + */ +int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property); + +/** + * fdt_stringlist_search - find a string in a string list and return its index + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * @string: string to look up in the string list + * + * Note that it is possible for this function to succeed on property values + * that are not NUL-terminated. That's because the function will stop after + * finding the first occurrence of @string. This can for example happen with + * small-valued cell properties, such as #address-cells, when searching for + * the empty string. + * + * @return: + * the index of the string in the list of strings + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist or does not contain + * the given string + */ +int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, + const char *string); + +/** + * fdt_stringlist_get() - obtain the string at a given index in a string list + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * @index: index of the string to return + * @lenp: return location for the string length or an error code on failure + * + * Note that this will successfully extract strings from properties with + * non-NUL-terminated values. For example on small-valued cell properties + * this function will return the empty string. + * + * If non-NULL, the length of the string (on success) or a negative error-code + * (on failure) will be stored in the integer pointer to by lenp. + * + * @return: + * A pointer to the string at the given index in the string list or NULL on + * failure. On success the length of the string will be stored in the memory + * location pointed to by the lenp parameter, if non-NULL. On failure one of + * the following negative error codes will be returned in the lenp parameter + * (if non-NULL): + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist + */ +const char *fdt_stringlist_get(const void *fdt, int nodeoffset, + const char *property, int index, + int *lenp); + +/**********************************************************************/ +/* Read-only functions (addressing related) */ +/**********************************************************************/ + +/** + * FDT_MAX_NCELLS - maximum value for #address-cells and #size-cells + * + * This is the maximum value for #address-cells, #size-cells and + * similar properties that will be processed by libfdt. IEE1275 + * requires that OF implementations handle values up to 4. + * Implementations may support larger values, but in practice higher + * values aren't used. + */ +#define FDT_MAX_NCELLS 4 + +/** + * fdt_address_cells - retrieve address size for a bus represented in the tree + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to find the address size for + * + * When the node has a valid #address-cells property, returns its value. + * + * returns: + * 0 <= n < FDT_MAX_NCELLS, on success + * 2, if the node has no #address-cells property + * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid + * #address-cells property + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_address_cells(const void *fdt, int nodeoffset); + +/** + * fdt_size_cells - retrieve address range size for a bus represented in the + * tree + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to find the address range size for + * + * When the node has a valid #size-cells property, returns its value. + * + * returns: + * 0 <= n < FDT_MAX_NCELLS, on success + * 2, if the node has no #address-cells property + * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid + * #size-cells property + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_size_cells(const void *fdt, int nodeoffset); + + +/**********************************************************************/ +/* Write-in-place functions */ +/**********************************************************************/ + +/** + * fdt_setprop_inplace_namelen_partial - change a property's value, + * but not its size + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @namelen: number of characters of name to consider + * @idx: index of the property to change in the array + * @val: pointer to data to replace the property value with + * @len: length of the property value + * + * Identical to fdt_setprop_inplace(), but modifies the given property + * starting from the given index, and using only the first characters + * of the name. It is useful when you want to manipulate only one value of + * an array and you have a string that doesn't end with \0. + */ +#ifndef SWIG /* Not available in Python */ +int fdt_setprop_inplace_namelen_partial(void *fdt, int nodeoffset, + const char *name, int namelen, + uint32_t idx, const void *val, + int len); +#endif + +/** + * fdt_setprop_inplace - change a property's value, but not its size + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: pointer to data to replace the property value with + * @len: length of the property value + * + * fdt_setprop_inplace() replaces the value of a given property with + * the data in val, of length len. This function cannot change the + * size of a property, and so will only work if len is equal to the + * current length of the property. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if len is not equal to the property's current length + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#ifndef SWIG /* Not available in Python */ +int fdt_setprop_inplace(void *fdt, int nodeoffset, const char *name, + const void *val, int len); +#endif + +/** + * fdt_setprop_inplace_u32 - change the value of a 32-bit integer property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value to replace the property with + * + * fdt_setprop_inplace_u32() replaces the value of a given property + * with the 32-bit integer value in val, converting val to big-endian + * if necessary. This function cannot change the size of a property, + * and so will only work if the property already exists and has length + * 4. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if the property's length is not equal to 4 + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_inplace_u32(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_inplace_u64 - change the value of a 64-bit integer property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value to replace the property with + * + * fdt_setprop_inplace_u64() replaces the value of a given property + * with the 64-bit integer value in val, converting val to big-endian + * if necessary. This function cannot change the size of a property, + * and so will only work if the property already exists and has length + * 8. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if the property's length is not equal to 8 + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_inplace_u64(void *fdt, int nodeoffset, + const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_inplace_cell - change the value of a single-cell property + * + * This is an alternative name for fdt_setprop_inplace_u32() + */ +static inline int fdt_setprop_inplace_cell(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + return fdt_setprop_inplace_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_nop_property - replace a property with nop tags + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to nop + * @name: name of the property to nop + * + * fdt_nop_property() will replace a given property's representation + * in the blob with FDT_NOP tags, effectively removing it from the + * tree. + * + * This function will alter only the bytes in the blob which contain + * the property, and will not alter or move any other part of the + * tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_nop_property(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_nop_node - replace a node (subtree) with nop tags + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to nop + * + * fdt_nop_node() will replace a given node's representation in the + * blob, including all its subnodes, if any, with FDT_NOP tags, + * effectively removing it from the tree. + * + * This function will alter only the bytes in the blob which contain + * the node and its properties and subnodes, and will not alter or + * move any other part of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_nop_node(void *fdt, int nodeoffset); + +/**********************************************************************/ +/* Sequential write functions */ +/**********************************************************************/ + +int fdt_create(void *buf, int bufsize); +int fdt_resize(void *fdt, void *buf, int bufsize); +int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size); +int fdt_finish_reservemap(void *fdt); +int fdt_begin_node(void *fdt, const char *name); +int fdt_property(void *fdt, const char *name, const void *val, int len); +static inline int fdt_property_u32(void *fdt, const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_property(fdt, name, &tmp, sizeof(tmp)); +} +static inline int fdt_property_u64(void *fdt, const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_property(fdt, name, &tmp, sizeof(tmp)); +} +static inline int fdt_property_cell(void *fdt, const char *name, uint32_t val) +{ + return fdt_property_u32(fdt, name, val); +} + +/** + * fdt_property_placeholder - add a new property and return a ptr to its value + * + * @fdt: pointer to the device tree blob + * @name: name of property to add + * @len: length of property value in bytes + * @valp: returns a pointer to where where the value should be placed + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_NOSPACE, standard meanings + */ +int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp); + +#define fdt_property_string(fdt, name, str) \ + fdt_property(fdt, name, str, strlen(str)+1) +int fdt_end_node(void *fdt); +int fdt_finish(void *fdt); + +/**********************************************************************/ +/* Read-write functions */ +/**********************************************************************/ + +int fdt_create_empty_tree(void *buf, int bufsize); +int fdt_open_into(const void *fdt, void *buf, int bufsize); +int fdt_pack(void *fdt); + +/** + * fdt_add_mem_rsv - add one memory reserve map entry + * @fdt: pointer to the device tree blob + * @address, @size: 64-bit values (native endian) + * + * Adds a reserve map entry to the given blob reserving a region at + * address address of length size. + * + * This function will insert data into the reserve map and will + * therefore change the indexes of some entries in the table. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new reservation entry + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size); + +/** + * fdt_del_mem_rsv - remove a memory reserve map entry + * @fdt: pointer to the device tree blob + * @n: entry to remove + * + * fdt_del_mem_rsv() removes the n-th memory reserve map entry from + * the blob. + * + * This function will delete data from the reservation table and will + * therefore change the indexes of some entries in the table. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, there is no entry of the given index (i.e. there + * are less than n+1 reserve map entries) + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_del_mem_rsv(void *fdt, int n); + +/** + * fdt_set_name - change the name of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of a node + * @name: name to give the node + * + * fdt_set_name() replaces the name (including unit address, if any) + * of the given node with the given string. NOTE: this function can't + * efficiently check if the new name is unique amongst the given + * node's siblings; results are undefined if this function is invoked + * with a name equal to one of the given node's siblings. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob + * to contain the new name + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_set_name(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_setprop - create or change a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: pointer to data to set the property value to + * @len: length of the property value + * + * fdt_setprop() sets the value of the named property in the given + * node to the given value and length, creating the property if it + * does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_setprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len); + +/** + * fdt_setprop _placeholder - allocate space for a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @len: length of the property value + * @prop_data: return pointer to property data + * + * fdt_setprop_placeholer() allocates the named property in the given node. + * If the property exists it is resized. In either case a pointer to the + * property data is returned. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_setprop_placeholder(void *fdt, int nodeoffset, const char *name, + int len, void **prop_data); + +/** + * fdt_setprop_u32 - set a property to a 32-bit integer + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value for the property (native endian) + * + * fdt_setprop_u32() sets the value of the named property in the given + * node to the given 32-bit integer value (converting to big-endian if + * necessary), or creates a new property with that value if it does + * not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_u32(void *fdt, int nodeoffset, const char *name, + uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_u64 - set a property to a 64-bit integer + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value for the property (native endian) + * + * fdt_setprop_u64() sets the value of the named property in the given + * node to the given 64-bit integer value (converting to big-endian if + * necessary), or creates a new property with that value if it does + * not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_u64(void *fdt, int nodeoffset, const char *name, + uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_cell - set a property to a single cell value + * + * This is an alternative name for fdt_setprop_u32() + */ +static inline int fdt_setprop_cell(void *fdt, int nodeoffset, const char *name, + uint32_t val) +{ + return fdt_setprop_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_setprop_string - set a property to a string value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @str: string value for the property + * + * fdt_setprop_string() sets the value of the named property in the + * given node to the given string value (using the length of the + * string to determine the new length of the property), or creates a + * new property with that value if it does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_setprop_string(fdt, nodeoffset, name, str) \ + fdt_setprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) + + +/** + * fdt_setprop_empty - set a property to an empty value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * + * fdt_setprop_empty() sets the value of the named property in the + * given node to an empty (zero length) value, or creates a new empty + * property if it does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_setprop_empty(fdt, nodeoffset, name) \ + fdt_setprop((fdt), (nodeoffset), (name), NULL, 0) + +/** + * fdt_appendprop - append to or create a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to append to + * @val: pointer to data to append to the property value + * @len: length of the data to append to the property value + * + * fdt_appendprop() appends the value to the named property in the + * given node, creating the property if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_appendprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len); + +/** + * fdt_appendprop_u32 - append a 32-bit integer value to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value to append to the property (native endian) + * + * fdt_appendprop_u32() appends the given 32-bit integer value + * (converting to big-endian if necessary) to the value of the named + * property in the given node, or creates a new property with that + * value if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_appendprop_u32(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_appendprop_u64 - append a 64-bit integer value to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value to append to the property (native endian) + * + * fdt_appendprop_u64() appends the given 64-bit integer value + * (converting to big-endian if necessary) to the value of the named + * property in the given node, or creates a new property with that + * value if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_appendprop_u64(void *fdt, int nodeoffset, + const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_appendprop_cell - append a single cell value to a property + * + * This is an alternative name for fdt_appendprop_u32() + */ +static inline int fdt_appendprop_cell(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + return fdt_appendprop_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_appendprop_string - append a string to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @str: string value to append to the property + * + * fdt_appendprop_string() appends the given string to the value of + * the named property in the given node, or creates a new property + * with that value if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_appendprop_string(fdt, nodeoffset, name, str) \ + fdt_appendprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) + +/** + * fdt_delprop - delete a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to nop + * @name: name of the property to nop + * + * fdt_del_property() will delete the given property. + * + * This function will delete data from the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_delprop(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_add_subnode_namelen - creates a new node based on substring + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * @namelen: number of characters of name to consider + * + * Identical to fdt_add_subnode(), but use only the first namelen + * characters of name as the name of the new node. This is useful for + * creating subnodes based on a portion of a larger string, such as a + * full path. + */ +#ifndef SWIG /* Not available in Python */ +int fdt_add_subnode_namelen(void *fdt, int parentoffset, + const char *name, int namelen); +#endif + +/** + * fdt_add_subnode - creates a new node + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * + * fdt_add_subnode() creates a new node as a subnode of the node at + * structure block offset parentoffset, with the given name (which + * should include the unit address, if any). + * + * This function will insert data into the blob, and will therefore + * change the offsets of some existing nodes. + + * returns: + * structure block offset of the created nodeequested subnode (>=0), on + * success + * -FDT_ERR_NOTFOUND, if the requested subnode does not exist + * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE + * tag + * -FDT_ERR_EXISTS, if the node at parentoffset already has a subnode of + * the given name + * -FDT_ERR_NOSPACE, if there is insufficient free space in the + * blob to contain the new node + * -FDT_ERR_NOSPACE + * -FDT_ERR_BADLAYOUT + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_add_subnode(void *fdt, int parentoffset, const char *name); + +/** + * fdt_del_node - delete a node (subtree) + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to nop + * + * fdt_del_node() will remove the given node, including all its + * subnodes if any, from the blob. + * + * This function will delete data from the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_del_node(void *fdt, int nodeoffset); + +/** + * fdt_overlay_apply - Applies a DT overlay on a base DT + * @fdt: pointer to the base device tree blob + * @fdto: pointer to the device tree overlay blob + * + * fdt_overlay_apply() will apply the given device tree overlay on the + * given base device tree. + * + * Expect the base device tree to be modified, even if the function + * returns an error. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there's not enough space in the base device tree + * -FDT_ERR_NOTFOUND, the overlay points to some inexistant nodes or + * properties in the base DT + * -FDT_ERR_BADPHANDLE, + * -FDT_ERR_BADOVERLAY, + * -FDT_ERR_NOPHANDLES, + * -FDT_ERR_INTERNAL, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADOFFSET, + * -FDT_ERR_BADPATH, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_overlay_apply(void *fdt, void *fdto); + +/**********************************************************************/ +/* Debugging / informational functions */ +/**********************************************************************/ + +const char *fdt_strerror(int errval); + +#endif /* _LIBFDT_H */ diff --git a/external/libfdt/libfdt_env.h b/external/libfdt/libfdt_env.h new file mode 100644 index 0000000..952056c --- /dev/null +++ b/external/libfdt/libfdt_env.h @@ -0,0 +1,112 @@ +#ifndef _LIBFDT_ENV_H +#define _LIBFDT_ENV_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * Copyright 2012 Kim Phillips, Freescale Semiconductor. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) 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. + * + * 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 OWNER 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. + */ + +#include +#include +#include +#include + +#ifdef __CHECKER__ +#define FDT_FORCE __attribute__((force)) +#define FDT_BITWISE __attribute__((bitwise)) +#else +#define FDT_FORCE +#define FDT_BITWISE +#endif + +typedef uint16_t FDT_BITWISE fdt16_t; +typedef uint32_t FDT_BITWISE fdt32_t; +typedef uint64_t FDT_BITWISE fdt64_t; + +#define EXTRACT_BYTE(x, n) ((unsigned long long)((uint8_t *)&x)[n]) +#define CPU_TO_FDT16(x) ((EXTRACT_BYTE(x, 0) << 8) | EXTRACT_BYTE(x, 1)) +#define CPU_TO_FDT32(x) ((EXTRACT_BYTE(x, 0) << 24) | (EXTRACT_BYTE(x, 1) << 16) | \ + (EXTRACT_BYTE(x, 2) << 8) | EXTRACT_BYTE(x, 3)) +#define CPU_TO_FDT64(x) ((EXTRACT_BYTE(x, 0) << 56) | (EXTRACT_BYTE(x, 1) << 48) | \ + (EXTRACT_BYTE(x, 2) << 40) | (EXTRACT_BYTE(x, 3) << 32) | \ + (EXTRACT_BYTE(x, 4) << 24) | (EXTRACT_BYTE(x, 5) << 16) | \ + (EXTRACT_BYTE(x, 6) << 8) | EXTRACT_BYTE(x, 7)) + +static inline uint16_t fdt16_to_cpu(fdt16_t x) +{ + return (FDT_FORCE uint16_t)CPU_TO_FDT16(x); +} +static inline fdt16_t cpu_to_fdt16(uint16_t x) +{ + return (FDT_FORCE fdt16_t)CPU_TO_FDT16(x); +} + +static inline uint32_t fdt32_to_cpu(fdt32_t x) +{ + return (FDT_FORCE uint32_t)CPU_TO_FDT32(x); +} +static inline fdt32_t cpu_to_fdt32(uint32_t x) +{ + return (FDT_FORCE fdt32_t)CPU_TO_FDT32(x); +} + +static inline uint64_t fdt64_to_cpu(fdt64_t x) +{ + return (FDT_FORCE uint64_t)CPU_TO_FDT64(x); +} +static inline fdt64_t cpu_to_fdt64(uint64_t x) +{ + return (FDT_FORCE fdt64_t)CPU_TO_FDT64(x); +} +#undef CPU_TO_FDT64 +#undef CPU_TO_FDT32 +#undef CPU_TO_FDT16 +#undef EXTRACT_BYTE + +#endif /* _LIBFDT_ENV_H */ diff --git a/external/libfdt/libfdt_internal.h b/external/libfdt/libfdt_internal.h new file mode 100644 index 0000000..02cfa6f --- /dev/null +++ b/external/libfdt/libfdt_internal.h @@ -0,0 +1,95 @@ +#ifndef _LIBFDT_INTERNAL_H +#define _LIBFDT_INTERNAL_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * + * libfdt is dual licensed: you can use it either under the terms of + * the GPL, or the BSD license, at your option. + * + * a) This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Alternatively, + * + * b) 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. + * + * 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 OWNER 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. + */ +#include + +#define FDT_ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) +#define FDT_TAGALIGN(x) (FDT_ALIGN((x), FDT_TAGSIZE)) + +#define FDT_CHECK_HEADER(fdt) \ + { \ + int __err; \ + if ((__err = fdt_check_header(fdt)) != 0) \ + return __err; \ + } + +int _fdt_check_node_offset(const void *fdt, int offset); +int _fdt_check_prop_offset(const void *fdt, int offset); +const char *_fdt_find_string(const char *strtab, int tabsize, const char *s); +int _fdt_node_end_offset(void *fdt, int nodeoffset); + +static inline const void *_fdt_offset_ptr(const void *fdt, int offset) +{ + return (const char *)fdt + fdt_off_dt_struct(fdt) + offset; +} + +static inline void *_fdt_offset_ptr_w(void *fdt, int offset) +{ + return (void *)(uintptr_t)_fdt_offset_ptr(fdt, offset); +} + +static inline const struct fdt_reserve_entry *_fdt_mem_rsv(const void *fdt, int n) +{ + const struct fdt_reserve_entry *rsv_table = + (const struct fdt_reserve_entry *) + ((const char *)fdt + fdt_off_mem_rsvmap(fdt)); + + return rsv_table + n; +} +static inline struct fdt_reserve_entry *_fdt_mem_rsv_w(void *fdt, int n) +{ + return (void *)(uintptr_t)_fdt_mem_rsv(fdt, n); +} + +#define FDT_SW_MAGIC (~FDT_MAGIC) + +#endif /* _LIBFDT_INTERNAL_H */ diff --git a/img/README.md b/img/README.md new file mode 100644 index 0000000..0c05424 --- /dev/null +++ b/img/README.md @@ -0,0 +1,22 @@ +# Rockchip binary provenance + +Files in this directory are redistributed under Rockchip's binary license in +[`LICENSE`](LICENSE). The RK356x files below were copied from +[`rockchip-linux/rkbin`](https://github.com/rockchip-linux/rkbin) commit +`ecb4fcbe954edf38b3ae037d5de6d9f5bccf81f4`; a separate rkbin checkout is not +needed to build this repository. + +| Vendored file | Original rkbin path | Bytes | SHA-256 | +| --- | --- | ---: | --- | +| `rk3566_ddr_1056MHz_v1.25.bin` | `bin/rk35/rk3566_ddr_1056MHz_v1.25.bin` | 59,392 | `c2a1b37673bf03ed338bc39efbe942136459cb3621dad09351144d744d78db26` | +| `rk3568_ddr_1560MHz_v1.25.bin` | `bin/rk35/rk3568_ddr_1560MHz_v1.25.bin` | 59,392 | `ab1d9b822a256b6ef4b3aa54b911c4d1e0faaebc882403c7a6b3efc3e69e07fc` | +| `rk356x_usbplug_v1.17.bin` | `bin/rk35/rk356x_usbplug_v1.17.bin` | 98,708 | `4038b7857b840f539760decc0daf1601b8ff61cc17798101e93b11128a7f333e` | +| `rk3568_bl31_v1.46.elf` | `bin/rk35/rk3568_bl31_v1.46.elf` | 402,376 | `c81ac7e8e1fd727cf7f0db62a9aaea760bde2b270e34d98eb264a264b86df749` | + +The DDR blobs are used by `makeboot.out` and the direct `rock.out` 0x471 +loader flow. The shared USB-plug blob is used only by the optional +`xrock maskrom ... --rc4-off` helper targets. + +The BL31 ELF is linked only into optional RK3568 U-Boot FITs selected through +validated board manifests (currently YY3568 and ROCK 3A). It is not linked into +normal or demo firmware. diff --git a/img/rk3566_ddr_1056MHz_v1.25.bin b/img/rk3566_ddr_1056MHz_v1.25.bin new file mode 100644 index 0000000..c41ff86 Binary files /dev/null and b/img/rk3566_ddr_1056MHz_v1.25.bin differ diff --git a/img/rk3568_bl31_v1.46.elf b/img/rk3568_bl31_v1.46.elf new file mode 100644 index 0000000..72c9b03 Binary files /dev/null and b/img/rk3568_bl31_v1.46.elf differ diff --git a/img/rk3568_ddr_1560MHz_v1.25.bin b/img/rk3568_ddr_1560MHz_v1.25.bin new file mode 100644 index 0000000..6f8988e Binary files /dev/null and b/img/rk3568_ddr_1560MHz_v1.25.bin differ diff --git a/img/rk356x_usbplug_v1.17.bin b/img/rk356x_usbplug_v1.17.bin new file mode 100644 index 0000000..500993b Binary files /dev/null and b/img/rk356x_usbplug_v1.17.bin differ diff --git a/mkdocs.yml b/mkdocs.yml index efcf1cc..ea76714 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,34 @@ site_name: rk +nav: + - Home: index.md + - Getting started: intro.md + - Firmware and payloads: payloads.md + - RK356x: + - Device overview: rk356x/index.md + - Common bare-metal firmware: rk356x/bare-metal.md + - BL31/U-Boot chainloading: rk356x/chainloading.md + - Boards: + - ROC-RK3566-PC: rk356x/boards/roc3566.md + - YY3568: rk356x/boards/yy3568.md + - ROCK 3A: rk356x/boards/rock3a.md + - RK3399: + - Boot: rk3399/boot.md + - Display: rk3399/disp.md + - RK3588: + - Bring-up: rk3588/boot.md + - BootROM: rk3588/bootrom.md + - Clock: rk3588/clock.md + - Display: rk3588/disp.md + - HDMI: rk3588/hdmi.md + - Boards: + - Pinebook Pro: devices/pinebook.md + - Cool-Pi Genbook: devices/genbook.md + - Orange Pi 5: devices/orangepi.md + - Custom DDR image: custom_ddr_image.md + - Releases: releases.md + - References: ref.md + theme: name: readthedocs color_mode: dark diff --git a/src/chainload/chainload.h b/src/chainload/chainload.h new file mode 100644 index 0000000..eb43c71 --- /dev/null +++ b/src/chainload/chainload.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef RK_CHAINLOAD_H +#define RK_CHAINLOAD_H + +#include +#include + +#define CHAIN_MAX_IMAGES 8 +#define CHAIN_DTB_SLACK 8192U +#define CHAIN_PARAMS_RESERVE 1024U + +#ifdef _MSC_VER +#define CHAIN_NORETURN __declspec(noreturn) +#else +#define CHAIN_NORETURN __attribute__((noreturn)) +#endif + +enum ChainImageRole { + CHAIN_IMAGE_BL31, + CHAIN_IMAGE_BL33, + CHAIN_IMAGE_FDT, +}; + +enum ChainHandoffProtocol { + CHAIN_HANDOFF_TFA_V1_BL33_EL2 = 1, +}; + +struct ChainAddressRange { + uintptr_t start; + uintptr_t end; +}; + +struct ChainImage { + const char *name; + const void *data; + size_t size; + uintptr_t load; + uintptr_t entry; + const char *type; + const char *os; + enum ChainImageRole role; + uint8_t record_in_control_fdt; + uint8_t entry_explicit; +}; + +struct ChainFitPlan { + struct ChainImage images[CHAIN_MAX_IMAGES]; + unsigned int image_count; + unsigned int bl31_count; + unsigned int bl33_index; + unsigned int fdt_index; + uintptr_t bl31_entry; + uintptr_t bl33_entry; + uintptr_t control_fdt; + size_t control_fdt_capacity; +}; + +struct ChainPlatform { + const char *board; + const char *soc; + uintptr_t fit_stage_start; + uintptr_t fit_stage_end; + uintptr_t params_addr; + uintptr_t expected_bl31_entry; + uintptr_t expected_bl33_entry; + uintptr_t bl33_limit; + enum ChainHandoffProtocol handoff_protocol; + const struct ChainAddressRange *bl31_ranges; + unsigned int bl31_range_count; + unsigned int expected_bl31_segments; + const char *(*get_boot_source)(void); + int (*prepare_handoff)(void); + int (*range_is_cacheable)(uintptr_t start, uintptr_t end); + void (*recover)(const char *stage, const char *reason); +}; + +int chain_fit_parse(const void *fit, size_t available, + const struct ChainPlatform *platform, struct ChainFitPlan *plan, + const char **reason); +int chain_fit_load(const void *fit, struct ChainFitPlan *plan, + const char **reason); +void *chain_build_bl31_params(uintptr_t address, uintptr_t bl31_entry, + size_t bl31_size, uintptr_t bl33_entry, uintptr_t bl33_base, + size_t bl33_size); +CHAIN_NORETURN void chainload_run(const struct ChainPlatform *platform, + const void *appended_fit); +CHAIN_NORETURN void chain_jump_bl31(uintptr_t entry, void *params); + +#endif diff --git a/src/chainload/compat.c b/src/chainload/compat.c new file mode 100644 index 0000000..a6a17b8 --- /dev/null +++ b/src/chainload/compat.c @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include + +size_t strlen(const char *s) { + const char *p = s; + while (*p) + p++; + return (size_t)(p - s); +} + +size_t strnlen(const char *s, size_t maxlen) { + const char *p = s; + + while (maxlen-- && *p) + p++; + return (size_t)(p - s); +} + +void *memchr(const void *s_, int c, size_t len) { + const unsigned char *s = s_; + while (len--) { + if (*s == (unsigned char)c) + return (void *)s; + s++; + } + return NULL; +} + +void *memmove(void *dst_, const void *src_, size_t len) { + unsigned char *dst = dst_; + const unsigned char *src = src_; + if (dst < src) { + while (len--) + *dst++ = *src++; + } else if (dst > src) { + dst += len; + src += len; + while (len--) + *--dst = *--src; + } + return dst_; +} diff --git a/src/chainload/fit.c b/src/chainload/fit.c new file mode 100644 index 0000000..985ff16 --- /dev/null +++ b/src/chainload/fit.c @@ -0,0 +1,387 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include +#include + +#include "chainload.h" +#include "sha256.h" +#include "libfdt.h" + +static int string_equal(const char *a, const char *b) { + while (*a && *a == *b) { + a++; + b++; + } + return *a == *b; +} + +static const char *string_prop(const void *fit, int node, const char *name) { + int len; + const char *value = fdt_getprop(fit, node, name, &len); + if (!value || len < 1 || !memchr(value, 0, (size_t)len)) + return NULL; + return value; +} + +static int address_prop(const void *fit, int node, const char *name, + uintptr_t *value, int required) { + int len; + const fdt32_t *cells = fdt_getprop(fit, node, name, &len); + if (!cells) + return required ? -1 : 1; + if (len == 4) { + *value = fdt32_to_cpu(cells[0]); + return 0; + } + if (len == 8) { + uint64_t v = ((uint64_t)fdt32_to_cpu(cells[0]) << 32) | + fdt32_to_cpu(cells[1]); + if (v > UINTPTR_MAX) + return -1; + *value = (uintptr_t)v; + return 0; + } + return -1; +} + +static int checked_end(uintptr_t start, size_t size, uintptr_t *end) { + if (size > UINTPTR_MAX - start) + return -1; + *end = start + size; + return 0; +} + +static int in_range(uintptr_t start, uintptr_t end, + const struct ChainAddressRange *range) { + return start >= range->start && end > start && end <= range->end; +} + +static int bl31_address_allowed(const struct ChainPlatform *platform, + uintptr_t start, uintptr_t end) { + for (unsigned int i = 0; i < platform->bl31_range_count; i++) + if (in_range(start, end, &platform->bl31_ranges[i])) + return 1; + return 0; +} + +static int validate_hash(const void *fit, int image, const void *data, + size_t size) { + uint8_t digest[CHAIN_SHA256_SIZE]; + int hash; + int valid_hashes = 0; + fdt_for_each_subnode(hash, fit, image) { + const char *algo = string_prop(fit, hash, "algo"); + int len; + const uint8_t *expected; + if (!algo || !string_equal(algo, "sha256")) + continue; + expected = fdt_getprop(fit, hash, "value", &len); + if (!expected || len != CHAIN_SHA256_SIZE) + return -1; + chain_sha256(data, size, digest); + if (memcmp(expected, digest, sizeof(digest))) + return -1; + valid_hashes++; + } + return valid_hashes == 1 ? 0 : -1; +} + +static int add_image(const void *fit, int images, const char *name, + int primary, const struct ChainPlatform *platform, struct ChainFitPlan *plan, + const char **reason) { + struct ChainImage *out; + const char *arch, *compression, *type, *os; + const void *data; + uintptr_t end; + int node, len, entry_status; + + if (plan->image_count == CHAIN_MAX_IMAGES) { + *reason = "too many FIT images"; + return -1; + } + node = fdt_subnode_offset(fit, images, name); + if (node < 0) { + *reason = "configuration references a missing image"; + return -1; + } + if (fdt_getprop(fit, node, "data-position", NULL) || + fdt_getprop(fit, node, "data-offset", NULL)) { + *reason = "external FIT data is not supported"; + return -1; + } + arch = string_prop(fit, node, "arch"); + compression = string_prop(fit, node, "compression"); + type = string_prop(fit, node, "type"); + os = string_prop(fit, node, "os"); + if (!compression || !string_equal(compression, "none")) { + *reason = "FIT compression is not supported"; + return -1; + } + data = fdt_getprop(fit, node, "data", &len); + if (!data || len <= 0) { + *reason = "FIT image has no inline data"; + return -1; + } + if (validate_hash(fit, node, data, (size_t)len)) { + *reason = "FIT SHA-256 verification failed"; + return -1; + } + + out = &plan->images[plan->image_count]; + memset(out, 0, sizeof(*out)); + out->name = name; + out->data = data; + out->size = (size_t)len; + out->entry = UINTPTR_MAX; + out->type = type; + out->os = os; + out->record_in_control_fdt = primary ? 0 : 1; + entry_status = address_prop(fit, node, "entry", &out->entry, 0); + if (entry_status < 0) { + *reason = "invalid FIT entry point"; + return -1; + } + out->entry_explicit = entry_status == 0; + + if (type && string_equal(type, "flat_dt")) { + /* + * Mainline U-Boot's Rockchip binman template intentionally omits + * the architecture property from flat_dt images. A control DTB is + * data rather than executable code, so accept that canonical form; + * if an architecture is supplied, still reject a conflicting one. + */ + if (arch && !string_equal(arch, "arm64")) { + *reason = "control DTB has an incompatible architecture"; + return -1; + } + if (fdt_getprop(fit, node, "load", NULL) || + out->entry != UINTPTR_MAX) { + *reason = "control DTB must not carry a load or entry address"; + return -1; + } + out->role = CHAIN_IMAGE_FDT; + out->record_in_control_fdt = 0; + if (plan->fdt_index != UINT32_MAX) { + *reason = "multiple control DTBs are not supported"; + return -1; + } + plan->fdt_index = plan->image_count++; + return 0; + } + + /* BL31 and BL33 are executable AArch64 images. */ + if (!arch || !string_equal(arch, "arm64")) { + *reason = "FIT executable image is not ARM64"; + return -1; + } + + if (address_prop(fit, node, "load", &out->load, 1) || + checked_end(out->load, out->size, &end)) { + *reason = "invalid FIT load range"; + return -1; + } + if (os && (string_equal(os, "tee") || string_equal(os, "op-tee"))) { + *reason = "BL32/OP-TEE is not supported"; + return -1; + } + if (os && (string_equal(os, "U-Boot") || string_equal(os, "u-boot"))) { + if (!type || !string_equal(type, "standalone")) { + *reason = "BL33 is not a standalone U-Boot image"; + return -1; + } + if (out->entry == UINTPTR_MAX) + out->entry = out->load; + out->role = CHAIN_IMAGE_BL33; + if (plan->bl33_index != UINT32_MAX || + out->load != platform->expected_bl33_entry || + out->entry != platform->expected_bl33_entry || + end > platform->bl33_limit) { + *reason = "unsafe or duplicate BL33 image"; + return -1; + } + plan->bl33_index = plan->image_count; + plan->bl33_entry = out->entry; + } else if (os && string_equal(os, "arm-trusted-firmware")) { + if (!type || !string_equal(type, "firmware")) { + *reason = "BL31 segment is not firmware"; + return -1; + } + if (primary && out->entry == UINTPTR_MAX) + out->entry = out->load; + out->role = CHAIN_IMAGE_BL31; + if (!bl31_address_allowed(platform, out->load, end)) { + *reason = "BL31 segment violates board address policy"; + return -1; + } + if (out->entry != UINTPTR_MAX) { + if (out->entry != platform->expected_bl31_entry || plan->bl31_entry) { + *reason = "invalid BL31 entry point"; + return -1; + } + plan->bl31_entry = out->entry; + } + plan->bl31_count++; + } else { + *reason = "unsupported FIT operating system"; + return -1; + } + plan->image_count++; + return 0; +} + +static int images_overlap(const struct ChainImage *a, const struct ChainImage *b) { + uintptr_t a_end, b_end; + if (a->role == CHAIN_IMAGE_FDT || b->role == CHAIN_IMAGE_FDT) + return 0; + if (checked_end(a->load, a->size, &a_end) || checked_end(b->load, b->size, &b_end)) + return 1; + return a->load < b_end && b->load < a_end; +} + +int chain_fit_parse(const void *fit, size_t available, + const struct ChainPlatform *platform, struct ChainFitPlan *plan, + const char **reason) { + const char *name; + int configurations, configuration, images, count, len; + size_t total; + + *reason = "invalid FIT header"; + if (!fit || !platform || fdt_check_header(fit)) + return -1; + total = (size_t)fdt_totalsize(fit); + if (total > available || total < sizeof(struct fdt_header)) + return -1; + memset(plan, 0, sizeof(*plan)); + plan->bl33_index = UINT32_MAX; + plan->fdt_index = UINT32_MAX; + configurations = fdt_path_offset(fit, "/configurations"); + images = fdt_path_offset(fit, "/images"); + if (configurations < 0 || images < 0) { + *reason = "FIT lacks images or configurations"; + return -1; + } + name = string_prop(fit, configurations, "default"); + configuration = name ? fdt_subnode_offset(fit, configurations, name) : -1; + if (configuration < 0) { + *reason = "FIT has no valid default configuration"; + return -1; + } + name = fdt_stringlist_get(fit, configuration, "firmware", 0, &len); + if (!name || add_image(fit, images, name, 1, platform, plan, reason)) + return -1; + count = fdt_stringlist_count(fit, configuration, "loadables"); + if (count < 1) { + *reason = "FIT has no loadables"; + return -1; + } + for (int i = 0; i < count; i++) { + name = fdt_stringlist_get(fit, configuration, "loadables", i, &len); + if (!name || add_image(fit, images, name, 0, platform, plan, reason)) + return -1; + } + name = fdt_stringlist_get(fit, configuration, "fdt", 0, &len); + if (!name || add_image(fit, images, name, 0, platform, plan, reason)) + return -1; + if (!plan->bl31_count || !plan->bl31_entry || + plan->bl33_index == UINT32_MAX || plan->fdt_index == UINT32_MAX) { + *reason = "FIT is missing BL31, BL33, or its control DTB"; + return -1; + } + if (platform->expected_bl31_segments && + plan->bl31_count != platform->expected_bl31_segments) { + *reason = "unexpected number of split BL31 segments"; + return -1; + } + for (unsigned int i = 0; i < plan->image_count; i++) + for (unsigned int j = i + 1; j < plan->image_count; j++) + if (images_overlap(&plan->images[i], &plan->images[j])) { + *reason = "FIT load ranges overlap"; + return -1; + } + { + struct ChainImage *uboot = &plan->images[plan->bl33_index]; + struct ChainImage *dtb = &plan->images[plan->fdt_index]; + uintptr_t dtb_end, params_end; + plan->control_fdt = uboot->load + uboot->size; + plan->control_fdt_capacity = dtb->size + CHAIN_DTB_SLACK; + if (checked_end(plan->control_fdt, plan->control_fdt_capacity, &dtb_end) || + dtb_end > platform->bl33_limit) { + *reason = "U-Boot plus control DTB exceeds its initial stack limit"; + return -1; + } + if (checked_end(platform->params_addr, CHAIN_PARAMS_RESERVE, ¶ms_end)) { + *reason = "invalid BL31 parameter range"; + return -1; + } + for (unsigned int i = 0; i < plan->image_count; i++) { + uintptr_t image_end; + if (plan->images[i].role == CHAIN_IMAGE_FDT) + continue; + if (checked_end(plan->images[i].load, plan->images[i].size, &image_end) || + (plan->images[i].load < platform->fit_stage_end && + platform->fit_stage_start < image_end) || + (plan->images[i].load < params_end && + platform->params_addr < image_end)) { + *reason = "loaded image overlaps staging or BL31 parameters"; + return -1; + } + } + if ((plan->control_fdt < platform->fit_stage_end && + platform->fit_stage_start < dtb_end) || + (plan->control_fdt < params_end && platform->params_addr < dtb_end)) { + *reason = "control DTB overlaps staging or BL31 parameters"; + return -1; + } + } + *reason = NULL; + return 0; +} + +static int record_image(void *dtb, const struct ChainImage *image) { + int parent = fdt_subnode_offset(dtb, 0, "fit-images"); + int node; + if (parent == -FDT_ERR_NOTFOUND) + parent = fdt_add_subnode(dtb, 0, "fit-images"); + if (parent < 0) + return parent; + node = fdt_subnode_offset(dtb, parent, image->name); + if (node == -FDT_ERR_NOTFOUND) + node = fdt_add_subnode(dtb, parent, image->name); + if (node < 0) + return node; + if (fdt_setprop_u32(dtb, node, "load-addr", (uint32_t)image->load) || + fdt_setprop_u32(dtb, node, "size", (uint32_t)image->size)) + return -1; + if (image->entry_explicit && + fdt_setprop_u32(dtb, node, "entry-point", (uint32_t)image->entry)) + return -1; + if (image->type && fdt_setprop_string(dtb, node, "type", image->type)) + return -1; + if (image->os && fdt_setprop_string(dtb, node, "os", image->os)) + return -1; + return 0; +} + +int chain_fit_load(const void *fit, struct ChainFitPlan *plan, + const char **reason) { + (void)fit; + struct ChainImage *dtb = &plan->images[plan->fdt_index]; + for (unsigned int i = 0; i < plan->image_count; i++) { + struct ChainImage *image = &plan->images[i]; + if (image->role != CHAIN_IMAGE_FDT) + memcpy((void *)image->load, image->data, image->size); + } + if (fdt_open_into(dtb->data, (void *)plan->control_fdt, + (int)plan->control_fdt_capacity)) { + *reason = "cannot expand U-Boot control DTB"; + return -1; + } + for (unsigned int i = 0; i < plan->image_count; i++) { + if (plan->images[i].record_in_control_fdt && + record_image((void *)plan->control_fdt, &plan->images[i])) { + *reason = "cannot create /fit-images metadata"; + return -1; + } + } + *reason = NULL; + return 0; +} diff --git a/src/chainload/handoff.S b/src/chainload/handoff.S new file mode 100644 index 0000000..03e6f7b --- /dev/null +++ b/src/chainload/handoff.S @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +.global chain_jump_bl31 +.type chain_jump_bl31, %function +chain_jump_bl31: + /* x0 = BL31 entry, x1 = TF-A v1 parameter block. */ + mov x9, x0 + mov x10, x1 + msr DAIFSet, #0xf + dsb sy + ic iallu + dsb sy + isb + mrs x11, SCTLR_EL3 + bic x11, x11, #(1 << 0) + bic x11, x11, #(1 << 2) + bic x11, x11, #(1 << 12) + msr SCTLR_EL3, x11 + dsb sy + isb + mov x0, x10 + mov x1, xzr + br x9 + .size chain_jump_bl31, . - chain_jump_bl31 diff --git a/src/chainload/loader.c b/src/chainload/loader.c new file mode 100644 index 0000000..fa3a88e --- /dev/null +++ b/src/chainload/loader.c @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include +#include + +#include "chainload.h" +#include "libfdt.h" + +int puts(const char *text); +void debug(const char *text, uint64_t value); +void dcache_clean(uintptr_t start, uintptr_t end); + +static CHAIN_NORETURN void fail(const struct ChainPlatform *platform, + const char *stage, const char *reason) { + platform->recover(stage, reason); + for (;;) + ; +} + +void chainload_run(const struct ChainPlatform *platform, const void *appended_fit) { + struct ChainFitPlan plan; + const char *reason; + void *params; + size_t bl31_bytes = 0; + size_t stage_capacity; + size_t fit_size; + + if (platform->fit_stage_end <= platform->fit_stage_start) + fail(platform, "policy", "invalid FIT staging arena"); + if (platform->handoff_protocol != CHAIN_HANDOFF_TFA_V1_BL33_EL2) + fail(platform, "policy", "unsupported BL31/BL33 handoff protocol"); + stage_capacity = platform->fit_stage_end - platform->fit_stage_start; + puts("chainload: validating appended FIT"); + if (fdt_check_header(appended_fit)) + fail(platform, "stage", "invalid appended FIT header"); + fit_size = (size_t)fdt_totalsize(appended_fit); + if (fit_size < sizeof(struct fdt_header) || fit_size > stage_capacity) + fail(platform, "stage", "FIT exceeds board staging arena"); + memcpy((void *)platform->fit_stage_start, appended_fit, fit_size); + if (chain_fit_parse((void *)platform->fit_stage_start, fit_size, + platform, &plan, &reason)) + fail(platform, "validate", reason); + debug("chainload: FIT bytes ", fit_size); + debug("chainload: BL31 segments ", plan.bl31_count); + if (platform->prepare_handoff && platform->prepare_handoff()) + fail(platform, "prepare", "platform pre-BL31 preparation failed"); + if (chain_fit_load((void *)platform->fit_stage_start, &plan, &reason)) + fail(platform, "load", reason); + for (unsigned int i = 0; i < plan.image_count; i++) + if (plan.images[i].role == CHAIN_IMAGE_BL31) + bl31_bytes += plan.images[i].size; + params = chain_build_bl31_params(platform->params_addr, plan.bl31_entry, + bl31_bytes, plan.bl33_entry, + plan.images[plan.bl33_index].load, + plan.images[plan.bl33_index].size); + + for (unsigned int i = 0; i < plan.image_count; i++) { + uintptr_t end = plan.images[i].load + plan.images[i].size; + if (plan.images[i].role != CHAIN_IMAGE_FDT && + (!platform->range_is_cacheable || + platform->range_is_cacheable(plan.images[i].load, end))) + dcache_clean(plan.images[i].load, + end); + } + dcache_clean(plan.control_fdt, + plan.control_fdt + plan.control_fdt_capacity); + dcache_clean(platform->params_addr, + platform->params_addr + CHAIN_PARAMS_RESERVE); + puts("chainload: entering BL31"); + chain_jump_bl31(plan.bl31_entry, params); +} diff --git a/src/chainload/rk3568.c b/src/chainload/rk3568.c new file mode 100644 index 0000000..a8e3c9a --- /dev/null +++ b/src/chainload/rk3568.c @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include +#include + +#include "board_config.h" +#include "chainload.h" +#include "main.h" + +#define PMUGRF_BASE 0xfdc20000UL +#define CPU_GRF_BASE 0xfdc30000UL +#define GRF_BASE 0xfdc60000UL +#define USBPHY_U3_GRF 0xfdca0000UL +#define USBPHY_U2_GRF 0xfdca8000UL +#define PMUCRU_BASE 0xfdd00000UL +#define SGRF_BASE 0xfdd18000UL +#define CRU_BASE 0xfdd20000UL +#define PMU_BASE 0xfdd90000UL +#define UART2_BASE 0xfe660000UL +#define EBC_PRIORITY_REG 0xfe158008UL +#define BROM_BOOTSOURCE_ID_ADDR 0xfdcc0010UL +#define BROM_BOOTSOURCE_MASK 0x0fU + +extern char _fit_start[]; + +static uint64_t page_tables[3][512] __attribute__((aligned(4096))); + +static inline volatile uint32_t *reg(uintptr_t address) { + return (volatile uint32_t *)address; +} + +static inline void write32(uintptr_t address, uint32_t value) { + *reg(address) = value; +} + +volatile void *plat_get_uart_base(void) { + return (void *)UART2_BASE; +} + +void enable_uart(void) { + /* RK3568 UART2 M0: GPIO0_D0 RX, GPIO0_D1 TX, xin24m clock. */ + rk_clr_set_bits(reg(PMUGRF_BASE + 0x18), 2, 0, 1); + rk_clr_set_bits(reg(PMUGRF_BASE + 0x18), 6, 4, 1); + rk_clr_set_bits(reg(GRF_BASE + 0x30c), 11, 10, 0); + rk_clr_set_bits(reg(CRU_BASE + 0x100 + 54 * 4), 13, 12, 2); + rk_clr_set_bits(reg(CRU_BASE + 0x300 + 26 * 4), 1, 1, 0); + rk_clr_set_bits(reg(CRU_BASE + 0x300 + 28 * 4), 0, 0, 0); + rk_clr_set_bits(reg(CRU_BASE + 0x300 + 28 * 4), 3, 3, 0); + rk_clr_set_bits(reg(CRU_BASE + 0x400 + 25 * 4), 1, 0, 0); +} + +void plat_setup_mmu(void *unused) { + uint8_t *l1 = (uint8_t *)page_tables[0]; + uint8_t *low = (uint8_t *)page_tables[1]; + uint8_t *high = (uint8_t *)page_tables[2]; + (void)unused; + memset(page_tables, 0, sizeof(page_tables)); + ttbl_table_entry(l1, (uintptr_t)low); + ttbl_block_1gb(l1 + 8, 0x40000000, 3); + ttbl_block_1gb(l1 + 16, 0x80000000, 3); + ttbl_table_entry(l1 + 24, (uintptr_t)high); + for (unsigned int i = 0; i < 512; i++) + ttbl_block_2mb(low + i * 8, (uint64_t)i << 21, 3); + for (unsigned int i = 0; i < 512; i++) { + uint64_t address = 0xc0000000ULL + ((uint64_t)i << 21); + ttbl_block_2mb(high + i * 8, address, + address >= 0xf0000000ULL ? 0 : 3); + } + setup_tt_el3(0x3520, 0xeeff440400ULL, (uintptr_t)l1); + enable_mmu_el3(); +} + +static int wait_clear(uintptr_t address, uint32_t mask) { + for (unsigned int timeout = 0; timeout < 1000; timeout++) { + if (!(*reg(address) & mask)) + return 0; + usleep(1); + } + return -1; +} + +static int rk3568_prepare_handoff(void) { + /* Ported from U-Boot rk3568 arch_cpu_init()/qos_priority_init(). */ + write32(PMU_BASE + 0x70, 0xffffffff); + write32(PMU_BASE + 0x74, 0x000f000f); + write32(SGRF_BASE + 0x10, ((0x3U << 11 | 0x1U << 4) << 16)); + write32(CPU_GRF_BASE + 0x10, 0x00ff002b); + write32(CRU_BASE + 0x470, 0x02a002a0); + write32(USBPHY_U3_GRF + 0x04, 0x01ff01d1); + write32(USBPHY_U2_GRF + 0x00, 0x01ff01d1); + write32(USBPHY_U2_GRF + 0x04, 0x01ff01d1); + + /* Power every domain except GPU and NPU, then release their NOC idle. */ + write32(PMU_BASE + 0xa0, 0xfffc0000); + if (wait_clear(PMU_BASE + 0x98, 0xfffffffc)) + return -1; + write32(PMU_BASE + 0x50, 0xfff90000); + if (wait_clear(PMU_BASE + 0x60, 0xfffffff9) || + wait_clear(PMU_BASE + 0x68, 0xfffffff9)) + return -1; + write32(EBC_PRIORITY_REG, 0x303); + return 0; +} + +static int rk3568_range_is_cacheable(uintptr_t start, uintptr_t end) { + (void)start; + return end <= 0xf0000000UL; +} + +static const char *rk3568_boot_source(void) { + switch (*reg(BROM_BOOTSOURCE_ID_ADDR) & BROM_BOOTSOURCE_MASK) { + case 1: return "source=nand"; + case 2: return "source=emmc"; + case 3: return "source=spi-nor"; + case 4: return "source=spi-nand"; + case 5: return "source=sd"; + case 10: return "source=usb"; + default: return "source=unknown"; + } +} + +static void rk3568_recover(const char *stage, const char *reason) { + puts("chainload: fatal error"); + puts("board: " CHAIN_BOARD_NAME); + puts("stage:"); + puts(stage); + puts("reason:"); + puts(reason ? reason : "unknown error"); + /* BootROM download marker followed by the RK3568 global reset. */ + write32(PMUGRF_BASE + 0x200, 0xef08a53c); + __asm__ volatile("dsb sy"); + write32(CRU_BASE + 0xd4, 0xfdb9); + halt(); +} + +static const struct ChainAddressRange bl31_ranges[] = { + CHAIN_BL31_RANGE_INITIALIZER +}; + +_Static_assert(sizeof(bl31_ranges) / sizeof(bl31_ranges[0]) == + CHAIN_BL31_RANGE_COUNT, "generated BL31 range count mismatch"); + +static const struct ChainPlatform board_platform = { + .board = CHAIN_BOARD_NAME, + .soc = CHAIN_SOC_NAME, + .fit_stage_start = CHAIN_FIT_STAGE_START, + .fit_stage_end = CHAIN_FIT_STAGE_END, + .params_addr = CHAIN_PARAMS_ADDR, + .expected_bl31_entry = CHAIN_EXPECTED_BL31_ENTRY, + .expected_bl33_entry = CHAIN_EXPECTED_BL33_ENTRY, + .bl33_limit = CHAIN_BL33_LIMIT, + .handoff_protocol = CHAIN_HANDOFF_TFA_V1_BL33_EL2, + .bl31_ranges = bl31_ranges, + .bl31_range_count = CHAIN_BL31_RANGE_COUNT, + .expected_bl31_segments = CHAIN_EXPECTED_BL31_SEGMENTS, + .get_boot_source = rk3568_boot_source, + .prepare_handoff = rk3568_prepare_handoff, + .range_is_cacheable = rk3568_range_is_cacheable, + .recover = rk3568_recover, +}; + +void c_entry(void) { + asm_set_cnt_freq(24000000); + enable_uart(); + uart_init(1500000); + puts("rk chainloader"); + puts(board_platform.board); + puts(board_platform.soc); + puts(board_platform.get_boot_source()); + plat_setup_mmu(NULL); + chainload_run(&board_platform, _fit_start); +} diff --git a/src/chainload/sha256.c b/src/chainload/sha256.c new file mode 100644 index 0000000..c5ce5d4 --- /dev/null +++ b/src/chainload/sha256.c @@ -0,0 +1,120 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * SHA-256 implementation based on Olivier Gay's BSD-licensed implementation + * as carried by upstream U-Boot in lib/avb/libavb/avb_sha256.c. + * Copyright (C) 2005, 2007 Olivier Gay + */ +#include "sha256.h" + +#define ROR(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) +#define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z))) +#define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define S0(x) (ROR((x), 2) ^ ROR((x), 13) ^ ROR((x), 22)) +#define S1(x) (ROR((x), 6) ^ ROR((x), 11) ^ ROR((x), 25)) +#define G0(x) (ROR((x), 7) ^ ROR((x), 18) ^ ((x) >> 3)) +#define G1(x) (ROR((x), 17) ^ ROR((x), 19) ^ ((x) >> 10)) + +static const uint32_t initial[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +}; + +static const uint32_t constants[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +}; + +static void copy_bytes(uint8_t *dst, const uint8_t *src, size_t len) { + while (len--) + *dst++ = *src++; +} + +static void transform(struct ChainSha256 *ctx, const uint8_t *data) { + uint32_t w[64], a, b, c, d, e, f, g, h; + for (unsigned int i = 0; i < 16; i++) { + w[i] = ((uint32_t)data[i * 4] << 24) | + ((uint32_t)data[i * 4 + 1] << 16) | + ((uint32_t)data[i * 4 + 2] << 8) | data[i * 4 + 3]; + } + for (unsigned int i = 16; i < 64; i++) + w[i] = G1(w[i - 2]) + w[i - 7] + G0(w[i - 15]) + w[i - 16]; + a = ctx->h[0]; b = ctx->h[1]; c = ctx->h[2]; d = ctx->h[3]; + e = ctx->h[4]; f = ctx->h[5]; g = ctx->h[6]; h = ctx->h[7]; + for (unsigned int i = 0; i < 64; i++) { + uint32_t t1 = h + S1(e) + CH(e, f, g) + constants[i] + w[i]; + uint32_t t2 = S0(a) + MAJ(a, b, c); + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + ctx->h[0] += a; ctx->h[1] += b; ctx->h[2] += c; ctx->h[3] += d; + ctx->h[4] += e; ctx->h[5] += f; ctx->h[6] += g; ctx->h[7] += h; +} + +void chain_sha256_init(struct ChainSha256 *ctx) { + for (unsigned int i = 0; i < 8; i++) + ctx->h[i] = initial[i]; + ctx->total = 0; + ctx->used = 0; +} + +void chain_sha256_update(struct ChainSha256 *ctx, const void *data_, size_t len) { + const uint8_t *data = data_; + ctx->total += len; + while (len) { + size_t amount = 64 - ctx->used; + if (amount > len) + amount = len; + copy_bytes(ctx->block + ctx->used, data, amount); + ctx->used += amount; + data += amount; + len -= amount; + if (ctx->used == 64) { + transform(ctx, ctx->block); + ctx->used = 0; + } + } +} + +void chain_sha256_final(struct ChainSha256 *ctx, uint8_t digest[CHAIN_SHA256_SIZE]) { + uint64_t bits = ctx->total * 8; + ctx->block[ctx->used++] = 0x80; + if (ctx->used > 56) { + while (ctx->used < 64) + ctx->block[ctx->used++] = 0; + transform(ctx, ctx->block); + ctx->used = 0; + } + while (ctx->used < 56) + ctx->block[ctx->used++] = 0; + for (unsigned int i = 0; i < 8; i++) + ctx->block[56 + i] = bits >> (56 - i * 8); + transform(ctx, ctx->block); + for (unsigned int i = 0; i < 8; i++) { + digest[i * 4] = ctx->h[i] >> 24; + digest[i * 4 + 1] = ctx->h[i] >> 16; + digest[i * 4 + 2] = ctx->h[i] >> 8; + digest[i * 4 + 3] = ctx->h[i]; + } +} + +void chain_sha256(const void *data, size_t len, uint8_t digest[CHAIN_SHA256_SIZE]) { + struct ChainSha256 ctx; + chain_sha256_init(&ctx); + chain_sha256_update(&ctx, data, len); + chain_sha256_final(&ctx, digest); +} diff --git a/src/chainload/sha256.h b/src/chainload/sha256.h new file mode 100644 index 0000000..40fb330 --- /dev/null +++ b/src/chainload/sha256.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef RK_CHAINLOAD_SHA256_H +#define RK_CHAINLOAD_SHA256_H + +#include +#include + +#define CHAIN_SHA256_SIZE 32 + +struct ChainSha256 { + uint32_t h[8]; + uint64_t total; + size_t used; + uint8_t block[128]; +}; + +void chain_sha256_init(struct ChainSha256 *ctx); +void chain_sha256_update(struct ChainSha256 *ctx, const void *data, size_t len); +void chain_sha256_final(struct ChainSha256 *ctx, uint8_t digest[CHAIN_SHA256_SIZE]); +void chain_sha256(const void *data, size_t len, uint8_t digest[CHAIN_SHA256_SIZE]); + +#endif diff --git a/src/chainload/tf_a.c b/src/chainload/tf_a.c new file mode 100644 index 0000000..99ae558 --- /dev/null +++ b/src/chainload/tf_a.c @@ -0,0 +1,96 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * TF-A v1 parameter structures are derived from upstream U-Boot's + * include/atf_common.h (ARM Limited, Rockchip, and Theobroma Systems). + */ +#include +#include + +#include "chainload.h" + +uint64_t asm_get_mpidr(void); + +#define ATF_PARAM_EP 0x01 +#define ATF_PARAM_IMAGE_BINARY 0x02 +#define ATF_PARAM_BL31 0x03 +#define ATF_VERSION_1 0x01 +#define ATF_EP_NON_SECURE 0x01 +#define SPSR_EL2H_MASKED 0x3c9U + +struct ParamHeader { + uint8_t type; + uint8_t version; + uint16_t size; + uint32_t attr; +}; + +struct Aapcs64Params { + unsigned long arg[8]; +}; + +struct EntryPointInfo { + struct ParamHeader h; + uintptr_t pc; + uint32_t spsr; + uint32_t pad; + struct Aapcs64Params args; +}; + +struct ImageInfo { + struct ParamHeader h; + uintptr_t image_base; + uint32_t image_size; + uint32_t pad; +}; + +struct Bl31Params { + struct ParamHeader h; + struct ImageInfo *bl31_image_info; + struct EntryPointInfo *bl32_ep_info; + struct ImageInfo *bl32_image_info; + struct EntryPointInfo *bl33_ep_info; + struct ImageInfo *bl33_image_info; +}; + +struct Bl31ParamMemory { + struct Bl31Params params; + struct ImageInfo bl31_image; + struct ImageInfo bl33_image; + struct EntryPointInfo bl33_ep; +}; + +static void header(struct ParamHeader *h, uint8_t type, uint16_t size, + uint32_t attr) { + h->type = type; + h->version = ATF_VERSION_1; + h->size = size; + h->attr = attr; +} + +void *chain_build_bl31_params(uintptr_t address, uintptr_t bl31_entry, + size_t bl31_size, uintptr_t bl33_entry, uintptr_t bl33_base, + size_t bl33_size) { + struct Bl31ParamMemory *memory = (void *)address; + memset(memory, 0, sizeof(*memory)); + header(&memory->params.h, ATF_PARAM_BL31, sizeof(memory->params), 0); + header(&memory->bl31_image.h, ATF_PARAM_IMAGE_BINARY, + sizeof(memory->bl31_image), 0); + header(&memory->bl33_image.h, ATF_PARAM_IMAGE_BINARY, + sizeof(memory->bl33_image), 0); + header(&memory->bl33_ep.h, ATF_PARAM_EP, sizeof(memory->bl33_ep), + ATF_EP_NON_SECURE); + memory->bl31_image.image_base = bl31_entry; + memory->bl31_image.image_size = (uint32_t)bl31_size; + memory->bl33_image.image_base = bl33_base; + memory->bl33_image.image_size = (uint32_t)bl33_size; + memory->bl33_ep.pc = bl33_entry; + memory->bl33_ep.spsr = SPSR_EL2H_MASKED; + memory->bl33_ep.args.arg[0] = asm_get_mpidr() & 0xffff; + memory->params.bl31_image_info = &memory->bl31_image; + memory->params.bl32_ep_info = NULL; + memory->params.bl32_image_info = NULL; + memory->params.bl33_ep_info = &memory->bl33_ep; + memory->params.bl33_image_info = &memory->bl33_image; + return &memory->params; +} diff --git a/src/edid.c b/src/edid.c new file mode 100644 index 0000000..6e62e69 --- /dev/null +++ b/src/edid.c @@ -0,0 +1,184 @@ +#include +#include "edid.h" + +struct VicMode { + uint8_t vic; + struct VideoMode mode; +}; + +#define MODE(v, p, ha, hf, hs, hb, va, vf, vs, vb, r, f) \ + { v, { p, ha, hf, hs, hb, va, vf, vs, vb, r, f } } + +static const struct VicMode vic_modes[] = { + MODE(1, 25175, 640, 16, 96, 48, 480, 10, 2, 33, 60, 0), + MODE(2, 27000, 720, 16, 62, 60, 480, 9, 6, 30, 60, 0), + MODE(4, 74250, 1280, 110, 40, 220, 720, 5, 5, 20, 60, + VIDEO_FLAG_HSYNC_HIGH | VIDEO_FLAG_VSYNC_HIGH), + MODE(16, 148500, 1920, 88, 44, 148, 1080, 4, 5, 36, 60, + VIDEO_FLAG_HSYNC_HIGH | VIDEO_FLAG_VSYNC_HIGH), + MODE(19, 74250, 1280, 440, 40, 220, 720, 5, 5, 20, 50, + VIDEO_FLAG_HSYNC_HIGH | VIDEO_FLAG_VSYNC_HIGH), + MODE(31, 148500, 1920, 528, 44, 148, 1080, 4, 5, 36, 50, + VIDEO_FLAG_HSYNC_HIGH | VIDEO_FLAG_VSYNC_HIGH), + MODE(32, 74250, 1920, 638, 44, 148, 1080, 4, 5, 36, 24, + VIDEO_FLAG_HSYNC_HIGH | VIDEO_FLAG_VSYNC_HIGH), + MODE(33, 74250, 1920, 528, 44, 148, 1080, 4, 5, 36, 25, + VIDEO_FLAG_HSYNC_HIGH | VIDEO_FLAG_VSYNC_HIGH), + MODE(34, 74250, 1920, 88, 44, 148, 1080, 4, 5, 36, 30, + VIDEO_FLAG_HSYNC_HIGH | VIDEO_FLAG_VSYNC_HIGH), + MODE(93, 297000, 3840, 1276, 88, 296, 2160, 8, 10, 72, 24, + VIDEO_FLAG_HSYNC_HIGH | VIDEO_FLAG_VSYNC_HIGH), + MODE(94, 297000, 3840, 1056, 88, 296, 2160, 8, 10, 72, 25, + VIDEO_FLAG_HSYNC_HIGH | VIDEO_FLAG_VSYNC_HIGH), + MODE(95, 297000, 3840, 176, 88, 296, 2160, 8, 10, 72, 30, + VIDEO_FLAG_HSYNC_HIGH | VIDEO_FLAG_VSYNC_HIGH), +}; + +int edid_block_valid(const uint8_t block[EDID_BLOCK_SIZE]) { + uint8_t sum = 0; + for (unsigned int i = 0; i < EDID_BLOCK_SIZE; i++) + sum = (uint8_t)(sum + block[i]); + return sum == 0; +} + +static int mode_supported(const struct VideoMode *m) { + if (!m->hactive || !m->vactive || !m->pixel_clock_khz) + return 0; + if (m->hactive > 3840 || m->vactive > 2160 || m->pixel_clock_khz > 297000) + return 0; + if (m->refresh_hz > 60) + return 0; + if ((m->hactive > 1920 || m->vactive > 1080) && m->refresh_hz > 30) + return 0; + return 1; +} + +static int parse_dtd(const uint8_t *d, struct VideoMode *m) { + uint32_t hblank, vblank, htotal, vtotal; + if (!d[0] && !d[1]) + return 0; + memset(m, 0, sizeof(*m)); + m->pixel_clock_khz = ((uint32_t)d[1] << 8 | d[0]) * 10; + m->hactive = d[2] | ((uint16_t)(d[4] & 0xf0) << 4); + hblank = d[3] | ((uint16_t)(d[4] & 0x0f) << 8); + m->vactive = d[5] | ((uint16_t)(d[7] & 0xf0) << 4); + vblank = d[6] | ((uint16_t)(d[7] & 0x0f) << 8); + m->hfront_porch = d[8] | ((uint16_t)(d[11] & 0xc0) << 2); + m->hsync_len = d[9] | ((uint16_t)(d[11] & 0x30) << 4); + m->vfront_porch = (d[10] >> 4) | ((uint16_t)(d[11] & 0x0c) << 2); + m->vsync_len = (d[10] & 0x0f) | ((uint16_t)(d[11] & 0x03) << 4); + if (hblank < (uint32_t)m->hfront_porch + m->hsync_len || + vblank < (uint32_t)m->vfront_porch + m->vsync_len || vblank > 255 || + m->vfront_porch > 255 || m->vsync_len > 255 || (d[17] & 0x80)) + return 0; + m->hback_porch = (uint16_t)(hblank - m->hfront_porch - m->hsync_len); + m->vback_porch = (uint16_t)(vblank - m->vfront_porch - m->vsync_len); + if ((d[17] & 0x18) == 0x18) { + if (d[17] & 0x02) m->flags |= VIDEO_FLAG_HSYNC_HIGH; + if (d[17] & 0x04) m->flags |= VIDEO_FLAG_VSYNC_HIGH; + } + htotal = m->hactive + hblank; + vtotal = m->vactive + vblank; + m->refresh_hz = (uint16_t)(((uint64_t)m->pixel_clock_khz * 1000 + + (uint64_t)htotal * vtotal / 2) / ((uint64_t)htotal * vtotal)); + return mode_supported(m); +} + +static int vic_mode(uint8_t vic, struct VideoMode *mode) { + for (unsigned int i = 0; i < sizeof(vic_modes) / sizeof(vic_modes[0]); i++) { + if (vic_modes[i].vic == vic) { + *mode = vic_modes[i].mode; + return 1; + } + } + return 0; +} + +static uint64_t mode_score(const struct VideoMode *m) { + return (uint64_t)m->hactive * m->vactive * 100 + m->refresh_hz; +} + +void edid_fallback_mode(struct VideoMode *mode) { + (void)vic_mode(4, mode); +} + +int edid_select_mode(const uint8_t *edid, unsigned int blocks, + struct VideoMode *mode) { + static const uint8_t header[8] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; + struct VideoMode candidate, best = { 0 }; + struct VideoMode native_best = { 0 }; + uint64_t best_score = 0; + uint64_t native_score = 0; + + if (!edid || !mode || !blocks || blocks > EDID_MAX_BLOCKS || + memcmp(edid, header, sizeof(header)) || !edid_block_valid(edid)) + return 0; + + /* The first base-block DTD is the EDID preferred timing. */ + if (parse_dtd(edid + 54, &candidate)) { + *mode = candidate; + return 1; + } + for (unsigned int off = 72; off <= 108; off += 18) { + if (parse_dtd(edid + off, &candidate) && mode_score(&candidate) > best_score) { + best = candidate; + best_score = mode_score(&candidate); + } + } + + for (unsigned int b = 1; b < blocks; b++) { + const uint8_t *ext = edid + b * EDID_BLOCK_SIZE; + unsigned int end; + unsigned int native_dtds; + if (!edid_block_valid(ext) || ext[0] != 0x02) + continue; + end = ext[2] ? ext[2] : 127; + if (end < 4 || end > 127) + continue; + native_dtds = ext[3] & 0x0f; + for (unsigned int i = 4; i < end;) { + unsigned int tag = ext[i] >> 5; + unsigned int len = ext[i] & 0x1f; + if (i + len >= 127) break; + if (tag == 2) { + for (unsigned int j = 1; j <= len; j++) { + uint8_t svd = ext[i + j]; + if (vic_mode(svd & 0x7f, &candidate)) { + if ((svd & 0x80) && mode_supported(&candidate)) { + if (mode_score(&candidate) > native_score) { + native_best = candidate; + native_score = mode_score(&candidate); + } + } + if (mode_supported(&candidate) && mode_score(&candidate) > best_score) { + best = candidate; + best_score = mode_score(&candidate); + } + } + } + } + i += len + 1; + } + for (unsigned int off = end, dtd_index = 0; + off + 17 < 127; off += 18, dtd_index++) { + if (parse_dtd(ext + off, &candidate)) { + uint64_t score = mode_score(&candidate); + if (dtd_index < native_dtds && score > native_score) { + native_best = candidate; + native_score = score; + } else if (dtd_index >= native_dtds && score > best_score) { + best = candidate; + best_score = score; + } + } + } + } + if (native_score) { + *mode = native_best; + return 1; + } + if (!best_score) + return 0; + *mode = best; + return 1; +} diff --git a/src/edid.h b/src/edid.h new file mode 100644 index 0000000..50e8971 --- /dev/null +++ b/src/edid.h @@ -0,0 +1,31 @@ +#ifndef RK_EDID_H +#define RK_EDID_H + +#include + +#define EDID_BLOCK_SIZE 128 +#define EDID_MAX_BLOCKS 5 + +#define VIDEO_FLAG_HSYNC_HIGH (1u << 0) +#define VIDEO_FLAG_VSYNC_HIGH (1u << 1) + +struct VideoMode { + uint32_t pixel_clock_khz; + uint16_t hactive; + uint16_t hfront_porch; + uint16_t hsync_len; + uint16_t hback_porch; + uint16_t vactive; + uint16_t vfront_porch; + uint16_t vsync_len; + uint16_t vback_porch; + uint16_t refresh_hz; + uint16_t flags; +}; + +int edid_block_valid(const uint8_t block[EDID_BLOCK_SIZE]); +int edid_select_mode(const uint8_t *edid, unsigned int blocks, + struct VideoMode *mode); +void edid_fallback_mode(struct VideoMode *mode); + +#endif diff --git a/src/firmware.c b/src/firmware.c index 0684b7d..6b57ea1 100644 --- a/src/firmware.c +++ b/src/firmware.c @@ -3,10 +3,22 @@ #include #include "main.h" #include "firmware.h" +#include "input.h" // Memory shared between EL2/EL3 static uint8_t *shared_mem; +__attribute__((weak)) void plat_get_screen(uint32_t *width, uint32_t *height, + uint32_t *stride) { + if (plat_get_framebuffer()) { + *width = 1920; + *height = 1080; + *stride = 1920 * 4; + } else { + *width = *height = *stride = 0; + } +} + uint64_t smc_handler(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4) { return process_firmware_call(p1, p2, p3, p4); } @@ -14,8 +26,10 @@ uint64_t smc_handler(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4) { static void bsod(void) { // No font is included in this minimal binary, so just fill the screen with blue uint32_t *fb = (void *)plat_get_framebuffer(); - if (fb != NULL) { - for (int i = 0; i < (1080 * 1920); i++) { + uint32_t width = 0, height = 0, stride = 0; + plat_get_screen(&width, &height, &stride); + if (fb != NULL && width && height && stride) { + for (uint64_t i = 0; i < ((uint64_t)stride / 4) * height; i++) { fb[i] = 0x0000FF; } } @@ -45,8 +59,11 @@ uint64_t process_firmware_call(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p } } return 0; case FU_GET_CHAR: + input_poll(); + return input_get_char(); case FU_POLL_CHAR: - return 0; + input_poll(); + return input_available(); } uint64_t rc = plat_process_firmware_call(p1, p2, p3, p4); diff --git a/src/firmware.h b/src/firmware.h index 5cb1c0f..c46243a 100644 --- a/src/firmware.h +++ b/src/firmware.h @@ -105,6 +105,9 @@ struct __attribute__((packed)) FuScreenList { }screens[]; }; +// FuScreenList.type values. Existing structure layout is unchanged. +#define FU_SCREEN_XRGB8888 1 + // Map to any type #define FU_MEM_ATTR_UNUSED (1 << 0) // Map to normal memory diff --git a/src/genbook.c b/src/genbook.c index fc7be6e..90a2079 100644 --- a/src/genbook.c +++ b/src/genbook.c @@ -16,6 +16,7 @@ uint64_t plat_process_firmware_call(uint64_t p1, uint64_t p2, uint64_t p3, uint6 switch (p1) { case FU_GET_SCREEN_LIST: screens->length = 1; + screens->type = FU_SCREEN_XRGB8888; screens->screens[0].framebuffer_addr = plat_get_framebuffer(); screens->screens[0].width = 1920; screens->screens[0].height = 1080; diff --git a/src/hid_keyboard.c b/src/hid_keyboard.c new file mode 100644 index 0000000..edce8f4 --- /dev/null +++ b/src/hid_keyboard.c @@ -0,0 +1,76 @@ +#include +#include "hid_keyboard.h" +#include "input.h" + +static uint8_t previous[8]; +static int caps_lock; + +static int was_pressed(uint8_t usage) { + for (int i = 2; i < 8; i++) + if (previous[i] == usage) + return 1; + return 0; +} + +static uint8_t translate(uint8_t usage, int shift) { + static const char plain[] = "1234567890-=[]\\;\'`,./"; + static const char shifted[] = "!@#$%^&*()_+{}|:\"~<>?"; + + if (usage >= 0x04 && usage <= 0x1d) { + int upper = shift ^ caps_lock; + return (uint8_t)((upper ? 'A' : 'a') + usage - 0x04); + } + if ((usage >= 0x1e && usage <= 0x27) || + (usage >= 0x2d && usage <= 0x38)) { + unsigned int index; + if (usage <= 0x27) + index = usage - 0x1e; + else + index = 10 + usage - 0x2d; + if (index < sizeof(plain) - 1) + return (uint8_t)(shift ? shifted[index] : plain[index]); + } + switch (usage) { + case 0x28: return '\r'; + case 0x29: return 0x1b; + case 0x2a: return '\b'; + case 0x2b: return '\t'; + case 0x2c: return ' '; + default: return 0; + } +} + +void hid_keyboard_reset(void) { + memset(previous, 0, sizeof(previous)); + caps_lock = 0; +} + +void hid_keyboard_report(const uint8_t report[8]) { + int shift = (report[0] & 0x22) != 0; + + /* ErrorRollOver, POSTFail and ErrorUndefined invalidate the report. */ + for (int i = 2; i < 8; i++) + if (report[i] >= 1 && report[i] <= 3) + return; + + /* Ctrl, Alt and GUI-modified input has no printable firmware mapping. */ + if (report[0] & 0xdd) { + memcpy(previous, report, sizeof(previous)); + return; + } + + for (int i = 2; i < 8; i++) { + uint8_t usage = report[i]; + uint8_t c; + if (!usage || was_pressed(usage)) + continue; + if (usage == 0x39) { + caps_lock = !caps_lock; + continue; + } + c = translate(usage, shift); + if (c) + input_enqueue(c); + } + memcpy(previous, report, sizeof(previous)); +} diff --git a/src/hid_keyboard.h b/src/hid_keyboard.h new file mode 100644 index 0000000..8f86a9c --- /dev/null +++ b/src/hid_keyboard.h @@ -0,0 +1,9 @@ +#ifndef RK_HID_KEYBOARD_H +#define RK_HID_KEYBOARD_H + +#include + +void hid_keyboard_reset(void); +void hid_keyboard_report(const uint8_t report[8]); + +#endif diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..85ecfbf --- /dev/null +++ b/src/input.c @@ -0,0 +1,44 @@ +#include "input.h" + +#define INPUT_QUEUE_SIZE 64 + +static uint8_t queue[INPUT_QUEUE_SIZE]; +static unsigned int read_pos; +static unsigned int write_pos; +static input_poll_fn *poll_fn; + +void input_reset(void) { + read_pos = 0; + write_pos = 0; +} + +void input_set_poller(input_poll_fn *poller) { + poll_fn = poller; +} + +void input_poll(void) { + if (poll_fn) + poll_fn(); +} + +int input_enqueue(uint8_t c) { + unsigned int next = (write_pos + 1) % INPUT_QUEUE_SIZE; + if (!c || next == read_pos) + return 0; + queue[write_pos] = c; + write_pos = next; + return 1; +} + +int input_available(void) { + return read_pos != write_pos; +} + +uint8_t input_get_char(void) { + uint8_t c; + if (read_pos == write_pos) + return 0; + c = queue[read_pos]; + read_pos = (read_pos + 1) % INPUT_QUEUE_SIZE; + return c; +} diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..d18a301 --- /dev/null +++ b/src/input.h @@ -0,0 +1,15 @@ +#ifndef RK_INPUT_H +#define RK_INPUT_H + +#include + +typedef void input_poll_fn(void); + +void input_reset(void); +void input_set_poller(input_poll_fn *poller); +void input_poll(void); +int input_enqueue(uint8_t c); +int input_available(void); +uint8_t input_get_char(void); + +#endif diff --git a/src/lib.c b/src/lib.c index dbc8d60..cc34711 100644 --- a/src/lib.c +++ b/src/lib.c @@ -128,6 +128,20 @@ void *memcpy(void *dest, const void *src, long unsigned int count) { return dest; } +int memcmp(const void *left, const void *right, long unsigned int count) { + const unsigned char *a = left; + const unsigned char *b = right; + + while (count--) { + if (*a != *b) + return *a - *b; + a++; + b++; + } + + return 0; +} + char *strcpy(char *dst, const char *src) { char *d = dst; while ((*d++ = *src++)); diff --git a/src/main.h b/src/main.h index ff4a8b0..43b5d03 100644 --- a/src/main.h +++ b/src/main.h @@ -1,7 +1,9 @@ #ifndef PINE_BOOT #define PINE_BOOT +#ifndef STACK_TOP #define STACK_TOP 0xc0000000 +#endif #define STACK_SIZE 0x10000 // Stack pointer for EL2 payload (TODO: may conflict with firmware stack) #define STACK2_TOP (STACK_TOP - (STACK_SIZE / 2)) @@ -22,6 +24,9 @@ void plat_get_mem_map(void *buffer); /// Get address for where framebuffer should be stored (should be setup as noncache memory) uintptr_t plat_get_framebuffer(void); +/// Return the active framebuffer geometry, or zero when no screen is active. +void plat_get_screen(uint32_t *width, uint32_t *height, uint32_t *stride); + /// Function that implements platform-specific firmware calls uint64_t plat_process_firmware_call(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4); diff --git a/src/ohci.c b/src/ohci.c index 1186b41..0bbd1b6 100644 --- a/src/ohci.c +++ b/src/ohci.c @@ -2,308 +2,502 @@ #include #include "main.h" #include "usb.h" - -// TODO: Remove hardcoded magic address -static uintptr_t noncache_memory_start = 0xf0000000; - -// HostControllerFunctionalState -#define USBRESET 0b00 -#define USBRESUME 0b01 -#define USBOPERATIONAL 0b10 -#define USBSUSPEND 0b11 - -struct __attribute__((packed)) OhciHC { - uint32_t revision; - uint32_t control; - uint32_t cmdstatus; - uint32_t intrstatus; - uint32_t intrenable; - uint32_t intrdisable; - uint32_t hcca; - uint32_t ed_periodcurrent; - uint32_t ed_controlhead; - uint32_t ed_controlcurrent; - uint32_t ed_bulkhead; - uint32_t ed_bulkcurrent; - uint32_t donehead; - uint32_t fminterval; - uint32_t fmremaining; - uint32_t fmnumber; - uint32_t periodicstart; - uint32_t lsthresh; - uint32_t desc_a; - uint32_t desc_b; - uint32_t status; - uint32_t portstatus [15]; +#include "ohci.h" +#include "hid_keyboard.h" + +#define MAX_CONTROLLERS 2 +#define MAX_CONFIG_SIZE 512 +#define BIT(x) (1U << (x)) +#define TD_CC 0xf0000000U +#define TD_DATA0 0x02000000U +#define TD_DATA1 0x03000000U +#define TD_ROUND 0x00040000U +#define TD_SETUP 0x00000000U +#define TD_OUT 0x00080000U +#define TD_IN 0x00100000U +#define ED_LOWSPEED (1U << 13) +#define ED_SKIP (1U << 14) + +#define CTRL_PLE (1U << 2) +#define CTRL_CLE (1U << 4) +#define CTRL_HCFS (3U << 6) +#define CTRL_OPERATIONAL (2U << 6) +#define CMD_RESET (1U << 0) +#define CMD_CLF (1U << 1) + +struct OhciRegs { + uint32_t revision, control, command_status, interrupt_status; + uint32_t interrupt_enable, interrupt_disable, hcca; + uint32_t period_current_ed, control_head_ed, control_current_ed; + uint32_t bulk_head_ed, bulk_current_ed, done_head; + uint32_t frame_interval, frame_remaining, frame_number; + uint32_t periodic_start, ls_threshold, rh_descriptor_a; + uint32_t rh_descriptor_b, rh_status, rh_port_status[15]; }; -struct __attribute__((packed)) OhciHcca { - uint32_t int_table[32]; - uint32_t frame_no; +struct OhciHcca { + uint32_t interrupt_table[32]; + uint16_t frame_number; + uint16_t pad; uint32_t done_head; + uint8_t reserved[120]; +} __attribute__((packed, aligned(256))); + +struct OhciEd { + uint32_t info, tail, head, next; +} __attribute__((aligned(16))); + +struct OhciTd { + uint32_t info, cbp, next, be; +} __attribute__((aligned(16))); + +struct ControllerDma { + struct OhciHcca hcca; + struct OhciEd control_ed; + struct OhciTd control_td[4]; + struct OhciEd interrupt_ed; + struct OhciTd interrupt_td; + struct OhciTd interrupt_tail; + uint8_t setup[8] __attribute__((aligned(16))); + uint8_t data[MAX_CONFIG_SIZE] __attribute__((aligned(16))); + uint8_t report[8] __attribute__((aligned(16))); +} __attribute__((aligned(256))); + +struct Controller { + volatile struct OhciRegs *regs; + struct ControllerDma *dma; + uint64_t deadline; + uint64_t retry_after; + uint8_t address; + uint8_t port; + uint8_t enum_state; + uint8_t configured; + uint8_t low_speed; + uint8_t control_packet; + uint8_t endpoint; + uint8_t interval; + uint16_t max_packet; + uint8_t interface_number; }; -struct __attribute__((packed)) OhciTD { - uint32_t info; - uint32_t cbp; // current buffer pointer - uint32_t next; - uint32_t be; // buffer end +enum EnumState { + ENUM_SCAN, + ENUM_POWER_WAIT, + ENUM_RESET_WAIT, + ENUM_GET_DEVICE, + ENUM_SET_ADDRESS, + ENUM_ADDRESS_WAIT, + ENUM_GET_DEVICE_FULL, + ENUM_GET_CONFIG_HEAD, + ENUM_GET_CONFIG, + ENUM_SET_CONFIG, + ENUM_SET_PROTOCOL, + ENUM_SET_IDLE, }; -struct __attribute__((packed)) OhciED { - uint32_t info; - uint32_t tail; - uint32_t head; - uint32_t next; -}; +static uintptr_t dma_start, dma_cursor, dma_end; +static struct Controller controllers[MAX_CONTROLLERS]; +static unsigned int controller_count; -#define OHCI_CTRL_CBSR (3 << 0) /* control/bulk service ratio */ -#define OHCI_CTRL_PLE (1 << 2) /* periodic list enable */ -#define OHCI_CTRL_IE (1 << 3) /* isochronous enable */ -#define OHCI_CTRL_CLE (1 << 4) /* control list enable */ -#define OHCI_CTRL_BLE (1 << 5) /* bulk list enable */ -#define OHCI_CTRL_HCFS (3 << 6) /* host controller functional state */ -#define OHCI_CTRL_IR (1 << 8) /* interrupt routing */ -#define OHCI_CTRL_RWC (1 << 9) /* remote wakeup connected */ -#define OHCI_CTRL_RWE (1 << 10) /* remote wakeup enable */ - -/* pre-shifted values for HCFS */ -#define OHCI_USB_RESET (0 << 6) -#define OHCI_USB_RESUME (1 << 6) -#define OHCI_USB_OPER (2 << 6) -#define OHCI_USB_SUSPEND (3 << 6) - -#define HCFS_USB_OPERATIONAL (USBOPERATIONAL << 6) -#define HCFS_PLE (1 << 2) -#define HCFS_IE (1 << 3) -#define HCFS_CLE (1 << 4) -#define HCFS_BLE (1 << 5) - -#define OHCI_INTR_SO (1 << 0) /* scheduling overrun */ -#define OHCI_INTR_WDH (1 << 1) /* writeback of done_head */ -#define OHCI_INTR_SF (1 << 2) /* start frame */ -#define OHCI_INTR_RD (1 << 3) /* resume detect */ -#define OHCI_INTR_UE (1 << 4) /* unrecoverable error */ -#define OHCI_INTR_FNO (1 << 5) /* frame number overflow */ -#define OHCI_INTR_RHSC (1 << 6) /* root hub status change */ -#define OHCI_INTR_OC (1 << 30) /* ownership change */ -#define OHCI_INTR_MIE (1 << 31) /* master interrupt enable */ - -static inline void *ptr32(uint32_t x) { - return (void *)(uintptr_t)x; +void ohci_dma_configure(uintptr_t start, uintptr_t end) { + dma_start = dma_cursor = start; + dma_end = end; + controller_count = 0; + memset(controllers, 0, sizeof(controllers)); } -uint32_t usb_alloc(int size, int alignment) { - uint32_t new = (noncache_memory_start + alignment - 1) & ~(alignment - 1); // alignment trick - noncache_memory_start = new + size; - memset((void *)((uintptr_t)new), 0x0, size); - return new; +void *ohci_dma_alloc(unsigned long size, unsigned long alignment) { + uintptr_t address; + if (!alignment || (alignment & (alignment - 1))) + return 0; + address = (dma_cursor + alignment - 1) & + ~((uintptr_t)alignment - 1); + if (address < dma_cursor || address > dma_end || size > dma_end - address) + return 0; + dma_cursor = address + size; + memset((void *)address, 0, size); + return (void *)address; } -int interrupt_handler(volatile struct OhciHC *ohci) { - uint32_t intr = ohci->intrstatus; - - if (intr & OHCI_INTR_UE) { - puts("! Unrecoverable error"); - abort(); - } - if (intr & OHCI_INTR_RHSC) { - puts("! Root hub status changed"); - } - if (intr & OHCI_INTR_WDH) { - ohci->intrdisable = OHCI_INTR_WDH; - - volatile struct OhciHcca *hcca = (volatile struct OhciHcca *)(uintptr_t)ohci->hcca; - debug("Donehead: ", hcca->done_head); - - struct OhciTD *td = (struct OhciTD *)(uintptr_t)hcca->done_head; - if (((td->info >> 24) & 0b11) != 0) { // check data toggle bits (T) - debug("Successful transaction: ", td->info); - } - - puts("! Processing finished"); - ohci->intrenable = OHCI_INTR_WDH; - - ohci->intrstatus = intr; - - return 1; - } +int ohci_dma_contains(uintptr_t address, unsigned long size) { + return address >= dma_start && address <= dma_end && size <= dma_end - address; +} - ohci->intrstatus = intr; +static uint32_t ptr(const void *p) { + return (uint32_t)(uintptr_t)p; +} +static int wait_clear(volatile uint32_t *value, uint32_t mask, + unsigned int timeout_ms) { + uint64_t limit = asm_get_cpu_timer() + (uint64_t)timeout_ms * 1000; + while (*value & mask) + if (asm_get_cpu_timer() >= limit) + return -1; return 0; } -uint32_t new_td_out(int length, void *data, uint32_t next) { - uint32_t td = usb_alloc(sizeof(struct OhciTD), 16); - volatile struct OhciTD *td_ = ptr32(td); - td_->info = (0b1111 << 28) // ConditionCode not accessed - | (0b00 << 24) // toggle 0b01 - | (0b01 << 19); // OUT, to endpoint - - td_->cbp = (uint32_t)(uintptr_t)data; - td_->be = td_->cbp + length - 1; - td_->next = next; - - return td; +static void td_fill(struct OhciTd *td, uint32_t info, void *buffer, + unsigned int length, struct OhciTd *next) { + td->info = TD_CC | info; + td->cbp = length ? ptr(buffer) : 0; + td->be = length ? ptr(buffer) + length - 1 : 0; + td->next = ptr(next); } -uint32_t new_td_in(int length, void *data, uint32_t next) { - uint32_t td = usb_alloc(sizeof(struct OhciTD), 16); - volatile struct OhciTD *td_ = ptr32(td); - td_->info = (0b1111 << 28) // ConditionCode not accessed - | (0b10 << 19) // IN, from endpoint - | (0b00 << 24); // toggle 0b11 - - td_->cbp = (uint32_t)(uintptr_t)data; - td_->be = td_->cbp + length; - td_->next = next; - - return td; +static void control_submit(struct Controller *c, uint8_t address, + uint16_t max_packet, const struct UsbRequest *request, + void *buffer, unsigned int length, int low_speed) { + struct ControllerDma *d = c->dma; + struct OhciTd *setup = &d->control_td[0]; + struct OhciTd *data = &d->control_td[1]; + struct OhciTd *status = &d->control_td[2]; + struct OhciTd *tail = &d->control_td[3]; + struct OhciTd *after_setup = length ? data : status; + int input = (request->requesttype & USB_DIR_IN) != 0; + + memset(&d->control_ed, 0, sizeof(d->control_ed)); + memset(d->control_td, 0, sizeof(d->control_td)); + memcpy(d->setup, request, sizeof(*request)); + td_fill(setup, TD_SETUP | TD_DATA0, d->setup, sizeof(*request), after_setup); + if (length) + td_fill(data, (input ? TD_IN : TD_OUT) | TD_DATA1 | TD_ROUND, + buffer, length, status); + td_fill(status, (input ? TD_OUT : TD_IN) | TD_DATA1, 0, 0, tail); + d->control_ed.info = address | (low_speed ? ED_LOWSPEED : 0) | + ((uint32_t)max_packet << 16); + d->control_ed.head = ptr(setup); + d->control_ed.tail = ptr(tail); + c->regs->control_head_ed = ptr(&d->control_ed); + c->regs->control |= CTRL_CLE; + c->regs->command_status = CMD_CLF; + c->deadline = asm_get_cpu_timer() + 100000; } -uint32_t new_td_control(void *data, int length, uint32_t next) { - uint32_t td = usb_alloc(sizeof(struct OhciTD), 16); - volatile struct OhciTD *td_ = ptr32(td); - td_->info = (0b1111 << 28) // ConditionCode not accessed - | (0b00 << 24) // toggle 0b01 - | (0b00 << 19); // SETUP, to endpoint - - uint32_t buffer = usb_alloc(length, 1); - memcpy(ptr32(buffer), data, length); - td_->cbp = (uint32_t)(uintptr_t)data; - td_->be = td_->cbp + length - 1; - td_->next = next; +/* Zero means pending, one means success, and -1 means failed or timed out. */ +static int control_status(struct Controller *c) { + struct OhciTd *status = &c->dma->control_td[2]; + uint32_t condition = status->info >> 28; + if (condition == 0xf && asm_get_cpu_timer() < c->deadline) + return 0; + if (condition == 0xf) + condition = 1; + c->dma->control_ed.info |= ED_SKIP; + return condition == 0 ? 1 : -1; +} - return td; +static void request_submit(struct Controller *c, uint8_t address, uint8_t type, + uint8_t command, uint16_t value, uint16_t index, void *buffer, + uint16_t length, uint16_t max_packet, int low_speed) { + struct UsbRequest req = { + .requesttype = type, .request = command, .value = value, + .index = index, .length = length, + }; + control_submit(c, address, max_packet, &req, buffer, length, low_speed); } -static void print_td(struct OhciTD *td, uint32_t tail) { - debug("TD at ", (uintptr_t)td); - debug("TD ", td->info); - debug("TD ", td->cbp); - debug("TD ", td->next); - debug("TD ", td->be); - if ((uint32_t)(uintptr_t)td == tail) { - return; +int usb_find_boot_keyboard(const uint8_t *config, unsigned int length, + struct UsbBootKeyboard *keyboard) { + int candidate = 0; + memset(keyboard, 0, sizeof(*keyboard)); + for (unsigned int offset = 0; offset + 2 <= length;) { + uint8_t dlen = config[offset]; + uint8_t type = config[offset + 1]; + if (dlen < 2 || offset + dlen > length) + return -1; + if (type == USB_DT_INTERFACE && dlen >= 9) { + candidate = config[offset + 3] == 0 && + config[offset + 5] == 3 && + config[offset + 6] == 1 && config[offset + 7] == 1; + if (candidate) + keyboard->interface_number = config[offset + 2]; + } else if (candidate && type == USB_DT_ENDPOINT && dlen >= 7 && + (config[offset + 2] & 0x80) && (config[offset + 3] & 3) == 3) { + keyboard->endpoint = config[offset + 2] & 0x0f; + keyboard->max_packet = config[offset + 4] | + ((uint16_t)config[offset + 5] << 8); + keyboard->interval = config[offset + 6]; + if (keyboard->interval && keyboard->max_packet >= 8 && + keyboard->max_packet <= 64) + return 0; + return -1; + } + offset += dlen; } - - struct OhciTD *td2 = (struct OhciTD *)(uintptr_t)td->next; - print_td(td2, tail); + return -1; } -uint32_t new_ed(uint32_t info, uint32_t head, uint32_t tail) { - uint32_t ed = usb_alloc(sizeof(struct OhciED), 16); - volatile struct OhciED *ed_ = ptr32(ed); - ed_->info = info; - ed_->head = head; - ed_->tail = tail; - ed_->next = 0x0; - return ed; +static void periodic_stop(struct Controller *c) { + c->dma->interrupt_ed.info |= ED_SKIP; + c->regs->control &= ~CTRL_PLE; + memset(c->dma->hcca.interrupt_table, 0, + sizeof(c->dma->hcca.interrupt_table)); + c->configured = 0; + c->enum_state = ENUM_SCAN; + c->retry_after = asm_get_cpu_timer() + 500000; + hid_keyboard_reset(); } -uint32_t new_dummy_td() { - uint32_t td = usb_alloc(sizeof(struct OhciTD), 16); - return td; +static void enumeration_fail(struct Controller *c) { + c->dma->control_ed.info |= ED_SKIP; + c->enum_state = ENUM_SCAN; + c->address = 0; + c->retry_after = asm_get_cpu_timer() + 500000; } -uint32_t control_request(volatile struct OhciHC *ohci, uint32_t dev, struct UsbRequest *req, void *buffer, int length) { - uint32_t td3 = new_dummy_td(); // we need an empty dummy packet as tail - uint32_t td2 = new_td_in(length, buffer, td3); // data in packet - uint32_t td1 = new_td_control(req, length, td2); // data out (SETUP) packet - - uint32_t ed = new_ed(dev | (1 << 0x0d) | (8 << 0x10), td1, td3); - - ohci->ed_controlhead = ed; - - ohci->control |= 1 << 4; - ohci->cmdstatus = 1 << 1; - - msleep(10); - - while (!interrupt_handler(ohci)); - - ohci->control |= 1 << 4; - +static int periodic_start(struct Controller *c) { + struct ControllerDma *d = c->dma; + unsigned int period = 1; + while (period < c->interval && period < 32) + period <<= 1; + memset(d->report, 0, sizeof(d->report)); + memset(&d->interrupt_ed, 0, sizeof(d->interrupt_ed)); + memset(&d->interrupt_td, 0, sizeof(d->interrupt_td)); + memset(&d->interrupt_tail, 0, sizeof(d->interrupt_tail)); + td_fill(&d->interrupt_td, TD_IN | TD_ROUND, d->report, 8, + &d->interrupt_tail); + d->interrupt_ed.info = c->address | ((uint32_t)c->endpoint << 7) | + (2U << 11) | (c->low_speed ? ED_LOWSPEED : 0) | + ((uint32_t)c->max_packet << 16); + d->interrupt_ed.head = ptr(&d->interrupt_td); + d->interrupt_ed.tail = ptr(&d->interrupt_tail); + for (unsigned int i = 0; i < 32; i++) + d->hcca.interrupt_table[i] = (i & (period - 1)) ? 0 : + ptr(&d->interrupt_ed); + c->regs->control |= CTRL_PLE; + c->configured = 1; return 0; } -int setup_ohci(uintptr_t base) { - volatile struct OhciHC *ohci = (volatile struct OhciHC *)base; - - debug("revision: ", ohci->revision); - - if (ohci->revision != 0x10) { - puts("Driver only for OHCI v1.0"); - return 1; +/* Advance at most one enumeration transition per call. This keeps FUEFI input + * polling nonblocking even while a device is attaching or misbehaving. */ +static void enumerate_step(struct Controller *c) { + uint8_t *data = c->dma->data; + struct UsbBootKeyboard keyboard; + uint16_t total; + uint8_t configuration; + int status; + uint64_t now = asm_get_cpu_timer(); + volatile uint32_t *port_status; + + if (c->enum_state == ENUM_SCAN) { + unsigned int ports; + if (now < c->retry_after) + return; + ports = c->regs->rh_descriptor_a & 0xff; + if (ports > 15) ports = 15; + for (unsigned int port = 0; port < ports; port++) { + if (!(c->regs->rh_port_status[port] & BIT(0))) + continue; + c->port = (uint8_t)port; + c->regs->rh_port_status[port] = BIT(8); + c->deadline = now + 20000; + c->enum_state = ENUM_POWER_WAIT; + return; + } + return; } - ohci->intrdisable = 1 << 31; // disable interrupts - - ohci->control = 0; - ohci->cmdstatus = (1 << 0); // Reset controller - while (ohci->cmdstatus & (1 << 0)); // Wait until completed - - if (((ohci->control >> 6) & 0b11) != USBSUSPEND) { - debug("USB not suspended", ohci->control); - abort(); + port_status = &c->regs->rh_port_status[c->port]; + if (!(*port_status & BIT(0))) { + c->enum_state = ENUM_SCAN; + c->retry_after = 0; + return; } - uint32_t hcca32 = usb_alloc(256, 256); - memset((void *)(uintptr_t)hcca32, 0, 256); - ohci->ed_controlhead = 0x0; - ohci->ed_bulkhead = 0x0; - ohci->hcca = hcca32; - - // Set our magic numbers - ohci->fminterval = (((6 * (11999 - 210)) / 7) << 16) | 11999; - ohci->fminterval ^= (1 << 31); - ohci->periodicstart = (11999 * 9) / 10; - ohci->lsthresh = 0x628; - - // Enable all interrupts - ohci->intrdisable = 0b1111111 | (1 << 30) | (1 << 31); - ohci->intrstatus = 0b1111111 | (1 << 30); - ohci->intrenable = 0b1111111 | (1 << 30); - - ohci->control = HCFS_USB_OPERATIONAL | OHCI_CTRL_PLE | OHCI_CTRL_IE | 0b11; - - uint32_t num_ports = ohci->desc_a & 0xff; - debug("Number of ports: ", num_ports); - for (uint32_t i = 0; i < num_ports; i++) { - debug("portstatus: ", ohci->portstatus[i]); - - ohci->portstatus[i] = 1 << 8; - ohci->portstatus[i] = 1 << 16; + switch (c->enum_state) { + case ENUM_POWER_WAIT: + if (now < c->deadline) return; + *port_status = BIT(4); + c->deadline = now + 100000; + c->enum_state = ENUM_RESET_WAIT; + return; + case ENUM_RESET_WAIT: + if (*port_status & BIT(4)) { + if (now >= c->deadline) enumeration_fail(c); + return; + } + *port_status = BIT(20) | BIT(17) | BIT(16); + if ((*port_status & (BIT(0) | BIT(1))) != (BIT(0) | BIT(1))) { + enumeration_fail(c); + return; + } + c->low_speed = (*port_status & BIT(9)) != 0; + c->control_packet = 8; + request_submit(c, 0, 0x80, USB_REQ_GET_DESCRIPTOR, + USB_DT_DEVICE << 8, 0, data, 8, 8, c->low_speed); + c->enum_state = ENUM_GET_DEVICE; + return; + case ENUM_GET_DEVICE: + status = control_status(c); + if (!status) return; + if (status < 0 || (data[7] != 8 && data[7] != 16 && + data[7] != 32 && data[7] != 64)) { + enumeration_fail(c); + return; + } + c->control_packet = data[7]; + c->address = (uint8_t)(c - controllers + 1); + request_submit(c, 0, 0, USB_REQ_SET_ADDRESS, c->address, 0, + 0, 0, c->control_packet, c->low_speed); + c->enum_state = ENUM_SET_ADDRESS; + return; + case ENUM_SET_ADDRESS: + status = control_status(c); + if (!status) return; + if (status < 0) { enumeration_fail(c); return; } + c->deadline = now + 2000; + c->enum_state = ENUM_ADDRESS_WAIT; + return; + case ENUM_ADDRESS_WAIT: + if (now < c->deadline) return; + request_submit(c, c->address, 0x80, USB_REQ_GET_DESCRIPTOR, + USB_DT_DEVICE << 8, 0, data, 18, c->control_packet, + c->low_speed); + c->enum_state = ENUM_GET_DEVICE_FULL; + return; + case ENUM_GET_DEVICE_FULL: + status = control_status(c); + if (!status) return; + if (status < 0 || data[0] < 18 || data[1] != USB_DT_DEVICE) { + enumeration_fail(c); + return; + } + debug("USB VID: ", data[8] | ((uint16_t)data[9] << 8)); + debug("USB PID: ", data[10] | ((uint16_t)data[11] << 8)); + request_submit(c, c->address, 0x80, USB_REQ_GET_DESCRIPTOR, + USB_DT_CONFIG << 8, 0, data, 9, c->control_packet, + c->low_speed); + c->enum_state = ENUM_GET_CONFIG_HEAD; + return; + case ENUM_GET_CONFIG_HEAD: + status = control_status(c); + if (!status) return; + total = data[2] | ((uint16_t)data[3] << 8); + if (status < 0 || total < 9 || total > MAX_CONFIG_SIZE) { + enumeration_fail(c); + return; + } + request_submit(c, c->address, 0x80, USB_REQ_GET_DESCRIPTOR, + USB_DT_CONFIG << 8, 0, data, total, c->control_packet, + c->low_speed); + c->enum_state = ENUM_GET_CONFIG; + return; + case ENUM_GET_CONFIG: + status = control_status(c); + if (!status) return; + total = data[2] | ((uint16_t)data[3] << 8); + if (status < 0 || total < 9 || total > MAX_CONFIG_SIZE || + usb_find_boot_keyboard(data, total, &keyboard)) { + enumeration_fail(c); + return; + } + configuration = data[5]; + if (!configuration) { + enumeration_fail(c); + return; + } + c->endpoint = keyboard.endpoint; + c->interval = keyboard.interval; + c->max_packet = keyboard.max_packet; + c->interface_number = keyboard.interface_number; + request_submit(c, c->address, 0, USB_REQ_SET_CONFIGURATION, + configuration, 0, 0, 0, c->control_packet, c->low_speed); + c->enum_state = ENUM_SET_CONFIG; + return; + case ENUM_SET_CONFIG: + status = control_status(c); + if (!status) return; + if (status < 0) { enumeration_fail(c); return; } + request_submit(c, c->address, 0x21, 0x0b, 0, + c->interface_number, 0, 0, c->control_packet, c->low_speed); + c->enum_state = ENUM_SET_PROTOCOL; + return; + case ENUM_SET_PROTOCOL: + status = control_status(c); + if (!status) return; + if (status < 0) { enumeration_fail(c); return; } + request_submit(c, c->address, 0x21, 0x0a, 0, + c->interface_number, 0, 0, c->control_packet, c->low_speed); + c->enum_state = ENUM_SET_IDLE; + return; + case ENUM_SET_IDLE: + status = control_status(c); + if (!status) return; + if (status < 0) { enumeration_fail(c); return; } + puts("USB HID boot keyboard ready"); + periodic_start(c); + return; + default: + enumeration_fail(c); + } +} - ohci->portstatus[i] = (1 << 4); // reset port - while (ohci->portstatus[i] & (1 << 4)); // wait for reset over - // 'USB Address' for this device is now zero +static void periodic_poll(struct Controller *c) { + struct ControllerDma *d = c->dma; + uint32_t cc = d->interrupt_td.info >> 28; + if (cc == 0xf) + return; + if (cc != 0) { + periodic_stop(c); + return; + } + hid_keyboard_report(d->report); + d->interrupt_ed.info |= ED_SKIP; + d->hcca.done_head = 0; + memset(d->report, 0, sizeof(d->report)); + td_fill(&d->interrupt_td, TD_IN | TD_ROUND, d->report, 8, + &d->interrupt_tail); + d->interrupt_ed.head = ptr(&d->interrupt_td) | + (d->interrupt_ed.head & 2); + d->interrupt_ed.tail = ptr(&d->interrupt_tail); + d->interrupt_ed.info &= ~ED_SKIP; +} - if (!(ohci->portstatus[i] & (1 << 0))) { - debug("Device not connected on port ", i); +void ohci_poll_all(void) { + for (unsigned int n = 0; n < controller_count; n++) { + struct Controller *c = &controllers[n]; + if (c->configured) { + if (!(c->regs->rh_port_status[c->port] & BIT(0))) + periodic_stop(c); + else + periodic_poll(c); continue; } - - uint32_t stat = ohci->portstatus[i]; - if (stat & (1 << 1)) puts("Port is enabled"); - else puts("Port is disabled"); - if (stat & (1 << 9)) puts("Low speed device attached"); - if (stat & (1 << 8)) puts("Port is powered on"); - else puts("Port is powered off"); - if (stat & (1 << 0)) puts("Device connected"); - else puts("No device connected"); - - // Our first request will be SET_ADDRESS - struct UsbRequest *req = (void *)(uintptr_t)usb_alloc(8, 16); - req->requesttype = 0x0; - req->request = USB_REQ_SET_ADDRESS; - req->value = 0x1; - req->index = 0x0; - req->length = 0x0; - - control_request(ohci, 0, req, NULL, 0); + enumerate_step(c); } +} +int ohci_add_controller(uintptr_t base) { + struct Controller *c; + if (controller_count >= MAX_CONTROLLERS) + return -1; + c = &controllers[controller_count]; + memset(c, 0, sizeof(*c)); + c->regs = (volatile struct OhciRegs *)base; + if ((c->regs->revision & 0xff) != 0x10) + return -1; + c->dma = ohci_dma_alloc(sizeof(*c->dma), 256); + if (!c->dma) + return -1; + c->regs->interrupt_disable = 0xffffffff; + c->regs->control = 0; + c->regs->command_status = CMD_RESET; + if (wait_clear(&c->regs->command_status, CMD_RESET, 20)) + return -1; + c->regs->hcca = ptr(&c->dma->hcca); + c->regs->control_head_ed = 0; + c->regs->bulk_head_ed = 0; + c->regs->frame_interval = (1U << 31) | + (((6U * (11999U - 210U)) / 7U) << 16) | 11999U; + c->regs->periodic_start = (0x2edfU * 9) / 10; + c->regs->ls_threshold = 0x628; + c->regs->interrupt_status = 0xffffffff; + c->regs->rh_status = BIT(16); /* Global power. */ + c->regs->control = CTRL_OPERATIONAL | 3; + controller_count++; return 0; } + +int setup_ohci(uintptr_t base) { + return ohci_add_controller(base); +} diff --git a/src/ohci.h b/src/ohci.h new file mode 100644 index 0000000..228c56f --- /dev/null +++ b/src/ohci.h @@ -0,0 +1,22 @@ +#ifndef RK_OHCI_H +#define RK_OHCI_H + +#include + +struct UsbBootKeyboard { + uint8_t interface_number; + uint8_t endpoint; + uint8_t interval; + uint16_t max_packet; +}; + +void ohci_dma_configure(uintptr_t start, uintptr_t end); +void *ohci_dma_alloc(unsigned long size, unsigned long alignment); +int ohci_dma_contains(uintptr_t address, unsigned long size); +int usb_find_boot_keyboard(const uint8_t *config, unsigned int length, + struct UsbBootKeyboard *keyboard); +int ohci_add_controller(uintptr_t base); +int setup_ohci(uintptr_t base); +void ohci_poll_all(void); + +#endif diff --git a/src/pinebook.c b/src/pinebook.c index 796bed8..41c0900 100644 --- a/src/pinebook.c +++ b/src/pinebook.c @@ -28,6 +28,7 @@ uint64_t plat_process_firmware_call(uint64_t p1, uint64_t p2, uint64_t p3, uint6 switch (p1) { case FU_GET_SCREEN_LIST: screens->length = 1; + screens->type = FU_SCREEN_XRGB8888; screens->screens[0].framebuffer_addr = plat_get_framebuffer(); screens->screens[0].width = 1920; screens->screens[0].height = 1080; diff --git a/src/rk356x/board.c b/src/rk356x/board.c new file mode 100644 index 0000000..160e5d7 --- /dev/null +++ b/src/rk356x/board.c @@ -0,0 +1,102 @@ +#include +#include "main.h" +#include "firmware.h" +#include "input.h" +#include "ohci.h" +#include "rk356x.h" + +const struct Rk356xBoard *rk356x_board; +static void *dtb_addr; + +extern uint64_t rk356x_ram_size(void); + +void rk356x_set_dtb(const void *data, unsigned int size) { + if (size > RK356X_SHARED - RK356X_DTB) + size = RK356X_SHARED - RK356X_DTB; + memcpy((void *)RK356X_DTB, data, size); + dtb_addr = (void *)RK356X_DTB; +} + +static struct FuMemoryMapItem *largest_free(struct FuMemoryMap *map) { + struct FuMemoryMapItem *best = 0; + for (uint32_t i = 0; i < map->length; i++) { + if (!(map->items[i].flags & FU_MEM_ATTR_UNUSED)) + continue; + if (!best || map->items[i].end_addr - map->items[i].start_addr > + best->end_addr - best->start_addr) + best = &map->items[i]; + } + return best; +} + +uint64_t plat_process_firmware_call(uint64_t p1, uint64_t p2, + uint64_t p3, uint64_t p4) { + uint8_t *shared = (uint8_t *)RK356X_SHARED; + struct FuScreenList *screens = (void *)shared; + struct FuDeviceInfo *info = (void *)(shared + 0x80); + struct FuMemoryMap *map = (void *)(shared + 0x100); + (void)p2; (void)p3; (void)p4; + switch (p1) { + case FU_GET_SCREEN_LIST: + memset(screens, 0, 0x80); + screens->type = FU_SCREEN_XRGB8888; + if (rk356x_video.active) { + screens->length = 1; + screens->screens[0].framebuffer_addr = RK356X_FB_START; + screens->screens[0].width = rk356x_video.mode.hactive; + screens->screens[0].height = rk356x_video.mode.vactive; + screens->screens[0].stride = rk356x_video.stride; + } + return (uintptr_t)screens; + case FU_GET_MEM_CHUNK: + plat_get_mem_map(map); + return (uintptr_t)largest_free(map); + case FU_GET_MEM_MAP: + plat_get_mem_map(map); + return (uintptr_t)map; + case FU_GET_DEVICE_INFO: + memset(info, 0, sizeof(*info)); + strcpy(info->vendor, rk356x_board->vendor); + strcpy(info->product, rk356x_board->product); + return (uintptr_t)info; + case FU_GET_DTB: + return (uintptr_t)dtb_addr; + default: + return FU_ERROR; + } +} + +int rk356x_board_entry(const struct Rk356xBoard *board, + const void *dtb, unsigned int dtb_size) { + rk356x_board = board; + asm_set_cnt_freq(24000000); + enable_uart(); + uart_init(1500000); + puts("RK356x bare-metal firmware"); + puts(board->name); + puts(board->soc); + + rk356x_setup_security(); + plat_setup_mmu(0); + debug("DDR bytes: ", rk356x_ram_size()); + rk356x_set_dtb(dtb, dtb_size); + for (unsigned int i = 0; i < 2; i++) + rk356x_gpio_output(board->usb_vbus[i], 1); + for (unsigned int i = 0; i < 2; i++) + rk356x_gpio_output(board->leds[i], 1); + + if (rk356x_display_init()) + puts(rk356x_video.hpd ? "HDMI: setup failed" : "HDMI: headless"); + else { + puts(rk356x_video.edid_valid ? "HDMI: EDID mode" : + "HDMI: 720p60 fallback"); + debug("HDMI width: ", rk356x_video.mode.hactive); + debug("HDMI height: ", rk356x_video.mode.vactive); + } + + input_reset(); + rk356x_usb_init(); + puts("Transitioning payload to EL2"); + jump_to_payload(); + return 0; +} diff --git a/src/rk356x/cru.c b/src/rk356x/cru.c new file mode 100644 index 0000000..5f85c78 --- /dev/null +++ b/src/rk356x/cru.c @@ -0,0 +1,35 @@ +#include "main.h" +#include "rk356x.h" + +static inline volatile uint32_t *reg(uintptr_t base, unsigned int offset) { + return (volatile uint32_t *)(base + offset); +} + +void rk356x_enable_uart(void) { + /* UART2 M0: GPIO0_D0 RX and GPIO0_D1 TX. */ + rk_clr_set_bits(reg(RK356X_PMUGRF, 0x18), 2, 0, 1); + rk_clr_set_bits(reg(RK356X_PMUGRF, 0x18), 6, 4, 1); + /* Select the M0 rather than M1 pin group. */ + rk_clr_set_bits(reg(RK356X_GRF, 0x30c), 11, 10, 0); + + /* Select xin24m directly, enable PCLK/SCLK, and release both resets. */ + rk_clr_set_bits(reg(RK356X_CRU, 0x100 + 54 * 4), 13, 12, 2); + rk_clr_set_bits(reg(RK356X_CRU, 0x300 + 26 * 4), 1, 1, 0); + rk_clr_set_bits(reg(RK356X_CRU, 0x300 + 28 * 4), 0, 0, 0); + rk_clr_set_bits(reg(RK356X_CRU, 0x300 + 28 * 4), 3, 3, 0); + rk_clr_set_bits(reg(RK356X_CRU, 0x400 + 25 * 4), 1, 0, 0); +} + +void enable_uart(void) { + rk356x_enable_uart(); +} + +int rk356x_enable_vo_domain(void) { + /* PMU power-domain control: clear PD_VO to request power-up. */ + *reg(RK356X_PMU, 0xa0) = (1U << (16 + 7)); + for (unsigned int i = 0; i < 100000; i++) { + if (!(*reg(RK356X_PMU, 0x98) & (1U << 7))) + return 0; + } + return -1; +} diff --git a/src/rk356x/dram.c b/src/rk356x/dram.c new file mode 100644 index 0000000..700f363 --- /dev/null +++ b/src/rk356x/dram.c @@ -0,0 +1,124 @@ +#include +#include "rk356x.h" + +#define ATAGS_START 0x001fe000UL +#define ATAGS_END 0x00200000UL +#define ATAG_DDR_MEM 0x54410052U + +struct __attribute__((packed)) TagHeader { + uint32_t size; + uint32_t magic; +}; + +struct __attribute__((packed)) TagDdrMem { + uint32_t count; + uint32_t version; + uint64_t bank[20]; + uint32_t flags; + uint32_t data[2]; + uint32_t hash; +}; + +static uint32_t js_hash(const void *buffer, uint32_t length) { + const uint8_t *p = buffer; + uint32_t hash = 0x47c6a7e6; + for (uint32_t i = 0; i < length; i++) + hash ^= (hash << 5) + p[i] + (hash >> 2); + return hash; +} + +static uint64_t dram_from_atags(void) { + const uint32_t *cursor = (const uint32_t *)ATAGS_START; + while ((uintptr_t)cursor + sizeof(struct TagHeader) <= ATAGS_END) { + const struct TagHeader *header = (const void *)cursor; + const struct TagDdrMem *ddr; + uint64_t top = 0; + if (!header->size) + break; + if (header->size < 2 || (uintptr_t)(cursor + header->size) > ATAGS_END) + return 0; + if (header->magic != ATAG_DDR_MEM) { + cursor += header->size; + continue; + } + if (header->size < (sizeof(*header) + sizeof(struct TagDdrMem)) / 4) + return 0; + ddr = (const void *)((const uint8_t *)&header->magic + + sizeof(header->magic)); + if (!ddr->count || ddr->count > 10) + return 0; + if (ddr->hash && js_hash(header, (header->size - 1) * 4) != ddr->hash) + return 0; + for (uint32_t i = 0; i < ddr->count; i++) { + uint64_t start = ddr->bank[i]; + uint64_t size = ddr->bank[i + ddr->count]; + if (!size || start + size < start) + return 0; + if (start + size > top) + top = start + size; + } + return top; + } + return 0; +} + +static uint64_t dram_from_geometry(void) { + uint32_t r2 = *(volatile uint32_t *)(RK356X_PMUGRF + 0x208); + uint32_t r3 = *(volatile uint32_t *)(RK356X_PMUGRF + 0x20c); + uint32_t channels = 1 + ((r2 >> 12) & 1); + uint32_t version = r3 >> 28; + uint32_t type = (r2 >> 13) & 7; + uint64_t total_mb = 0; + if (version >= 3) + type |= ((r3 >> 12) & 3) << 3; + for (uint32_t ch = 0; ch < channels; ch++) { + uint32_t rank = 1 + ((r2 >> (11 + ch * 16)) & 1); + uint32_t col0 = 9 + ((r2 >> (9 + ch * 16)) & 3); + uint32_t col1 = col0; + uint32_t banks = type == 9 ? 3 + ((r2 >> (8 + ch * 16)) & 1) + : 3 - ((r2 >> (8 + ch * 16)) & 1); + uint32_t row0, row1; + uint32_t bw = 2 >> ((r2 >> (2 + ch * 16)) & 3); + uint32_t bg = 0; + if (version >= 2) { + uint32_t raw0 = ((r3 >> (5 + ch * 2)) & 1) * 4 + + ((r2 >> (6 + ch * 16)) & 3); + uint32_t raw1 = ((r3 >> (4 + ch * 2)) & 1) * 4 + + ((r2 >> (4 + ch * 16)) & 3); + row0 = raw0 == 7 ? 12 : 13 + raw0; + row1 = raw1 == 7 ? 12 : 13 + raw1; + col1 = 9 + ((r3 >> (ch * 2)) & 3); + } else { + row0 = 13 + ((r2 >> (6 + ch * 16)) & 3); + row1 = 13 + ((r2 >> (4 + ch * 16)) & 3); + } + if (type == 0) + bg = (((r2 >> (ch * 16)) & 3) == 2) ? 2 : 1; + if (row0 + col0 + banks + bg + bw < 20 || + row0 + col0 + banks + bg + bw > 34) + return 0; + uint64_t mb = 1ULL << (row0 + col0 + banks + bg + bw - 20); + if (rank > 1) { + int delta = (int)row0 - (int)row1 + + (int)col0 - (int)col1; + if (delta < 0 || delta > 8) + return 0; + mb += mb >> delta; + } + if ((r2 >> (30 + ch)) & 1) + mb = mb * 3 / 4; + total_mb += mb; + } + if (total_mb < 1024 || total_mb > 32768) + return 0; + return total_mb << 20; +} + +uint64_t rk356x_detect_dram(void) { + uint64_t size = dram_from_atags(); + if (!size) + size = dram_from_geometry(); + if (!size) + size = 1ULL << 30; + return size; +} diff --git a/src/rk356x/gpio.c b/src/rk356x/gpio.c new file mode 100644 index 0000000..e39b599 --- /dev/null +++ b/src/rk356x/gpio.c @@ -0,0 +1,89 @@ +#include "main.h" +#include "rk356x.h" + +struct RkGpio { + uint32_t dr_l; + uint32_t dr_h; + uint32_t ddr_l; + uint32_t ddr_h; +}; + +static volatile struct RkGpio *gpio_get(int bank) { + static const uintptr_t bases[] = { + 0xfdd60000, 0xfe740000, 0xfe750000, 0xfe760000, 0xfe770000, + }; + if ((unsigned int)bank >= sizeof(bases) / sizeof(bases[0])) + return 0; + return (volatile struct RkGpio *)bases[bank]; +} + +static void gpio_enable(int bank) { + if (bank == 0) { + rk_clr_set_bits((volatile void *)(RK356X_PMUCRU + 0x184), 9, 9, 0); + rk_clr_set_bits((volatile void *)(RK356X_PMUCRU + 0x200), 10, 9, 0); + } else if ((unsigned int)bank <= 4) { + unsigned int gate = 2U * (unsigned int)bank; + unsigned int reset = 344U + 2U * (unsigned int)bank; + rk_clr_set_bits((volatile void *)(RK356X_CRU + 0x300 + 31 * 4), + gate, gate, 0); + rk_clr_set_bits((volatile void *)(RK356X_CRU + 0x400 + + (reset / 16) * 4), reset % 16 + 1, reset % 16, 0); + } +} + +static void gpio_set_mux(int bank, int pin) { + uintptr_t base; + unsigned int group = (unsigned int)pin / 8; + unsigned int offset; + unsigned int shift = ((unsigned int)pin % 4) * 4; + if ((unsigned int)bank > 4 || (unsigned int)pin > 31) + return; + base = bank ? RK356X_GRF : RK356X_PMUGRF; + offset = bank ? ((unsigned int)bank - 1) * 0x20 : 0; + offset += group * 8 + (((unsigned int)pin & 4) ? 4 : 0); + rk_clr_set_bits((volatile void *)(base + offset), shift + 3, shift, 0); +} + +static void masked_pin_write(volatile uint32_t *low, volatile uint32_t *high, + int pin, int value) { + unsigned int bit = (unsigned int)pin & 15; + uint32_t value_and_mask = (1U << (bit + 16)) | ((value != 0) << bit); + if (pin < 16) + *low = value_and_mask; + else + *high = value_and_mask; +} + +void gpio_set_dir(int gpio, int pin, int bit) { + volatile struct RkGpio *g = gpio_get(gpio); + if (g) + masked_pin_write(&g->ddr_l, &g->ddr_h, pin, bit); +} + +void gpio_set_pin(int gpio, int pin, int bit) { + volatile struct RkGpio *g = gpio_get(gpio); + if (g) + masked_pin_write(&g->dr_l, &g->dr_h, pin, bit); +} + +int gpio_get_pin(int gpio, int pin) { + volatile uint32_t *ext_port = (volatile uint32_t *) + ((uintptr_t)gpio_get(gpio) + 0x70); + return gpio_get(gpio) ? ((*ext_port >> pin) & 1) : 0; +} + +void gpio_pin_mask_int(int gpio, int pin) { + volatile uint32_t *low = (volatile uint32_t *) + ((uintptr_t)gpio_get(gpio) + 0x18); + if (gpio_get(gpio)) + masked_pin_write(low, low + 1, pin, 1); +} + +void rk356x_gpio_output(struct Rk356xGpioPin pin, int asserted) { + if (!pin.valid) + return; + gpio_enable(pin.bank); + gpio_set_mux(pin.bank, pin.pin); + gpio_set_pin(pin.bank, pin.pin, asserted == pin.active_high); + gpio_set_dir(pin.bank, pin.pin, 1); +} diff --git a/src/rk356x/hdmi.c b/src/rk356x/hdmi.c new file mode 100644 index 0000000..26fd8fc --- /dev/null +++ b/src/rk356x/hdmi.c @@ -0,0 +1,253 @@ +#include +#include "main.h" +#include "edid.h" +#include "rk356x.h" + +#define BIT(x) (1U << (x)) +#define HDMI(reg) ((volatile uint32_t *)(RK356X_HDMI + ((reg) << 2))) + +struct Rk356xVideo rk356x_video; + +extern int rk356x_display_clocks(uint32_t pixel_clock_khz); +extern void rk356x_vop2_setup(const struct VideoMode *mode, uint32_t stride); + +static uint8_t hdmi_read(unsigned int offset) { + return (uint8_t)*HDMI(offset); +} + +static void hdmi_write(unsigned int offset, uint8_t value) { + *HDMI(offset) = value; +} + +static void hdmi_modify(unsigned int offset, uint8_t mask, uint8_t value) { + hdmi_write(offset, (hdmi_read(offset) & ~mask) | (value & mask)); +} + +static int ddc_wait(void) { + for (unsigned int timeout = 0; timeout < 100000; timeout++) { + uint8_t status = hdmi_read(0x0105) & 3; + if (status) { + hdmi_write(0x0105, status); + return (status & 2) ? 0 : -1; + } + } + return -1; +} + +static void ddc_init(void) { + hdmi_write(0x7e09, 0); + hdmi_write(0x7e07, 0); + hdmi_write(0x7e05, 0x08); + hdmi_write(0x7e06, 0x88); + hdmi_write(0x0105, 3); + hdmi_write(0x7e00, 0x50); +} + +static int ddc_read_block(unsigned int block, uint8_t *data) { + unsigned int segment = block >> 1; + unsigned int start = (block & 1) ? 128 : 0; + hdmi_write(0x7e08, 0x30); + hdmi_write(0x7e0a, segment); + for (unsigned int i = 0; i < EDID_BLOCK_SIZE; i++) { + hdmi_write(0x7e01, start + i); + hdmi_write(0x7e04, segment ? 2 : 1); + if (ddc_wait()) + return -1; + data[i] = hdmi_read(0x7e03); + } + return 0; +} + +static int read_edid(uint8_t *edid, unsigned int *blocks) { + unsigned int wanted; + ddc_init(); + if (ddc_read_block(0, edid) || !edid_block_valid(edid)) + return -1; + wanted = 1 + edid[126]; + if (wanted > EDID_MAX_BLOCKS) + wanted = EDID_MAX_BLOCKS; + *blocks = 1; + for (unsigned int i = 1; i < wanted; i++) { + if (ddc_read_block(i, edid + i * EDID_BLOCK_SIZE)) + break; + *blocks = i + 1; + } + return 0; +} + +static void fc_write16(unsigned int low, uint16_t value) { + hdmi_write(low, value); + hdmi_write(low + 1, value >> 8); +} + +static uint8_t mode_vic(const struct VideoMode *m) { + if (m->hactive == 640 && m->vactive == 480 && m->refresh_hz == 60) return 1; + if (m->hactive == 720 && m->vactive == 480 && m->refresh_hz == 60) return 2; + if (m->hactive == 1280 && m->vactive == 720 && m->refresh_hz == 60) return 4; + if (m->hactive == 1920 && m->vactive == 1080 && m->refresh_hz == 60) return 16; + if (m->hactive == 1280 && m->vactive == 720 && m->refresh_hz == 50) return 19; + if (m->hactive == 1920 && m->vactive == 1080 && m->refresh_hz == 50) return 31; + if (m->hactive == 1920 && m->vactive == 1080 && m->refresh_hz == 24) return 32; + if (m->hactive == 1920 && m->vactive == 1080 && m->refresh_hz == 25) return 33; + if (m->hactive == 1920 && m->vactive == 1080 && m->refresh_hz == 30) return 34; + if (m->hactive == 3840 && m->vactive == 2160 && m->refresh_hz == 24) return 93; + if (m->hactive == 3840 && m->vactive == 2160 && m->refresh_hz == 25) return 94; + if (m->hactive == 3840 && m->vactive == 2160 && m->refresh_hz == 30) return 95; + return 0; +} + +static void hdmi_video_setup(const struct VideoMode *m) { + uint16_t hblank = m->hfront_porch + m->hsync_len + m->hback_porch; + uint8_t vblank = m->vfront_porch + m->vsync_len + m->vback_porch; + uint8_t vic = mode_vic(m); + uint8_t aspect = 0; + uint8_t invidconf = 0x18; /* HDMI, active-high DE, progressive RGB. */ + if (vic == 1 || vic == 2 || (uint32_t)m->hactive * 3 == + (uint32_t)m->vactive * 4) + aspect = 0x10; + else if (vic || (uint32_t)m->hactive * 9 == + (uint32_t)m->vactive * 16) + aspect = 0x20; + if (m->flags & VIDEO_FLAG_HSYNC_HIGH) invidconf |= 0x20; + if (m->flags & VIDEO_FLAG_VSYNC_HIGH) invidconf |= 0x40; + hdmi_write(0x1000, invidconf); + fc_write16(0x1001, m->hactive); + fc_write16(0x1003, hblank); + fc_write16(0x1005, m->vactive); + hdmi_write(0x1007, vblank); + fc_write16(0x1008, m->hfront_porch); + fc_write16(0x100a, m->hsync_len); + hdmi_write(0x100c, m->vfront_porch); + hdmi_write(0x100d, m->vsync_len); + + /* RGB888 video sampler and a bypassed 8-bpc packetizer. */ + hdmi_write(0x0200, 0x01); + hdmi_write(0x0201, 0x07); + for (unsigned int r = 0x0202; r <= 0x0207; r++) hdmi_write(r, 0); + hdmi_write(0x0801, 0x40); + hdmi_write(0x0802, 0x27); + hdmi_write(0x0803, 0); + hdmi_write(0x0804, 0x47); + + /* RGB AVI infoframe with the timing's advertised picture aspect. */ + hdmi_write(0x1019, 0); + hdmi_write(0x101a, aspect); + hdmi_write(0x101b, 0x08); + hdmi_write(0x101c, vic); + hdmi_write(0x1011, 12); + hdmi_write(0x1012, 32); + hdmi_write(0x1013, 1); + hdmi_write(0x1014, 0x0b); + hdmi_write(0x1015, 0x16); + hdmi_write(0x1016, 0x21); + hdmi_write(0x4004, 0); /* Feed-through, no CSC. */ + hdmi_write(0x4001, 0x7e); + hdmi_write(0x4001, 0x7c); +} + +struct PhyRate { + uint32_t max_khz; + uint16_t cpce; + uint16_t gmp; + uint16_t sym; + uint16_t term; + uint16_t vlev; +}; + +static const struct PhyRate phy_rates[] = { + { 30666, 0x00b3, 0x0000, 0x8009, 0x0004, 0x0272 }, + { 74250, 0x0072, 0x0001, 0x8009, 0x0004, 0x0272 }, + { 165000, 0x0051, 0x0002, 0x802b, 0x0004, 0x0209 }, + { 184000, 0x0051, 0x0002, 0x8039, 0x0005, 0x028d }, + { 340000, 0x0040, 0x0003, 0x8039, 0x0005, 0x028d }, +}; + +static int phy_i2c_write(uint8_t address, uint16_t value) { + hdmi_write(0x0108, 3); + hdmi_write(0x3021, address); + hdmi_write(0x3022, value >> 8); + hdmi_write(0x3023, value); + hdmi_write(0x3026, 0x10); + for (unsigned int timeout = 0; timeout < 100000; timeout++) { + uint8_t status = hdmi_read(0x0108) & 3; + if (status) { + hdmi_write(0x0108, status); + return (status & 2) ? 0 : -1; + } + } + return -1; +} + +static int hdmi_phy_setup(uint32_t clock_khz) { + const struct PhyRate *rate = 0; + for (unsigned int i = 0; i < sizeof(phy_rates) / sizeof(phy_rates[0]); i++) + if (clock_khz <= phy_rates[i].max_khz) { rate = &phy_rates[i]; break; } + if (!rate) + return -1; + + /* The Gen2 PHY requires the complete setup sequence twice. */ + for (unsigned int pass = 0; pass < 2; pass++) { + int locked = 0; + hdmi_modify(0x3000, 0x18, 0x10); /* TX off, PDDQ asserted. */ + hdmi_modify(0x3000, 0x23, 0x22); /* SVSRET, data polarity, PHY I/F. */ + hdmi_write(0x4005, 1); + hdmi_write(0x4005, 0); + hdmi_write(0x4007, 1); + hdmi_modify(0x3001, 0x20, 0x20); + hdmi_write(0x3020, 0x69); + hdmi_modify(0x3001, 0x20, 0); + if (phy_i2c_write(0x06, rate->cpce) || + phy_i2c_write(0x15, rate->gmp) || + phy_i2c_write(0x10, 0) || phy_i2c_write(0x13, 0) || + phy_i2c_write(0x17, 6) || phy_i2c_write(0x19, rate->term) || + phy_i2c_write(0x09, rate->sym) || + phy_i2c_write(0x0e, rate->vlev) || + phy_i2c_write(0x05, 0x8000)) + return -1; + hdmi_modify(0x3000, 0x1c, 0x0c); /* TX power, PDDQ clear. */ + for (unsigned int timeout = 0; timeout < 100000; timeout++) { + if (hdmi_read(0x3004) & 1) { + locked = 1; + break; + } + } + if (!locked) + return -1; + } + return 0; +} + +int rk356x_display_init(void) { + uint8_t edid[EDID_BLOCK_SIZE * EDID_MAX_BLOCKS]; + unsigned int blocks = 0; + memset(&rk356x_video, 0, sizeof(rk356x_video)); + memset(edid, 0, sizeof(edid)); + + /* Clear GRF bits that otherwise force HDMI DDC inputs inactive. */ + *(volatile uint32_t *)(RK356X_GRF + 0x364) = (BIT(15) | BIT(14)) << 16; + if (rk356x_display_clocks(74250)) + return -1; + if (!(hdmi_read(0x3004) & 2)) + return -1; + rk356x_video.hpd = 1; + if (!read_edid(edid, &blocks) && edid_select_mode(edid, blocks, + &rk356x_video.mode)) { + rk356x_video.edid_valid = 1; + } else { + edid_fallback_mode(&rk356x_video.mode); + } + rk356x_video.stride = (rk356x_video.mode.hactive * 4 + 63) & ~63U; + if ((uint64_t)rk356x_video.stride * rk356x_video.mode.vactive > + RK356X_FB_END - RK356X_FB_START) + return -1; + if (rk356x_display_clocks(rk356x_video.mode.pixel_clock_khz)) + return -1; + memset((void *)RK356X_FB_START, 0, + (uint64_t)rk356x_video.stride * rk356x_video.mode.vactive); + rk356x_vop2_setup(&rk356x_video.mode, rk356x_video.stride); + hdmi_video_setup(&rk356x_video.mode); + if (hdmi_phy_setup(rk356x_video.mode.pixel_clock_khz)) + return -1; + rk356x_video.active = 1; + return 0; +} diff --git a/src/rk356x/io.c b/src/rk356x/io.c new file mode 100644 index 0000000..2a66859 --- /dev/null +++ b/src/rk356x/io.c @@ -0,0 +1,111 @@ +#include +#include "main.h" +#include "firmware.h" +#include "rk356x.h" + +static uint64_t dram_size; +static uint32_t page_tables[3][1024] __attribute__((aligned(4096))); + +static uint64_t payload_end(void) { + const struct FuPayloadHeader *header = (const void *)_end_of_image; + uint64_t end = RK356X_PAYLOAD; + if (header->magic == 0x08008135 && header->img_size >= sizeof(*header) && + header->img_size < RK356X_STACK_BOTTOM - RK356X_PAYLOAD) + end += header->img_size; + return (end + 0xfff) & ~0xfffULL; +} + +void plat_setup_mmu(void *unused) { + uint8_t *l1 = (uint8_t *)page_tables[0]; + uint8_t *low = (uint8_t *)page_tables[1]; + uint8_t *high = (uint8_t *)page_tables[2]; + (void)unused; + memset(page_tables, 0, sizeof(page_tables)); + + ttbl_table_entry(l1 + 0 * 8, (uintptr_t)low); + ttbl_block_1gb(l1 + 1 * 8, 0x40000000, 3); + ttbl_block_1gb(l1 + 2 * 8, 0x80000000, 3); + ttbl_table_entry(l1 + 3 * 8, (uintptr_t)high); + for (unsigned int i = 0; i < 512; i++) { + uint64_t address = (uint64_t)i << 21; + uint64_t attr = 3; + if ((address >= RK356X_DMA_START && address < RK356X_DMA_END) || + (address >= RK356X_FB_START && address < RK356X_FB_END)) + attr = 2; + ttbl_block_2mb(low + i * 8, address, attr); + } + for (unsigned int i = 0; i < 512; i++) { + uint64_t address = 0xc0000000ULL + ((uint64_t)i << 21); + ttbl_block_2mb(high + i * 8, address, + address >= RK356X_MMIO_START ? 0 : 3); + } + /* 4 KiB granule, 32-bit VA/PA; device, non-cacheable and WB attributes. */ + setup_tt_el3(0x3520, 0xeeff440400ULL, (uintptr_t)l1); + enable_mmu_el3(); +} + +static void map_add(struct FuMemoryMap *map, uint64_t start, uint64_t end, + uint32_t flags) { + if (end <= start) + return; + map->items[map->length].start_addr = start; + map->items[map->length].end_addr = end; + map->items[map->length].flags = flags; + map->items[map->length].pad2 = 0; + map->length++; +} + +void plat_get_mem_map(void *buffer) { + struct FuMemoryMap *map = buffer; + uint64_t ram_end = dram_size ? dram_size : rk356x_detect_dram(); + uint64_t p_end = payload_end(); + if (ram_end > RK356X_MMIO_START) + ram_end = RK356X_MMIO_START; + map->length = 0; + map->pad = 0; + map_add(map, 0, 0x00200000, FU_MEM_ATTR_RESERVED); + map_add(map, 0x00200000, RK356X_PAYLOAD, FU_MEM_ATTR_UNUSED); + map_add(map, RK356X_PAYLOAD, p_end, FU_MEM_ATTR_PAYLOAD); + map_add(map, p_end, RK356X_STACK_BOTTOM, FU_MEM_ATTR_UNUSED); + map_add(map, RK356X_STACK_BOTTOM, RK356X_STACK_TOP, FU_MEM_ATTR_RESERVED); + map_add(map, RK356X_DMA_START, RK356X_DMA_END, FU_MEM_ATTR_RESERVED); + map_add(map, RK356X_DMA_END, RK356X_FB_START, FU_MEM_ATTR_UNUSED); + map_add(map, RK356X_FB_START, RK356X_FB_END, FU_MEM_ATTR_FRAMEBUFFER); + map_add(map, RK356X_FB_END, ram_end, FU_MEM_ATTR_UNUSED); + map_add(map, RK356X_MMIO_START, 0x100000000ULL, FU_MEM_ATTR_MMIO); +} + +volatile void *plat_get_uart_base(void) { + return (volatile void *)RK356X_UART2; +} + +uintptr_t plat_get_framebuffer(void) { + return rk356x_video.active ? RK356X_FB_START : 0; +} + +void plat_get_screen(uint32_t *width, uint32_t *height, uint32_t *stride) { + if (rk356x_video.active) { + *width = rk356x_video.mode.hactive; + *height = rk356x_video.mode.vactive; + *stride = rk356x_video.stride; + } else { + *width = *height = *stride = 0; + } +} + +uint64_t rk356x_ram_size(void) { + if (!dram_size) + dram_size = rk356x_detect_dram(); + return dram_size; +} + +void plat_reset(void) { + *(volatile uint32_t *)(RK356X_PMUGRF + 0x200) = 0xef08a53c; + __asm__ volatile("dsb sy"); + *(volatile uint32_t *)(RK356X_CRU + 0xd4) = 0xfdb9; + halt(); +} + +void plat_shutdown(void) { + plat_reset(); +} diff --git a/src/rk356x/rk356x.dtsi b/src/rk356x/rk356x.dtsi new file mode 100644 index 0000000..3b85c86 --- /dev/null +++ b/src/rk356x/rk356x.dtsi @@ -0,0 +1,78 @@ +/ { + #address-cells = <2>; + #size-cells = <2>; + + chosen { + stdout-path = "serial2:1500000n8"; + }; + + gic: interrupt-controller@fd400000 { + compatible = "arm,gic-v3"; + reg = <0x0 0xfd400000 0x0 0x10000>, + <0x0 0xfd460000 0x0 0x80000>; + interrupt-controller; + #interrupt-cells = <3>; + }; + + gpio0: gpio@fdd60000 { + compatible = "rockchip,gpio-bank"; + reg = <0x0 0xfdd60000 0x0 0x100>; + gpio-controller; + #gpio-cells = <2>; + }; + + gpio1: gpio@fe740000 { + compatible = "rockchip,gpio-bank"; + reg = <0x0 0xfe740000 0x0 0x100>; + gpio-controller; + #gpio-cells = <2>; + }; + + gpio2: gpio@fe750000 { + compatible = "rockchip,gpio-bank"; + reg = <0x0 0xfe750000 0x0 0x100>; + gpio-controller; + #gpio-cells = <2>; + }; + + gpio3: gpio@fe760000 { + compatible = "rockchip,gpio-bank"; + reg = <0x0 0xfe760000 0x0 0x100>; + gpio-controller; + #gpio-cells = <2>; + }; + + serial2: serial@fe660000 { + compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart"; + reg = <0x0 0xfe660000 0x0 0x100>; + reg-shift = <2>; + reg-io-width = <4>; + current-speed = <1500000>; + status = "okay"; + }; + + vop: vop@fe040000 { + compatible = "rockchip,rk3568-vop"; + reg = <0x0 0xfe040000 0x0 0x3000>; + status = "okay"; + }; + + hdmi: hdmi@fe0a0000 { + compatible = "rockchip,rk3568-dw-hdmi"; + reg = <0x0 0xfe0a0000 0x0 0x20000>; + reg-io-width = <4>; + status = "okay"; + }; + + usb_host0_ohci: usb@fd840000 { + compatible = "generic-ohci"; + reg = <0x0 0xfd840000 0x0 0x40000>; + status = "disabled"; + }; + + usb_host1_ohci: usb@fd8c0000 { + compatible = "generic-ohci"; + reg = <0x0 0xfd8c0000 0x0 0x40000>; + status = "disabled"; + }; +}; diff --git a/src/rk356x/rk356x.h b/src/rk356x/rk356x.h new file mode 100644 index 0000000..23498fd --- /dev/null +++ b/src/rk356x/rk356x.h @@ -0,0 +1,71 @@ +#ifndef RK356X_H +#define RK356X_H + +#include +#include "edid.h" + +#define RK356X_PMUGRF 0xfdc20000UL +#define RK356X_GRF 0xfdc60000UL +#define RK356X_SGRF 0xfdd18000UL +#define RK356X_PMUCRU 0xfdd00000UL +#define RK356X_CRU 0xfdd20000UL +#define RK356X_PMU 0xfdd90000UL +#define RK356X_UART2 0xfe660000UL +#define RK356X_VOP2 0xfe040000UL +#define RK356X_HDMI 0xfe0a0000UL +#define RK356X_OHCI0 0xfd840000UL +#define RK356X_OHCI1 0xfd8c0000UL + +#define RK356X_PAYLOAD 0x00a00000UL +#define RK356X_STACK_BOTTOM 0x07ff0000UL +#define RK356X_STACK_TOP 0x08000000UL +#define RK356X_DMA_START 0x08000000UL +#define RK356X_DMA_END 0x08400000UL +#define RK356X_DMA_LIMIT 0x08300000UL +#define RK356X_DTB 0x08300000UL +#define RK356X_SHARED 0x083f0000UL +#define RK356X_FB_START 0x10000000UL +#define RK356X_FB_END 0x12000000UL +#define RK356X_MMIO_START 0xf0000000UL + +struct Rk356xGpioPin { + uint8_t bank; + uint8_t pin; + uint8_t active_high; + uint8_t valid; +}; + +struct Rk356xBoard { + const char *name; + const char *vendor; + const char *product; + const char *soc; + const char *connector_notes; + struct Rk356xGpioPin leds[2]; + struct Rk356xGpioPin usb_vbus[2]; + uint8_t ohci_mask; +}; + +struct Rk356xVideo { + struct VideoMode mode; + uint32_t stride; + uint8_t hpd; + uint8_t edid_valid; + uint8_t active; +}; + +extern const struct Rk356xBoard *rk356x_board; +extern struct Rk356xVideo rk356x_video; + +void rk356x_enable_uart(void); +void rk356x_setup_security(void); +int rk356x_enable_vo_domain(void); +void rk356x_gpio_output(struct Rk356xGpioPin pin, int asserted); +uint64_t rk356x_detect_dram(void); +void rk356x_set_dtb(const void *data, unsigned int size); +int rk356x_display_init(void); +void rk356x_usb_init(void); +int rk356x_board_entry(const struct Rk356xBoard *board, + const void *dtb, unsigned int dtb_size); + +#endif diff --git a/src/rk356x/roc3566.dts b/src/rk356x/roc3566.dts new file mode 100644 index 0000000..31a04a4 --- /dev/null +++ b/src/rk356x/roc3566.dts @@ -0,0 +1,28 @@ +/dts-v1/; + +#include "rk356x.dtsi" + +/ { + model = "Firefly ROC-RK3566-PC"; + compatible = "firefly,roc-rk3566-pc", "rockchip,rk3566"; + + leds { + compatible = "gpio-leds"; + user-led { + label = "user-led"; + gpios = <&gpio0 27 0>; + }; + }; + + usb-host-vbus { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_usb30_host"; + gpio = <&gpio0 21 0>; + enable-active-high; + regulator-always-on; + }; +}; + +&usb_host0_ohci { + status = "okay"; +}; diff --git a/src/rk356x/rock3a.dts b/src/rk356x/rock3a.dts new file mode 100644 index 0000000..751260e --- /dev/null +++ b/src/rk356x/rock3a.dts @@ -0,0 +1,40 @@ +/dts-v1/; + +#include "rk356x.dtsi" + +/ { + model = "Radxa ROCK 3A"; + compatible = "radxa,rock3a", "rockchip,rk3568"; + + leds { + compatible = "gpio-leds"; + user-led { + label = "user"; + gpios = <&gpio0 15 0>; + }; + }; + + usb-host-vbus { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_usb_host"; + gpio = <&gpio0 6 0>; + enable-active-high; + regulator-always-on; + }; + + usb-hub-vbus { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_usb_hub"; + gpio = <&gpio0 29 0>; + enable-active-high; + regulator-always-on; + }; +}; + +&usb_host0_ohci { + status = "okay"; +}; + +&usb_host1_ohci { + status = "okay"; +}; diff --git a/src/rk356x/sgrf.c b/src/rk356x/sgrf.c new file mode 100644 index 0000000..8edd541 --- /dev/null +++ b/src/rk356x/sgrf.c @@ -0,0 +1,11 @@ +#include +#include "rk356x.h" + +void rk356x_setup_security(void) { + volatile uint32_t *soc_con4 = (volatile uint32_t *)(RK356X_SGRF + 0x10); + uint32_t value = *soc_con4; + + /* Match the RK3568 early-boot policy: make eMMC/SDMMC0 transactions NS. */ + value &= ~((3U << 11) | (1U << 4)); + *soc_con4 = value; +} diff --git a/src/rk356x/usb.c b/src/rk356x/usb.c new file mode 100644 index 0000000..9473c7b --- /dev/null +++ b/src/rk356x/usb.c @@ -0,0 +1,56 @@ +#include "main.h" +#include "input.h" +#include "ohci.h" +#include "rk356x.h" + +static void set_reset(unsigned int id, int asserted) { + rk_clr_set_bits((volatile void *)(RK356X_CRU + 0x400 + (id / 16) * 4), + id % 16, id % 16, asserted); +} + +static void usb2phy1_enable(void) { + volatile uint32_t *phy = (volatile uint32_t *)0xfe8b0000; + /* Enable the 480 MHz output and return both UTMI ports to normal mode. */ + phy[0x008 / 4] = (1U << (16 + 4)); + phy[0x000 / 4] = (0x1ffU << 16) | 0x1d1; + phy[0x004 / 4] = (0x1ffU << 16) | 0x1d1; +} + +void rk356x_usb_init(void) { + puts(rk356x_board->connector_notes); + /* 200 MHz ACLK, 100 MHz HCLK/PCLK, plus both OHCI companion gates. */ + rk_clr_set_bits((volatile void *)(RK356X_CRU + 0x100 + 32 * 4), + 7, 0, (1 << 4) | (1 << 2) | 1); + rk_clr_set_bits((volatile void *)(RK356X_CRU + 0x300 + 16 * 4), + 2, 0, 0); + rk_clr_set_bits((volatile void *)(RK356X_CRU + 0x300 + 16 * 4), + 15, 12, 0); + /* xin24m reference for USB2PHY1. */ + rk_clr_set_bits((volatile void *)(RK356X_PMUCRU + 0x180 + 2 * 4), + 2, 2, 0); + for (unsigned int id = 224; id <= 233; id++) + set_reset(id, 1); + set_reset(459, 1); + for (unsigned int id = 467; id <= 469; id++) + set_reset(id, 1); + usleep(10); + for (unsigned int id = 224; id <= 233; id++) + set_reset(id, 0); + set_reset(459, 0); + for (unsigned int id = 467; id <= 469; id++) + set_reset(id, 0); + usb2phy1_enable(); + msleep(2); + + ohci_dma_configure(RK356X_DMA_START, RK356X_DMA_LIMIT); + if (rk356x_board->ohci_mask & 1) { + if (ohci_add_controller(RK356X_OHCI0)) puts("OHCI0 unavailable"); + else puts("OHCI0 ready"); + } + if (rk356x_board->ohci_mask & 2) { + if (ohci_add_controller(RK356X_OHCI1)) puts("OHCI1 unavailable"); + else puts("OHCI1 ready"); + } + input_set_poller(ohci_poll_all); + ohci_poll_all(); +} diff --git a/src/rk356x/vop2.c b/src/rk356x/vop2.c new file mode 100644 index 0000000..2d5da5d --- /dev/null +++ b/src/rk356x/vop2.c @@ -0,0 +1,133 @@ +#include +#include "main.h" +#include "rk356x.h" + +#define BIT(x) (1U << (x)) + +static inline volatile uint32_t *cru(unsigned int offset) { + return (volatile uint32_t *)(RK356X_CRU + offset); +} + +static inline volatile uint32_t *vop(unsigned int offset) { + return (volatile uint32_t *)(RK356X_VOP2 + offset); +} + +static int set_vpll(uint32_t rate_khz) { + volatile uint32_t *pll = cru(40 * 4); + uint32_t post1 = 0, post2 = 0; + uint64_t vco = 0, best_delta = ~(uint64_t)0; + + /* Keep the VCO close to 1.2 GHz, as in Rockchip's 74.25/297 MHz rows. */ + for (uint32_t p1 = 1; p1 <= 7; p1++) { + for (uint32_t p2 = 1; p2 <= p1; p2++) { + uint64_t candidate = (uint64_t)rate_khz * p1 * p2; + uint64_t delta; + if (candidate < 800000 || candidate > 3200000) + continue; + delta = candidate > 1200000 ? candidate - 1200000 : + 1200000 - candidate; + if (delta < best_delta) { + best_delta = delta; + post1 = p1; + post2 = p2; + vco = candidate; + } + } + } + if (!post1) + return -1; + + uint32_t fbdiv = (uint32_t)(vco / 24000); + uint32_t frac = (uint32_t)((((vco % 24000) << 24) + 12000) / 24000); + if (frac == 0x1000000) { + fbdiv++; + frac = 0; + } + if (fbdiv < 16 || fbdiv > 0xfff) + return -1; + + /* RK3328-style VPLL: mode bit 12, RK3036-compatible CON0..CON2. */ + rk_clr_set_bits(cru(0xc0), 12, 12, 0); + rk_clr_set_bits(&pll[0], 14, 0, (post1 << 12) | fbdiv); + rk_clr_set_bits(&pll[1], 5, 0, 1); + rk_clr_set_bits(&pll[1], 8, 6, post2); + rk_clr_set_bits(&pll[1], 12, 12, frac ? 0 : 1); + pll[2] = (pll[2] & ~0xffffffU) | frac; + for (unsigned int timeout = 0; timeout < 24000; timeout++) { + if (pll[1] & BIT(10)) { + rk_clr_set_bits(cru(0xc0), 12, 12, 1); + return 0; + } + } + rk_clr_set_bits(cru(0xc0), 12, 12, 1); + return -1; +} + +static void release_reset(unsigned int id) { + rk_clr_set_bits(cru(0x400 + (id / 16) * 4), id % 16, id % 16, 0); +} + +int rk356x_display_clocks(uint32_t pixel_clock_khz) { + if (rk356x_enable_vo_domain()) + return -1; + /* VO roots, VOP0, HDMI host and HDMI SFR clocks. */ + rk_clr_set_bits(cru(0x300 + 20 * 4), 2, 0, 0); + rk_clr_set_bits(cru(0x300 + 20 * 4), 6, 6, 0); + rk_clr_set_bits(cru(0x300 + 20 * 4), 10, 8, 0); + rk_clr_set_bits(cru(0x300 + 21 * 4), 4, 3, 0); + for (unsigned int id = 256; id <= 265; id++) + release_reset(id); + release_reset(270); + release_reset(271); + if (set_vpll(pixel_clock_khz)) + return -1; + /* ACLK_VOP_PRE = GPLL / 4; DCLK_VOP0 = VPLL / 1. */ + rk_clr_set_bits(cru(0x100 + 38 * 4), 7, 0, (1 << 6) | 3); + rk_clr_set_bits(cru(0x100 + 39 * 4), 11, 0, (1 << 10)); + return 0; +} + +void rk356x_vop2_setup(const struct VideoMode *mode, uint32_t stride) { + uint32_t htotal = mode->hactive + mode->hfront_porch + mode->hsync_len + + mode->hback_porch; + uint32_t vtotal = mode->vactive + mode->vfront_porch + mode->vsync_len + + mode->vback_porch; + uint32_t hact_st = htotal - (mode->hactive + mode->hfront_porch); + uint32_t vact_st = vtotal - (mode->vactive + mode->vfront_porch); + uint32_t polarity = 0; + + if (mode->flags & VIDEO_FLAG_HSYNC_HIGH) polarity |= BIT(4); + if (mode->flags & VIDEO_FLAG_VSYNC_HIGH) polarity |= BIT(5); + + *vop(0x008) = 0; + *vop(0x028) = BIT(1); /* HDMI from VP0. */ + *vop(0x030) = polarity | BIT(28); /* Immediate interface update. */ + *vop(0x600) = 0; + *vop(0x604) = 0x76543102; /* Layer 0 is eSmart0. */ + *vop(0x608) = 0x00000880; /* One layer on VP0; VP1/2 disabled. */ + *vop(0x6e0) = 42U << 24; + *vop(0x6f8) = 20; + + *vop(0xc00 + 0x00) = 0; /* P888, progressive, leave standby. */ + *vop(0xc00 + 0x2c) = 0; + *vop(0xc00 + 0x30) = ((42 + (mode->hactive >> 1) - 1) << 16) | + mode->hsync_len; + *vop(0xc00 + 0x34) = (hact_st << 16) | (hact_st + mode->hactive); + *vop(0xc00 + 0x38) = (vact_st << 16) | (vact_st + mode->vactive); + *vop(0xc00 + 0x3c) = 0x10001000; + *vop(0xc00 + 0x40) = 0; + *vop(0xc00 + 0x48) = (htotal << 16) | mode->hsync_len; + *vop(0xc00 + 0x4c) = (hact_st << 16) | (hact_st + mode->hactive); + *vop(0xc00 + 0x50) = (vtotal << 16) | mode->vsync_len; + *vop(0xc00 + 0x54) = (vact_st << 16) | (vact_st + mode->vactive); + + *vop(0x1800 + 0x10) = 1; /* ARGB/XRGB8888, region enabled. */ + *vop(0x1800 + 0x14) = RK356X_FB_START; + *vop(0x1800 + 0x1c) = stride / 4; + *vop(0x1800 + 0x20) = ((mode->vactive - 1) << 16) | + (mode->hactive - 1); + *vop(0x1800 + 0x24) = ((mode->vactive - 1) << 16) | + (mode->hactive - 1); + *vop(0x1800 + 0x28) = 0; + *vop(0x000) = BIT(15) | BIT(0); +} diff --git a/src/rk356x/yy3568.dts b/src/rk356x/yy3568.dts new file mode 100644 index 0000000..eaff162 --- /dev/null +++ b/src/rk356x/yy3568.dts @@ -0,0 +1,36 @@ +/dts-v1/; + +#include "rk356x.dtsi" + +/ { + model = "Youyeetoo YY3568"; + compatible = "youyeetoo,yy3568", "rockchip,rk3568"; + + leds { + compatible = "gpio-leds"; + work-led { + label = "work"; + gpios = <&gpio3 4 0>; + }; + blue-led { + label = "blue"; + gpios = <&gpio2 10 0>; + }; + }; + + usb-host-vbus { + compatible = "regulator-fixed"; + regulator-name = "vcc5v0_host"; + gpio = <&gpio0 30 0>; + enable-active-high; + regulator-always-on; + }; +}; + +&usb_host0_ohci { + status = "okay"; +}; + +&usb_host1_ohci { + status = "okay"; +}; diff --git a/src/roc3566.c b/src/roc3566.c new file mode 100644 index 0000000..57ac732 --- /dev/null +++ b/src/roc3566.c @@ -0,0 +1,24 @@ +#include "main.h" +#include "rk356x/rk356x.h" +#include "rk356x/roc3566.dtb.out.h" + +static const struct Rk356xBoard board = { + .name = "Firefly ROC-RK3566-PC", + .vendor = "Firefly", + .product = "ROC-RK3566-PC", + .soc = "RK3566", + .connector_notes = "USB2 host is the USB-A companion path", + .leds = { + { 0, RK_PIN_D3, 1, 1 }, + { 0, 0, 0, 0 }, + }, + .usb_vbus = { + { 0, RK_PIN_C5, 1, 1 }, + { 0, 0, 0, 0 }, + }, + .ohci_mask = 1, +}; + +int c_entry(void) { + return rk356x_board_entry(&board, dtb_data, sizeof(dtb_data)); +} diff --git a/src/rock3a.c b/src/rock3a.c new file mode 100644 index 0000000..c40cd39 --- /dev/null +++ b/src/rock3a.c @@ -0,0 +1,24 @@ +#include "main.h" +#include "rk356x/rk356x.h" +#include "rk356x/rock3a.dtb.out.h" + +static const struct Rk356xBoard board = { + .name = "Radxa ROCK 3A", + .vendor = "Radxa", + .product = "ROCK 3A", + .soc = "RK3568", + .connector_notes = "GPIO0_A6 powers the USB hosts; GPIO0_D5 powers the onboard hub", + .leds = { + { 0, RK_PIN_B7, 1, 1 }, + { 0, 0, 0, 0 }, + }, + .usb_vbus = { + { 0, RK_PIN_A6, 1, 1 }, + { 0, RK_PIN_D5, 1, 1 }, + }, + .ohci_mask = 3, +}; + +int c_entry(void) { + return rk356x_board_entry(&board, dtb_data, sizeof(dtb_data)); +} diff --git a/src/yy3568.c b/src/yy3568.c new file mode 100644 index 0000000..d574bce --- /dev/null +++ b/src/yy3568.c @@ -0,0 +1,24 @@ +#include "main.h" +#include "rk356x/rk356x.h" +#include "rk356x/yy3568.dtb.out.h" + +static const struct Rk356xBoard board = { + .name = "Youyeetoo YY3568", + .vendor = "Youyeetoo", + .product = "YY3568", + .soc = "RK3568", + .connector_notes = "GPIO0_D6 powers both supported USB2 host companions", + .leds = { + { 3, RK_PIN_A4, 1, 1 }, + { 2, RK_PIN_B2, 1, 1 }, + }, + .usb_vbus = { + { 0, RK_PIN_D6, 1, 1 }, + { 0, 0, 0, 0 }, + }, + .ohci_mask = 3, +}; + +int c_entry(void) { + return rk356x_board_entry(&board, dtb_data, sizeof(dtb_data)); +} diff --git a/tests/chainload_unit.c b/tests/chainload_unit.c new file mode 100644 index 0000000..7b675d8 --- /dev/null +++ b/tests/chainload_unit.c @@ -0,0 +1,315 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include +#include +#include +#include +#include +#include + +#include "chainload/chainload.h" +#include "chainload/sha256.h" +#include "libfdt.h" + +static const struct ChainAddressRange bl31_ranges[] = { + { 0x00040000, 0x00200000 }, + { 0xfdcc0000, 0xfdcf0000 }, +}; + +static const struct ChainPlatform platform = { + .board = "yy3568", + .soc = "rk3568", + .fit_stage_start = 0x08000000, + .fit_stage_end = 0x08400000, + .params_addr = 0x00100000, + .expected_bl31_entry = 0x00040000, + .expected_bl33_entry = 0x00a00000, + .bl33_limit = 0x00c00000, + .handoff_protocol = CHAIN_HANDOFF_TFA_V1_BL33_EL2, + .bl31_ranges = bl31_ranges, + .bl31_range_count = 2, + .expected_bl31_segments = 2, +}; + +static const struct ChainPlatform rock3a_platform = { + .board = "rock3a", + .soc = "rk3568", + .fit_stage_start = 0x08000000, + .fit_stage_end = 0x08400000, + .params_addr = 0x00100000, + .expected_bl31_entry = 0x00040000, + .expected_bl33_entry = 0x00800000, + .bl33_limit = 0x03f00000, + .handoff_protocol = CHAIN_HANDOFF_TFA_V1_BL33_EL2, + .bl31_ranges = bl31_ranges, + .bl31_range_count = 2, + .expected_bl31_segments = 2, +}; + +uint64_t asm_get_mpidr(void) { + return 0x80000001; +} + +static jmp_buf recovery_jump; +static const char *recovery_stage; +static const char *recovery_reason; + +void debug(const char *text, uint64_t value) { + (void)text; + (void)value; +} + +void dcache_clean(uintptr_t start, uintptr_t end) { + (void)start; + (void)end; +} + +CHAIN_NORETURN void chain_jump_bl31(uintptr_t entry, void *params) { + (void)entry; + (void)params; + abort(); +} + +static void test_recover(const char *stage, const char *reason) { + recovery_stage = stage; + recovery_reason = reason; + longjmp(recovery_jump, 1); +} + +static void require_ok(int result, const char *reason) { + if (result) { + fprintf(stderr, "unexpected parser failure: %s\n", reason); + abort(); + } +} + +static int add_hashed_image(void *fit, const char *name, const char *type, + const char *os, uintptr_t load, uintptr_t entry, + const uint8_t *data, int size) { + uint8_t digest[CHAIN_SHA256_SIZE]; + int images = fdt_path_offset(fit, "/images"); + int image = fdt_add_subnode(fit, images, name); + int hash; + assert(image >= 0); + assert(!fdt_setprop_string(fit, image, "arch", "arm64")); + assert(!fdt_setprop_string(fit, image, "compression", "none")); + assert(!fdt_setprop_string(fit, image, "type", type)); + if (os) + assert(!fdt_setprop_string(fit, image, "os", os)); + if (load != UINTPTR_MAX) + assert(!fdt_setprop_u32(fit, image, "load", (uint32_t)load)); + if (entry != UINTPTR_MAX) + assert(!fdt_setprop_u32(fit, image, "entry", (uint32_t)entry)); + assert(!fdt_setprop(fit, image, "data", data, size)); + chain_sha256(data, (size_t)size, digest); + hash = fdt_add_subnode(fit, image, "hash"); + assert(hash >= 0); + assert(!fdt_setprop_string(fit, hash, "algo", "sha256")); + assert(!fdt_setprop(fit, hash, "value", digest, sizeof(digest))); + return image; +} + +static size_t make_fit_for(uint8_t *fit, size_t capacity, uintptr_t bl33_load, + const char *compatible) { + static const uint8_t atf0[32] = { 0x31, 0x00, 0x40 }; + static const uint8_t atf1[16] = { 0x31, 0x01, 0x5a }; + static const uint8_t uboot[64] = { 0x55, 0x42, 0x4f, 0x4f, 0x54 }; + static const char loadables[] = "uboot\0atf1"; + uint8_t dtb[1024]; + int dtb_size; + int configurations, config; + assert(!fdt_create_empty_tree(dtb, sizeof(dtb))); + assert(!fdt_setprop_string(dtb, 0, "compatible", compatible)); + assert(!fdt_pack(dtb)); + dtb_size = fdt_totalsize(dtb); + assert(!fdt_create_empty_tree(fit, (int)capacity)); + assert(fdt_add_subnode(fit, 0, "images") >= 0); + assert(fdt_add_subnode(fit, 0, "configurations") >= 0); + add_hashed_image(fit, "atf0", "firmware", "arm-trusted-firmware", + 0x00040000, 0x00040000, atf0, sizeof(atf0)); + add_hashed_image(fit, "atf1", "firmware", "arm-trusted-firmware", + 0xfdcc1000, UINTPTR_MAX, atf1, sizeof(atf1)); + add_hashed_image(fit, "uboot", "standalone", "U-Boot", + bl33_load, UINTPTR_MAX, uboot, sizeof(uboot)); + add_hashed_image(fit, "fdt", "flat_dt", NULL, + UINTPTR_MAX, UINTPTR_MAX, dtb, dtb_size); + configurations = fdt_path_offset(fit, "/configurations"); + assert(!fdt_setprop_string(fit, configurations, "default", "conf")); + config = fdt_add_subnode(fit, configurations, "conf"); + assert(config >= 0); + assert(!fdt_setprop_string(fit, config, "firmware", "atf0")); + assert(!fdt_setprop(fit, config, "loadables", loadables, + (int)sizeof(loadables))); + assert(!fdt_setprop_string(fit, config, "fdt", "fdt")); + return (size_t)fdt_totalsize(fit); +} + +static size_t make_fit(uint8_t *fit, size_t capacity) { + return make_fit_for(fit, capacity, 0x00a00000, "youyeetoo,yy3568"); +} + +static void test_mainline_fit_policy(void) { + uint8_t fit[16384]; + struct ChainFitPlan plan; + const char *reason = NULL; + int fdt; + size_t size = make_fit_for(fit, sizeof(fit), 0x00800000, "radxa,rock3a"); + /* Mainline's rockchip-u-boot.dtsi omits arch from flat_dt images. */ + fdt = fdt_path_offset(fit, "/images/fdt"); + assert(fdt >= 0 && !fdt_delprop(fit, fdt, "arch")); + require_ok(chain_fit_parse(fit, size, &rock3a_platform, &plan, &reason), reason); + assert(plan.bl33_entry == 0x00800000); + assert(chain_fit_parse(fit, size, &platform, &plan, &reason)); + assert(reason && strstr(reason, "BL33")); +} + +static void test_valid_and_load(void) { + uint8_t fit[16384], uboot_memory[16384], atf0[64], atf1[64]; + struct ChainFitPlan plan; + const char *reason = NULL; + size_t size = make_fit(fit, sizeof(fit)); + require_ok(chain_fit_parse(fit, size, &platform, &plan, &reason), reason); + assert(plan.image_count == 4 && plan.bl31_count == 2); + assert(plan.bl31_entry == 0x40000 && plan.bl33_entry == 0xa00000); + for (unsigned int i = 0; i < plan.image_count; i++) { + if (plan.images[i].role == CHAIN_IMAGE_BL33) + plan.images[i].load = (uintptr_t)uboot_memory; + else if (plan.images[i].role == CHAIN_IMAGE_BL31) + plan.images[i].load = plan.images[i].entry == 0x40000 ? + (uintptr_t)atf0 : (uintptr_t)atf1; + } + plan.control_fdt = (uintptr_t)(uboot_memory + 128); + plan.control_fdt_capacity = sizeof(uboot_memory) - 128; + require_ok(chain_fit_load(fit, &plan, &reason), reason); + assert(!memcmp(uboot_memory, "UBOOT", 5)); + assert(fdt_path_offset((void *)plan.control_fdt, "/fit-images/uboot") >= 0); + assert(!fdt_getprop((void *)plan.control_fdt, + fdt_path_offset((void *)plan.control_fdt, "/fit-images/uboot"), + "entry-point", NULL)); + assert(fdt_path_offset((void *)plan.control_fdt, "/fit-images/atf1") >= 0); + assert(fdt_path_offset((void *)plan.control_fdt, "/fit-images/atf0") < 0); +} + +static void expect_invalid(void (*mutate)(void *), const char *expected) { + uint8_t fit[16384]; + struct ChainFitPlan plan; + const char *reason = NULL; + size_t size = make_fit(fit, sizeof(fit)); + mutate(fit); + assert(chain_fit_parse(fit, size, &platform, &plan, &reason)); + assert(reason && strstr(reason, expected)); +} + +static void corrupt_hash(void *fit) { + int node = fdt_path_offset(fit, "/images/uboot/hash"); + int len; + uint8_t *value = (uint8_t *)fdt_getprop_w(fit, node, "value", &len); + assert(value && len == 32); + value[0] ^= 1; +} + +static void gzip_image(void *fit) { + int node = fdt_path_offset(fit, "/images/uboot"); + assert(!fdt_setprop_string(fit, node, "compression", "gzip")); +} + +static void external_image(void *fit) { + int node = fdt_path_offset(fit, "/images/uboot"); + assert(!fdt_setprop_u32(fit, node, "data-offset", 0)); +} + +static void unsafe_atf(void *fit) { + int node = fdt_path_offset(fit, "/images/atf1"); + assert(!fdt_setprop_u32(fit, node, "load", 0x20000000)); +} + +static void overlapping_atf(void *fit) { + int node = fdt_path_offset(fit, "/images/atf1"); + assert(!fdt_setprop_u32(fit, node, "load", 0x00040010)); +} + +static void wrong_uboot_entry(void *fit) { + int node = fdt_path_offset(fit, "/images/uboot"); + assert(!fdt_setprop_u32(fit, node, "entry", 0x00a00100)); +} + +static void wrong_uboot_arch(void *fit) { + int node = fdt_path_offset(fit, "/images/uboot"); + assert(!fdt_setprop_string(fit, node, "arch", "arm")); +} + +static void wrong_fdt_arch(void *fit) { + int node = fdt_path_offset(fit, "/images/fdt"); + assert(!fdt_setprop_string(fit, node, "arch", "arm")); +} + +static void missing_hash(void *fit) { + int node = fdt_path_offset(fit, "/images/uboot/hash"); + assert(!fdt_del_node(fit, node)); +} + +static void optee(void *fit) { + int node = fdt_path_offset(fit, "/images/atf1"); + assert(!fdt_setprop_string(fit, node, "os", "op-tee")); +} + +static void missing_split_segment(void *fit) { + int node = fdt_path_offset(fit, "/configurations/conf"); + assert(!fdt_setprop_string(fit, node, "loadables", "uboot")); +} + +static void test_params(void) { + _Alignas(16) unsigned char buffer[1024]; + uint8_t *params = chain_build_bl31_params((uintptr_t)buffer, 0x40000, + 0x2345, 0xa00000, 0xa00000, 0x1234); + assert(params == buffer); + assert(params[0] == 3 && params[1] == 1); + /* The BL33 entry-point pointer is the fourth pointer after the header. */ + uintptr_t ep = ((uintptr_t *)(buffer + 8))[3]; + assert(ep >= (uintptr_t)buffer && ep < (uintptr_t)(buffer + sizeof(buffer))); +} + +static void test_recovery(void) { + struct ChainPlatform recovering = platform; + uint8_t invalid[64] = { 0 }; + recovering.recover = test_recover; + if (!setjmp(recovery_jump)) + chainload_run(&recovering, invalid); + assert(!strcmp(recovery_stage, "stage")); + assert(strstr(recovery_reason, "header")); +} + +int main(void) { + uint8_t fit[16384]; + struct ChainFitPlan plan; + struct ChainPlatform bad_platform; + const char *reason = NULL; + size_t size; + uint8_t digest[32]; + chain_sha256("abc", 3, digest); + assert(!memcmp(digest, + "\xba\x78\x16\xbf\x8f\x01\xcf\xea\x41\x41\x40\xde\x5d\xae\x22\x23" + "\xb0\x03\x61\xa3\x96\x17\x7a\x9c\xb4\x10\xff\x61\xf2\x00\x15\xad", 32)); + test_valid_and_load(); + test_mainline_fit_policy(); + expect_invalid(corrupt_hash, "SHA-256"); + expect_invalid(gzip_image, "compression"); + expect_invalid(external_image, "external"); + expect_invalid(unsafe_atf, "address policy"); + expect_invalid(overlapping_atf, "overlap"); + expect_invalid(wrong_uboot_entry, "BL33"); + expect_invalid(wrong_uboot_arch, "not ARM64"); + expect_invalid(wrong_fdt_arch, "incompatible architecture"); + expect_invalid(missing_hash, "SHA-256"); + expect_invalid(optee, "BL32/OP-TEE"); + expect_invalid(missing_split_segment, "split BL31"); + size = make_fit(fit, sizeof(fit)); + assert(chain_fit_parse(fit, size - 1, &platform, &plan, &reason)); + bad_platform = platform; + bad_platform.params_addr = 0x00a00010; + assert(chain_fit_parse(fit, size, &bad_platform, &plan, &reason)); + assert(strstr(reason, "parameters")); + test_params(); + test_recovery(); + puts("chainloader unit tests passed"); + return 0; +} diff --git a/tests/check.py b/tests/check.py new file mode 100644 index 0000000..cbbf04b --- /dev/null +++ b/tests/check.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +"""Artifact and source-contract checks for the RK356x bring-up.""" + +from __future__ import annotations + +import hashlib +import pathlib +import re +import struct +import subprocess +import sys + +ROOT = pathlib.Path(__file__).resolve().parents[1] + +BLOBS = { + "rk3566_ddr_1056MHz_v1.25.bin": (59392, "c2a1b37673bf03ed338bc39efbe942136459cb3621dad09351144d744d78db26"), + "rk3568_ddr_1560MHz_v1.25.bin": (59392, "ab1d9b822a256b6ef4b3aa54b911c4d1e0faaebc882403c7a6b3efc3e69e07fc"), + "rk356x_usbplug_v1.17.bin": (98708, "4038b7857b840f539760decc0daf1601b8ff61cc17798101e93b11128a7f333e"), + "rk3568_bl31_v1.46.elf": (402376, "c81ac7e8e1fd727cf7f0db62a9aaea760bde2b270e34d98eb264a264b86df749"), +} + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +def check_docs() -> None: + docs = ROOT / "docs" + required = { + "rk356x/index.md", + "rk356x/bare-metal.md", + "rk356x/chainloading.md", + "rk356x/boards/roc3566.md", + "rk356x/boards/yy3568.md", + "rk356x/boards/rock3a.md", + } + markdown = { + path.relative_to(docs).as_posix() + for path in docs.rglob("*.md") + } + require(required <= markdown, "RK356x documentation hierarchy is incomplete") + require(not (docs / "rk356x.md").exists() and + not (docs / "chainloading.md").exists(), + "legacy root-level RK356x documentation was reintroduced") + + for path in docs.rglob("*.md"): + source = path.read_text(encoding="utf-8") + for match in re.finditer(r"\]\(([^)]+)\)", source): + target = match.group(1).split("#", 1)[0] + if not target or target.startswith(("http://", "https://", "mailto:", "/")): + continue + require((path.parent / target).is_file(), + f"{path.relative_to(ROOT)}: broken local link: {target}") + + mkdocs = (ROOT / "mkdocs.yml").read_text(encoding="utf-8") + nav = set(re.findall(r"^\s+-\s+[^:]+:\s+([A-Za-z0-9_./-]+\.md)\s*$", + mkdocs, re.MULTILINE)) + require(nav == markdown, + f"MkDocs navigation mismatch: missing={sorted(markdown - nav)}, " + f"extra={sorted(nav - markdown)}") + + boards = { + "roc3566": ("Firefly ROC-RK3566-PC", "firefly,roc-rk3566-pc"), + "yy3568": ("Youyeetoo YY3568", "youyeetoo,yy3568"), + "rock3a": ("Radxa ROCK 3A", "radxa,rock3a"), + } + packager = (ROOT / "tools" / "release-dist.sh").read_text(encoding="utf-8") + for board, markers in boards.items(): + board_path = docs / "rk356x" / "boards" / f"{board}.md" + board_doc = board_path.read_text(encoding="utf-8") + require(all(marker in board_doc for marker in (board, *markers)), + f"{board}: board documentation lacks identity markers") + require(f"docs/rk356x/boards/{board}.md BOARD.md" in packager, + f"{board}: release does not select its board documentation") + require("docs/rk356x/chainloading.md chainloading.md" in packager, + "release uses a stale RK356x chainloading path") + + +def check_blobs() -> None: + for name, (size, digest) in BLOBS.items(): + data = (ROOT / "img" / name).read_bytes() + require(len(data) == size, f"{name}: expected {size} bytes") + require(hashlib.sha256(data).hexdigest() == digest, f"{name}: SHA-256 mismatch") + + +def image_entry(header: bytes, index: int) -> tuple[int, int]: + entry_size = 88 + offset = 0x78 + index * entry_size + return struct.unpack_from(" None: + image = (ROOT / image_name).read_bytes() + ddr = (ROOT / "img" / ddr_name).read_bytes() + os_image = (ROOT / os_name).read_bytes() + header = image[0x8000 : 0x8800] + signature, _, hash_offset, count, boot_flag = struct.unpack_from(" None: + firmware = (ROOT / f"{board}.bin").read_bytes() + demo = (ROOT / "demo.bin").read_bytes() + combined = (ROOT / f"demo_{board}.bin").read_bytes() + require(combined == firmware + demo, f"demo_{board}.bin is not firmware + payload") + require(struct.unpack_from(" None: + expected = { + "roc3566.dts": ("Firefly ROC-RK3566-PC", "firefly,roc-rk3566-pc", "rockchip,rk3566"), + "yy3568.dts": ("Youyeetoo YY3568", "youyeetoo,yy3568", "rockchip,rk3568"), + "rock3a.dts": ("Radxa ROCK 3A", "radxa,rock3a", "rockchip,rk3568"), + } + for name, strings in expected.items(): + path = ROOT / "src" / "rk356x" / name + source = path.read_text(encoding="utf-8") + for value in strings: + require(value in source, f"{name}: missing {value}") + cpp = subprocess.run( + ["cpp", "-nostdinc", "-undef", "-x", "assembler-with-cpp", str(path)], + check=True, stdout=subprocess.PIPE, + ) + dtb = subprocess.run( + ["dtc", "-I", "dts", "-O", "dtb", "-o", "-"], + input=cpp.stdout, check=True, stdout=subprocess.PIPE, + ) + compiled = subprocess.run( + ["dtc", "-I", "dtb", "-O", "dts"], input=dtb.stdout, + check=True, stdout=subprocess.PIPE, + ).stdout.decode("utf-8") + for value in strings: + require(value in compiled, f"{name}: compiled DTB lost {value}") + for unit in ("serial@fe660000", "hdmi@fe0a0000"): + start = compiled.find(unit + " {") + end = compiled.find("};", start) + require(start >= 0 and end > start and + 'status = "okay"' in compiled[start:end], + f"{name}: enabled {unit} node is missing") + enabled_hosts = ["usb@fd840000"] + if name in ("yy3568.dts", "rock3a.dts"): + enabled_hosts.append("usb@fd8c0000") + for unit in enabled_hosts: + start = compiled.find(unit + " {") + end = compiled.find("};", start) + require(start >= 0 and end > start and + 'status = "okay"' in compiled[start:end], + f"{name}: enabled {unit} node is missing") + if name == "rock3a.dts": + for wiring in ("gpios = <&gpio0 15 0>", + "gpio = <&gpio0 6 0>", + "gpio = <&gpio0 29 0>"): + require(wiring in source, f"ROCK 3A wiring changed: {wiring}") + + +def check_source_contracts() -> None: + makefile = (ROOT / "Makefile").read_text(encoding="utf-8") + rock = (ROOT / "tools" / "rock.c").read_text(encoding="utf-8") + io = (ROOT / "src" / "rk356x" / "io.c").read_text(encoding="utf-8") + ohci = (ROOT / "src" / "ohci.c").read_text(encoding="utf-8") + hdmi = (ROOT / "src" / "rk356x" / "hdmi.c").read_text(encoding="utf-8") + board = (ROOT / "src" / "rk356x" / "board.c").read_text(encoding="utf-8") + for target in ("usb3566", "usb3568", "usb:", "usb-chainload", + "maskrom3566", "maskrom3568"): + marker = target if target.endswith(":") else f"{target}:" + require(marker in makefile, f"Makefile lacks {target.rstrip(':')}") + require(makefile.count("--v2 --ddr img/rk356") >= 4, "RK356x images must use RKNS v2") + require("--rc4-off" in makefile, "xrock flow must explicitly disable RC4") + require("case 0x350a" in rock and 'soc = "RK356x"' in rock, + "rock.out lacks RK356x PID handling") + require("Multiple Rockchip MaskROM devices" in rock and "Unsupported Rockchip PID" in rock, + "rock.out lacks unambiguous selection diagnostics") + for address in ("0x07ff0000", "0x08000000", "0x08400000", "0x10000000", "0x12000000"): + require(address.lower() in (ROOT / "src" / "rk356x" / "rk356x.h").read_text().lower(), + f"layout constant {address} is missing") + require("address >= RK356X_MMIO_START ? 0 : 3" in io, + "MMIO page-table device mapping is missing") + require("enumerate_step" in ohci and "control_status" in ohci and + "retry_after" in ohci, + "OHCI attach/retry must use the nonblocking enumeration state machine") + require("period < c->interval" in ohci and "periodic_stop" in ohci, + "OHCI periodic interval/detach handling is missing") + require("if (!(hdmi_read(0x3004) & 2))" in hdmi and + "rk356x_video.active = 1" in hdmi, + "HDMI headless/active state handling is missing") + require("if (rk356x_video.active)" in board and + "screens->type = FU_SCREEN_XRGB8888" in board, + "FUEFI screen metadata/headless behavior is missing") + + stride_4k = (3840 * 4 + 63) & ~63 + require(stride_4k * 2160 <= 0x12000000 - 0x10000000, + "4K XRGB8888 framebuffer exceeds its arena") + + +def main() -> int: + check_docs() + check_blobs() + check_source_contracts() + check_dts() + for board, ddr in (("roc3566", "rk3566_ddr_1056MHz_v1.25.bin"), + ("yy3568", "rk3568_ddr_1560MHz_v1.25.bin"), + ("rock3a", "rk3568_ddr_1560MHz_v1.25.bin")): + check_payload(board) + check_image(f"{board}.img", ddr, f"{board}.bin") + check_image(f"demo_{board}.img", ddr, f"demo_{board}.bin") + print("RK356x artifact checks passed") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (AssertionError, OSError, subprocess.CalledProcessError) as error: + print(f"check failed: {error}", file=sys.stderr) + raise SystemExit(1) diff --git a/tests/check_chainload.py b/tests/check_chainload.py new file mode 100644 index 0000000..dfa7e39 --- /dev/null +++ b/tests/check_chainload.py @@ -0,0 +1,510 @@ +#!/usr/bin/env python3 +"""Offline isolation/provenance checks and built RK356x chainload checks.""" + +from __future__ import annotations + +import argparse +import copy +import hashlib +import importlib.util +import json +import os +import pathlib +import struct +import subprocess +import sys +import tempfile + +ROOT = pathlib.Path(__file__).resolve().parents[1] +PINNED_UBOOT = "39cd993e5d6296635438e84f4576b3a9bf76f86e" +PINNED_MAINLINE_UBOOT = "88dc2788777babfd6322fa655df549a019aa1e69" +PINNED_ARMBIAN = "587b6f2c0a867859ca3f323f6008bee9e3ef1553" +PINNED_RKBIN = "ecb4fcbe954edf38b3ae037d5de6d9f5bccf81f4" +PINNED_XROCK = "b90d3ba8f0a48320e3888701f7e66e0e4e038bbb" +PINNED_RKDEVELOPTOOL = "304f073752fd25c854e1bcf05d8e7f925b1f4e14" +BL31_HASH = "c81ac7e8e1fd727cf7f0db62a9aaea760bde2b270e34d98eb264a264b86df749" + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +def parse_load_segments(data: bytes) -> list[tuple[int, int, int]]: + require(data[:4] == b"\x7fELF" and data[4] == 2 and data[5] == 1, + "BL31 is not a little-endian ELF64 image") + phoff = struct.unpack_from(" int: + return (value + boundary - 1) & ~(boundary - 1) + + +def check_rkns_v2(raw: bytes, ddr: bytes, payload: bytes) -> None: + """Validate the pinned U-Boot RKNS v2 rksd byte layout.""" + ddr_size = align(len(ddr), 2048) + payload_size = align(len(payload), 2048) + require(len(raw) == 2048 + ddr_size + payload_size, + "RKNS v2 ID block has an unexpected size") + require(len(raw) % 2048 == 0, "RKNS v2 ID block is not 2-KiB aligned") + magic, reserved, size_images, boot_flag = struct.unpack_from(" None: + ddr = (ROOT / "img" / "rk3568_ddr_1560MHz_v1.25.bin").read_bytes() + artifacts = manifest["artifacts"] + assert isinstance(artifacts, dict) + payload = (ROOT / str(artifacts["binary"])).read_bytes() + idblock = (ROOT / str(artifacts["idblock"])).read_bytes() + spi = (ROOT / str(artifacts["spi_nor"])).read_bytes() + sd = (ROOT / str(artifacts["image"])).read_bytes() + check_rkns_v2(idblock, ddr, payload) + require(sd[:64 * 512] == bytes(64 * 512), + f"{board}: SD convenience image does not leave LBA 0-63 empty") + require(sd[64 * 512:] == idblock, + "SD convenience image does not contain the validated ID block at LBA 0x40") + require(len(spi) == len(idblock) * 2, + "rkspi image does not use the first-2-KiB-of-each-4-KiB layout") + for offset in range(0, len(idblock), 2048): + spi_offset = offset * 2 + require(spi[spi_offset:spi_offset + 2048] == idblock[offset:offset + 2048], + f"rkspi data mismatch at raw offset 0x{offset:x}") + require(spi[spi_offset + 2048:spi_offset + 4096] == bytes(2048), + f"rkspi padding is nonzero at raw offset 0x{offset:x}") + + +def load_manifests() -> dict[str, dict[str, object]]: + subprocess.run( + [sys.executable, str(ROOT / "tools" / "chainload-manifest.py"), + "validate", "--all"], check=True + ) + paths = sorted((ROOT / "config" / "chainload").glob("*.json")) + require([item.name for item in paths] == ["rock3a.json", "yy3568.json"], + "chainloader board list changed without CI/release review") + unknown = subprocess.run( + [sys.executable, str(ROOT / "tools" / "chainload-manifest.py"), + "validate", "not-a-board"], text=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + require(unknown.returncode != 0 and "has no chainload manifest" in unknown.stderr, + "unknown chainloader board was accepted") + return {path.stem: json.loads(path.read_text(encoding="utf-8")) for path in paths} + + +def check_manifests(manifests: dict[str, dict[str, object]]) -> None: + expected = { + "yy3568": ("vendor-fit", PINNED_UBOOT, "next-dev-v2024.10", + "0x00a00000", "0x00c00000", { + "nvme": ["nvme0", "nvme1"], "sd": ["mmc1"], + "usb": ["usb0"], "emmc": ["mmc0"], + }), + "rock3a": ("mainline-fit", PINNED_MAINLINE_UBOOT, "v2026.04", + "0x00800000", "0x03f00000", { + "nvme": ["nvme"], "sd": ["mmc1"], + "usb": ["usb"], "emmc": ["mmc0"], + }), + } + artifacts: set[str] = set() + for board, values in expected.items(): + manifest = manifests[board] + backend, commit, ref, load, stack, targets = values + require(manifest["schema"] == 3 and manifest["board"] == board and + manifest["soc"] == "rk3568" and manifest["platform"] == "rk3568", + f"{board}: manifest identity/schema mismatch") + uboot = manifest["uboot"] + assert isinstance(uboot, dict) + require(uboot["backend"] == backend and uboot["commit"] == commit and + uboot["ref"] == ref and uboot["armbian_commit"] == PINNED_ARMBIAN, + f"{board}: U-Boot/Armbian provenance changed") + if board == "rock3a": + require(uboot["armbian_path"] == "config/boards/rock-3a.conf", + "ROCK 3A Armbian board reference changed") + bl31 = manifest["bl31"] + assert isinstance(bl31, dict) + require(bl31["rkbin_commit"] == PINNED_RKBIN and + bl31["sha256"] == BL31_HASH, f"{board}: BL31 provenance changed") + layout = manifest["layout"] + assert isinstance(layout, dict) + require(layout["stage_limit"] == "0x00040000" and + layout["bl33_load"] == load and layout["bl33_stack"] == stack and + layout["expected_bl31_segments"] == 6, + f"{board}: memory policy changed without review") + policy = manifest["boot_policy"] + assert isinstance(policy, dict) + scan = policy["automatic_scan"] + assert isinstance(scan, dict) + require(scan["order"] == ["nvme", "sd", "usb", "emmc"] and + scan["targets"] == targets and + policy["interactive_only"] == ["spi"] and + policy["baud_rate"] == 1500000, + f"{board}: automatic scan policy is not board-scoped") + host_tools = manifest["host_tools"] + assert isinstance(host_tools, dict) + require(host_tools["xrock"]["commit"] == PINNED_XROCK and + host_tools["rkdeveloptool"]["commit"] == PINNED_RKDEVELOPTOOL, + f"{board}: host-tool compatibility pin changed") + board_artifacts = manifest["artifacts"] + assert isinstance(board_artifacts, dict) + for artifact in board_artifacts.values(): + require(str(artifact) not in artifacts, + f"cross-board artifact collision: {artifact}") + artifacts.add(str(artifact)) + + +def check_manifest_rejections(manifests: dict[str, dict[str, object]]) -> None: + spec = importlib.util.spec_from_file_location( + "chainload_manifest_test", ROOT / "tools" / "chainload-manifest.py") + require(spec is not None and spec.loader is not None, + "cannot import chainload manifest validator") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + with tempfile.TemporaryDirectory(prefix="rk-chainload-manifest-") as temporary: + manifest_dir = pathlib.Path(temporary) + module.MANIFEST_DIR = manifest_dir + + def rejected(change, description: str) -> None: + fixtures = copy.deepcopy(manifests) + change(fixtures) + for board, data in fixtures.items(): + (manifest_dir / f"{board}.json").write_text( + json.dumps(data), encoding="utf-8") + try: + module.validate_all() + except module.ManifestError: + return + raise AssertionError(f"manifest validator accepted {description}") + + rejected(lambda items: items["rock3a"].update(board="yy3568"), + "a mismatched board name") + rejected(lambda items: items["rock3a"]["uboot"].update(backend="unknown"), + "an unsupported U-Boot backend") + rejected(lambda items: items["rock3a"]["uboot"].update( + overlay="config/chainload/yy3568/overlay"), + "another board's overlay") + rejected(lambda items: items["rock3a"]["uboot"].update( + config_fragment="../yy3568.config"), "a path escape") + rejected(lambda items: items["rock3a"]["artifacts"].update( + binary="uboot_yy3568.bin"), "a cross-board artifact") + rejected(lambda items: items["rock3a"]["layout"].update( + fit_stage_start="0x00800000"), "overlapping memory ranges") + rejected(lambda items: items["rock3a"]["boot_media"]["emmc"].update( + artifact="uboot_yy3568_idbloader.img"), "cross-board media") + rejected(lambda items: items["rock3a"]["boot_media"]["spi-nor"].update( + unknown_policy=True), "an unknown media policy field") + rejected(lambda items: items["rock3a"]["boot_policy"]["automatic_scan"].update( + order=["nvme", "usb", "sd", "emmc"]), "an incorrect OS scan order") + rejected(lambda items: items["rock3a"]["boot_policy"]["automatic_scan"] + ["targets"].update(emmc=["mmc1"]), + "one MMC target assigned to both SD and eMMC") + rejected(lambda items: items["rock3a"]["boot_policy"]["automatic_scan"] + ["targets"].update(sd=["usb0"]), + "a USB device assigned to the SD group") + rejected(lambda items: items["rock3a"].pop("boot_policy"), + "a missing required policy") + + +def check_bl31() -> None: + blob = (ROOT / "img" / "rk3568_bl31_v1.46.elf").read_bytes() + require(len(blob) == 402376, "BL31 size mismatch") + require(hashlib.sha256(blob).hexdigest() == BL31_HASH, "BL31 hash mismatch") + ranges = ((0x40000, 0x200000), (0xFDCC0000, 0xFDCF0000)) + segments = parse_load_segments(blob) + require(len(segments) == 6, "expected exactly six split BL31 load segments") + for address, _, memory_size in segments: + end = address + memory_size + require(any(address >= start and end <= limit for start, limit in ranges), + f"BL31 segment 0x{address:x}-0x{end:x} violates manifest policy") + + +def check_isolation() -> None: + makefile = (ROOT / "Makefile").read_text(encoding="utf-8") + normal_board = (ROOT / "src" / "rk356x" / "board.c").read_text(encoding="utf-8") + loader = (ROOT / "src" / "chainload" / "loader.c").read_text(encoding="utf-8") + linker = (ROOT / "Chainload-rk356x.ld").read_text(encoding="utf-8") + builder = (ROOT / "tools" / "build-chainload-uboot.sh").read_text(encoding="utf-8") + compat = (ROOT / "src" / "chainload" / "compat.c").read_text(encoding="utf-8") + media_builder = (ROOT / "tools" / "build-chainload-media.sh").read_text(encoding="utf-8") + flasher = (ROOT / "tools" / "flash-chainload.sh").read_text(encoding="utf-8") + chainfit = (ROOT / "tools" / "chainfit.c").read_text(encoding="utf-8") + require("build/chainload/$(1)/obj/" in makefile and + "CHAINLOAD_BOARDS := yy3568 rock3a" in makefile, + "chainloader objects lack generated board/variant namespaces") + for symbol in ("YY3568_OBJ :=", "ROCK3A_OBJ :="): + line = next(item for item in makefile.splitlines() if item.startswith(symbol)) + require("chain" not in line.lower(), f"normal {symbol} firmware inherited chainloader objects") + require("chainload" not in normal_board and "FIT" not in normal_board, + "normal RK356x entry gained FIT auto-detection") + common_list = makefile[makefile.index("CHAINLOAD_SRC :="):makefile.index("CHAINLOAD_CFLAGS :=")] + chainfit_rule = makefile[makefile.index("tools/chainfit.out:"): + makefile.index("\nchainload:")] + for excluded in ("firmware.c", "ohci.c", "hid_keyboard.c", "hdmi.c", "vop2.c", "demo/"): + require(excluded not in common_list, f"dedicated chainloader includes {excluded}") + require("ASSERT(_end_of_image < 0x00040000" in linker, + "chainloader link limit is missing") + require("first BL31 load address\");" not in linker, + "chainloader ASSERT uses GNU ld-incompatible trailing semicolon") + require("size_t strnlen(const char *s, size_t maxlen)" in compat, + "chainloader libfdt compatibility layer lacks strnlen") + require("-D_POSIX_C_SOURCE=200809L" in makefile and + "src/chainload/loader.c src/chainload/compat.c" in makefile, + "host chainloader tests do not exercise the strnlen compatibility layer") + require("-D_POSIX_C_SOURCE=200809L" in chainfit_rule and + "src/chainload/compat.c" in chainfit_rule, + "chainfit host tool lacks the strnlen compatibility contract") + require(loader.index("dcache_clean") < loader.index("chain_jump_bl31"), + "loaded ranges are not cleaned before the BL31 branch") + require("fdt_check_header" in loader and "fit_stage_start" in loader, + "FIT is not staged before loading") + require("vendor-fit)" in builder and "mainline-fit)" in builder and + "u-boot.bin tools u-boot.its u-boot.dtb" in builder and + 'CFLAGS="$vendor_flags" KCFLAGS="$vendor_flags"' in builder and + "fix-u-boot-itb-dependency-on-u-boot-dtb.patch" in + (ROOT / "config" / "chainload" / "yy3568" / "overlay" / + "README.rk-chainload").read_text(encoding="utf-8") and + "dumpimage -l u-boot.itb" in builder and "zero-byte image" in builder and + '[[ -x "$snapshot/tools/mkimage" ]]' in builder and + "all tools/mkimage" not in builder, + "U-Boot builder does not isolate vendor/mainline FIT backends") + require('"$mkimage" -n "$soc" -T rksd' in media_builder and + '"$mkimage" -n "$soc" -T rkspi' in media_builder, + "chainload media is not generated by pinned U-Boot mkimage") + require("makeboot.out" not in media_builder, + "chainload media accidentally uses the normal firmware packer") + for marker in ("cs 1", "cs 9", "check-partition-overlap.py", "cmp \"$artifact\"", + "complete-spi-nor.bin", "emmc-lba0-63.bin"): + require(marker in (ROOT / "docs" / "rk356x" / "chainloading.md").read_text(encoding="utf-8") + or marker in flasher, + f"guarded media installer lacks contract marker: {marker}") + platform = (ROOT / "src" / "chainload" / "rk3568.c").read_text() + require("0xfdcc0010" in platform and 'return "source=spi-nor"' in platform and + 'return "source=emmc"' in platform and 'return "source=sd"' in platform and + 'return "source=usb"' in platform, + "RK3568 chainloader lacks BootROM-source decoding") + require("CHAIN_EXPECTED_BL33_ENTRY" in platform and + "CHAIN_BL31_RANGE_INITIALIZER" in platform, + "chainloader platform does not consume the generated board descriptor") + require("BootROM download marker" in platform, + "validation recovery does not document MaskROM reset") + require("deprecated alias" in makefile and "usb-chainload" in makefile, + "ambiguous RK3568 USB aliases were not replaced safely") + require("chainfit-args" in makefile and "yy3568" not in chainfit and + "rock3a" not in chainfit, + "host FIT validation bypasses manifest-derived board policy") + + +def check_overlays() -> None: + base = ROOT / "config" / "chainload" / "yy3568" / "overlay" + config = (base / "configs" / "yy3568-rk3568_defconfig").read_text() + dts = (base / "arch" / "arm" / "dts" / "rk3568-yy3568.dts").read_text() + header = (base / "include" / "configs" / "yy3568.h").read_text() + board_makefile = (base / "board" / "rockchip" / "yy3568" / + "Makefile").read_text() + board_source = (base / "board" / "rockchip" / "yy3568" / + "yy3568.c").read_text() + board_kconfig = (base / "board" / "rockchip" / "evb_rk3568" / + "Kconfig").read_text() + for setting in ( + "CONFIG_BOOTDELAY=3", "CONFIG_BAUDRATE=1500000", "CONFIG_NVME=y", + "CONFIG_CMD_NVME=y", "CONFIG_CMD_EXT4=y", "CONFIG_CMD_FAT=y", + "CONFIG_CMD_SF=y", "CONFIG_CMD_MMC=y", "CONFIG_CMD_USB=y", + "CONFIG_USB_STORAGE=y", "CONFIG_EFI_LOADER=y", "CONFIG_TARGET_YY3568=y", + "# CONFIG_DM_CHARGE_DISPLAY is not set", + "# CONFIG_CHARGE_ANIMATION is not set", + ): + require(setting in config, f"YY3568 U-Boot config lacks {setting}") + require("setenv boot_targets nvme0 nvme1 mmc1 usb0 mmc0; run distro_bootcmd" in header, + "YY3568 board header does not enforce its automatic scan order") + require('default "yy3568" if TARGET_YY3568' in board_kconfig and + "obj-y += yy3568.o" in board_makefile and + '#include "../evb_rk3568/evb_rk3568.c"' in board_source, + "YY3568 target lacks its isolated vendor board shim") + for alias in ("mmc0 = &sdhci;", "mmc1 = &sdmmc0;", "mmc2 = &sdmmc2;"): + require(alias in dts, f"YY3568 U-Boot DTS lacks board alias {alias}") + for node in ("&pcie2x1", "&pcie3x2", "vcc3v3_pcie", "reset-gpios"): + require(node in dts, f"YY3568 U-Boot DTS lacks {node}") + for marker in ("&sfc", "&fspi_pins", "&spi_nor", "&spi_nand", "&sdhci", + "&emmc_bus8", "bus-width = <8>"): + require(marker in dts, f"YY3568 storage DTS lacks {marker}") + require(dts[dts.index("&spi_nand"):].split("};", 1)[0].find('status = "disabled"') >= 0, + "YY3568 SPI-NAND child is not disabled") + require(dts[dts.index("&spi_nor"):].split("};", 1)[0].find('status = "okay"') >= 0, + "YY3568 JEDEC SPI-NOR child is not enabled") + require("youyeetoo,yy3568" in dts and "rockchip,rk3568" in dts, + "YY3568 U-Boot DTS identity mismatch") + + rock = ROOT / "config" / "chainload" / "rock3a" / "overlay" + fragment = (rock / "configs" / "rock3a-chainload.config").read_text() + config_header = (rock / "include" / "configs" / + "evb_rk3568.h").read_text() + uboot_dtsi = (rock / "arch" / "arm" / "dts" / + "rk3568-rock-3a-u-boot.dtsi").read_text() + source_readme = (rock / "README.rk-chainload").read_text() + for setting in ( + "CONFIG_BOOTDELAY=3", "CONFIG_BAUDRATE=1500000", + "CONFIG_BOOTSTD_DEFAULTS=y", + "CONFIG_BOOTMETH_EXTLINUX=y", "CONFIG_BOOTMETH_SCRIPT=y", + "CONFIG_CMD_NVME=y", "CONFIG_CMD_EXT4=y", "CONFIG_CMD_FAT=y", + "CONFIG_CMD_SF=y", "CONFIG_CMD_MMC=y", "CONFIG_CMD_USB=y", + "CONFIG_USB_STORAGE=y", "CONFIG_NVME_PCI=y", "CONFIG_EFI_LOADER=y", + ): + require(setting in fragment, f"ROCK 3A config fragment lacks {setting}") + require('#define BOOT_TARGETS "nvme mmc1 usb mmc0"' in config_header and + "#include " in config_header and + "ROCKCHIP_DEVICE_SETTINGS" in config_header, + "ROCK 3A board header does not enforce its automatic scan order") + require("/delete-property/ fit,external-offset" in uboot_dtsi and + '#include "rk356x-u-boot.dtsi"' in uboot_dtsi, + "ROCK 3A does not preserve upstream DTS while forcing inline FIT data") + require("rock-3a-rk3568_defconfig" in source_readme and + "configs/rock3a-chainload.config" in source_readme and + "include/configs/evb_rk3568.h" in source_readme, + "ROCK 3A corresponding source lacks reproducible build instructions") + + +def check_built(board: str) -> None: + manifests = load_manifests() + require(board in manifests, "an unsupported board reached chainload checks") + manifest = manifests[board] + artifacts = manifest["artifacts"] + uboot_policy = manifest["uboot"] + assert isinstance(artifacts, dict) and isinstance(uboot_policy, dict) + stage = ROOT / "build" / "chainload" / board / "stage.bin" + require(stage.is_file() and stage.stat().st_size < 0x40000, + f"{board}: chainloader stage exceeds its dedicated low-memory limit") + fit = ROOT / str(artifacts["fit"]) + binary = ROOT / str(artifacts["binary"]) + require(fit.is_file() and binary.is_file(), "chainload outputs are incomplete") + require(binary.read_bytes() == stage.read_bytes() + fit.read_bytes(), + f"{board}: combined image is not stage + pinned FIT") + fit_args = subprocess.run( + [sys.executable, str(ROOT / "tools" / "chainload-manifest.py"), + "chainfit-args", board], check=True, text=True, + stdout=subprocess.PIPE, + ).stdout.split() + subprocess.run([str(ROOT / "tools" / "chainfit.out"), board, str(fit), + *fit_args], check=True) + if os.environ.get("UBOOT_ITB"): + return + source = ROOT / "build" / "chainload" / board / "source" + config = (source / ".config").read_text(encoding="utf-8") + common_settings = ( + "CONFIG_BOOTDELAY=3", "CONFIG_BAUDRATE=1500000", "CONFIG_CMD_NVME=y", + "CONFIG_CMD_MMC=y", "CONFIG_CMD_USB=y", "CONFIG_USB_STORAGE=y", + "CONFIG_EFI_LOADER=y", "CONFIG_CMD_BOOTEFI=y", + ) + backend_settings = { + "vendor-fit": ("CONFIG_TARGET_YY3568=y", "CONFIG_NVME=y", "CONFIG_CMD_SF=y"), + "mainline-fit": ("CONFIG_TEXT_BASE=0x00800000", "CONFIG_NVME_PCI=y", + 'CONFIG_SYS_CONFIG_NAME="evb_rk3568"', + "CONFIG_BOOTMETH_EXTLINUX=y", "CONFIG_BOOTMETH_EFILOADER=y"), + } + for setting in common_settings + backend_settings[str(uboot_policy["backend"])]: + require(setting in config, f"{board}: built U-Boot config lacks {setting}") + uboot = (source / "u-boot.bin").read_bytes() + if board == "yy3568": + require(b"setenv boot_targets nvme0 nvme1 mmc1 usb0 mmc0; run distro_bootcmd" in uboot, + "YY3568 U-Boot does not contain its automatic scan command") + else: + require(b"boot_targets=nvme mmc1 usb mmc0" in uboot, + "ROCK 3A U-Boot does not contain its automatic scan environment") + require(b"boot_targets=mmc1 mmc0 nvme scsi usb pxe dhcp spi" not in uboot, + "ROCK 3A U-Boot retained the broad RK3568 default boot targets") + compiled_dts = subprocess.run( + ["dtc", "-I", "dtb", "-O", "dts", str(source / "u-boot.dtb")], + check=True, stdout=subprocess.PIPE, + ).stdout.decode("utf-8") + identity = "youyeetoo,yy3568" if board == "yy3568" else "radxa,rock3a" + for marker in (identity, "pcie@fe260000", "pcie@fe280000", + "reset-gpios", "jedec,spi-nor"): + require(marker in compiled_dts, f"{board}: built U-Boot DTB lacks {marker}") + spi_name = "sfc@fe300000" if board == "yy3568" else "spi@fe300000" + spi_node = f"{spi_name} {{" + require(spi_node in compiled_dts, + f"{board}: built U-Boot DTB lacks its SPI-NOR controller") + spi = compiled_dts[compiled_dts.index(spi_node):].split("};", 1)[0] + require('status = "okay"' in spi, + f"{board}: built U-Boot DTB does not enable SPI NOR") + emmc_name = "sdhci@fe310000" if board == "yy3568" else "mmc@fe310000" + emmc_node = f"{emmc_name} {{" + require(emmc_node in compiled_dts, + f"{board}: built U-Boot DTB lacks its enabled eMMC controller") + emmc = compiled_dts[compiled_dts.index(emmc_node):].split("};", 1)[0] + require('status = "okay"' in emmc, + f"{board}: built U-Boot DTB does not enable eMMC") + require((ROOT / str(artifacts["source"])).is_file(), + f"{board}: corresponding U-Boot source archive was not generated") + check_media_images(board, manifest) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--offline", action="store_true") + parser.add_argument("--board") + args = parser.parse_args() + manifests = load_manifests() + check_manifests(manifests) + check_manifest_rejections(manifests) + check_bl31() + check_isolation() + check_overlays() + if args.offline: + for board in manifests: + stage = ROOT / "build" / "chainload" / board / "stage.bin" + require(stage.is_file() and stage.stat().st_size < 0x40000, + f"{board}: offline chainloader stage was not built or is oversized") + if args.board: + check_built(args.board) + print("chainloader checks passed") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (AssertionError, OSError, subprocess.CalledProcessError) as error: + print(f"chainload check failed: {error}", file=sys.stderr) + raise SystemExit(1) diff --git a/tests/check_chainload_flash.py b/tests/check_chainload_flash.py new file mode 100644 index 0000000..db755ed --- /dev/null +++ b/tests/check_chainload_flash.py @@ -0,0 +1,467 @@ +#!/usr/bin/env python3 +"""Exercise the board-scoped RK356x installer against file-backed mock devices.""" + +from __future__ import annotations + +import json +import os +import pathlib +import shutil +import struct +import subprocess +import sys +import tempfile +import zlib + +ROOT = pathlib.Path(__file__).resolve().parents[1] +FLASHER = ROOT / "tools" / "flash-chainload.sh" +PARTITION_CHECK = ROOT / "tools" / "check-partition-overlap.py" + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +def bash_path(path: pathlib.Path) -> str: + """Return an absolute path accepted by both POSIX and Git Bash.""" + resolved = path.resolve().as_posix() + if os.name == "nt" and len(resolved) >= 3 and resolved[1:3] == ":/": + return f"/{resolved[0].lower()}{resolved[2:]}" + return resolved + + +def host_path(path: pathlib.Path) -> str: + return path.resolve().as_posix() + + +def run(command: list[str], env: dict[str, str] | None = None, + check: bool = False) -> subprocess.CompletedProcess[str]: + complete_env = os.environ.copy() + if env: + complete_env.update(env) + result = subprocess.run(command, cwd=ROOT, env=complete_env, text=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if check and result.returncode: + raise AssertionError( + f"command failed ({result.returncode}): {' '.join(command)}\n" + f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" + ) + return result + + +def write_mock(path: pathlib.Path) -> None: + path.write_text(r'''#!/usr/bin/env python3 +import os, pathlib, sys + +state = pathlib.Path(os.environ["MOCK_STATE"]) +state.mkdir(parents=True, exist_ok=True) +with (state / "commands.log").open("a", encoding="utf-8") as log: + log.write(" ".join(sys.argv[1:]) + "\n") +args = sys.argv[1:] +if args == ["-h"]: + print("ChangeStorage: cs [storage]") +elif args == ["ld"]: + count = int(os.environ.get("MOCK_DEVICES", "1")) + mode = os.environ.get("MOCK_MODE", "Loader") + pid = os.environ.get("MOCK_PID", "350a") + for index in range(count): + print(f"DevNo={index + 1}\tVid=0x2207,Pid=0x{pid},LocationID={index + 1}\tMode={mode}") +elif args and args[0] == "cs": + if os.environ.get("MOCK_CS_FAIL") == "1": + print("Change Storage failed", file=sys.stderr) + raise SystemExit(1) + (state / "selected").write_text(args[1], encoding="ascii") + print("Change Storage OK.") +elif args == ["rfi"]: + selected = (state / "selected").read_text(encoding="ascii") + media = pathlib.Path(os.environ["MOCK_EMMC" if selected == "1" else "MOCK_SPI"]) + sectors = int(os.environ.get("MOCK_CAPACITY", str(media.stat().st_size // 512))) + print("Flash Info:") + print(f"\tFlash Size: {sectors} Sectors") +elif args == ["rid"]: + print(os.environ.get("MOCK_FLASH_ID", "Flash ID: EF 40 18 00 00")) +elif args and args[0] in ("rl", "wl"): + selected = (state / "selected").read_text(encoding="ascii") + media = pathlib.Path(os.environ["MOCK_EMMC" if selected == "1" else "MOCK_SPI"]) + data = bytearray(media.read_bytes()) + start = int(args[1]) * 512 + if args[0] == "rl": + count = int(args[2]) * 512 + if start + count > len(data): + raise SystemExit(1) + output = bytearray(data[start:start + count]) + if os.environ.get("MOCK_SHORT_READ") == "1" and not (state / "wrote").exists(): + output = output[:-512] + if (os.environ.get("MOCK_BAD_READBACK") == "1" and + (state / "wrote").exists() and + pathlib.Path(args[3]).name in ("installed-readback.bin", "restore-readback.bin")): + output[0] ^= 0xff + pathlib.Path(args[3]).write_bytes(output) + print("Read LBA OK.") + else: + source = pathlib.Path(args[2]).read_bytes() + if start + len(source) > len(data): + raise SystemExit(1) + data[start:start + len(source)] = source + media.write_bytes(data) + (state / "wrote").write_text("1", encoding="ascii") + print("Write LBA OK.") +elif args == ["rd"]: + (state / "reset").write_text("1", encoding="ascii") + print("Reset Device OK.") +else: + print(f"unsupported mock command: {args}", file=sys.stderr) + raise SystemExit(2) +''', encoding="utf-8", newline="\n") + path.chmod(0o755) + + +def fixture(parent: pathlib.Path) -> tuple[pathlib.Path, dict[str, str]]: + repo = parent / "fixture" + (repo / "config" / "chainload").mkdir(parents=True) + (repo / "img").mkdir() + (repo / "tools").mkdir() + (repo / "img" / "ddr.bin").write_bytes(b"DDR") + (repo / "img" / "usbplug.bin").write_bytes(b"USB") + idblock = bytes((index * 29 + 7) & 0xff for index in range(1024)) + spi = bytes((index * 13 + 3) & 0xff for index in range(2048)) + (repo / "uboot_yy3568_idbloader.img").write_bytes(idblock) + (repo / "uboot_yy3568_spi.img").write_bytes(spi) + manifest = { + "board": "yy3568", + "boot_media": { + "ddr": "img/ddr.bin", + "usbplug": "img/usbplug.bin", + "emmc": {"artifact": "uboot_yy3568_idbloader.img", + "storage_id": 1, "write_lba": 64}, + "spi-nor": {"artifact": "uboot_yy3568_spi.img", + "storage_id": 9, "write_lba": 0}, + }, + } + (repo / "config" / "chainload" / "yy3568.json").write_text( + json.dumps(manifest), encoding="utf-8") + shutil.copy2(PARTITION_CHECK, repo / "tools" / PARTITION_CHECK.name) + + mock = parent / "rkdeveloptool" + xrock = parent / "xrock" + write_mock(mock) + xrock.write_text("#!/usr/bin/env sh\necho xrock mock\n", encoding="utf-8", newline="\n") + xrock.chmod(0o755) + state = parent / "state" + emmc = parent / "emmc.bin" + spi_media = parent / "spi.bin" + emmc.write_bytes(bytes(256 * 512)) + spi_media.write_bytes(bytes([0xff]) * (64 * 512)) + env = { + "CHAINLOAD_REPO": bash_path(repo), + "XROCK": bash_path(xrock), + "RKDEVELOPTOOL": bash_path(mock), + "MOCK_STATE": host_path(state), + "MOCK_EMMC": host_path(emmc), + "MOCK_SPI": host_path(spi_media), + } + return repo, env + + +def flash(env: dict[str, str], media: str, backup: pathlib.Path, + confirmation: str | None = None) -> subprocess.CompletedProcess[str]: + backup.mkdir(parents=True, exist_ok=True) + return run(["bash", str(FLASHER), "flash", "yy3568", media, + host_path(backup), confirmation or f"yy3568:{media}"], env) + + +def make_gpt(first: int, last: int) -> bytes: + data = bytearray(64 * 512) + data[446 + 4] = 0xEE + struct.pack_into(" None: + safe = parent / "safe-gpt.bin" + overlap = parent / "overlap-gpt.bin" + safe.write_bytes(make_gpt(128, 160)) + overlap.write_bytes(make_gpt(64, 100)) + require(run(["python3", str(PARTITION_CHECK), str(safe), "64", "2"]).returncode == 0, + "safe GPT layout was rejected") + result = run(["python3", str(PARTITION_CHECK), str(overlap), "64", "2"]) + require(result.returncode != 0 and "GPT partition 1" in result.stderr, + "overlapping GPT layout was accepted") + + mbr = bytearray(64 * 512) + mbr[446 + 4] = 0x83 + struct.pack_into(" None: + repo, env = fixture(parent) + backup = parent / "backup" + emmc = parent / "emmc.bin" + before = emmc.read_bytes() + result = flash(env, "emmc", backup) + require(result.returncode == 0, f"mock eMMC flash failed: {result.stderr}") + payload = (repo / "uboot_yy3568_idbloader.img").read_bytes() + after = emmc.read_bytes() + require(after[64 * 512:64 * 512 + len(payload)] == payload, + "eMMC installer wrote the wrong range") + require(after[:64 * 512] == before[:64 * 512], + "eMMC installer changed LBA 0-63") + require((backup / "emmc-lba0-63.bin").stat().st_size == 64 * 512, + "eMMC metadata backup is incomplete") + require((backup / "previous-idblock-region.bin").read_bytes() == bytes(len(payload)), + "eMMC destination backup is incomplete") + log = parent / "state" / "commands.log" + require("cs 1\n" in log.read_text(encoding="utf-8"), + "eMMC storage ID 1 was not selected") + require((parent / "state" / "reset").is_file(), + "successful install did not reset the board") + + result = run(["bash", str(FLASHER), "restore", host_path(backup), + "restore:yy3568:emmc"], env) + require(result.returncode == 0, f"mock eMMC restore failed: {result.stderr}") + restored = emmc.read_bytes() + require(restored[64 * 512:64 * 512 + len(payload)] == bytes(len(payload)), + "eMMC restore did not restore the destination range") + + +def check_spi_install(parent: pathlib.Path) -> None: + repo, env = fixture(parent) + # Exercise the release-bundle layout as well as the source-tree layout. + (repo / "chainload").mkdir() + (repo / "loaders").mkdir() + (repo / "install").mkdir() + shutil.move(repo / "config" / "chainload" / "yy3568.json", + repo / "chainload" / "MANIFEST.json") + shutil.move(repo / "uboot_yy3568_idbloader.img", repo / "chainload") + shutil.move(repo / "uboot_yy3568_spi.img", repo / "chainload") + shutil.move(repo / "img" / "ddr.bin", repo / "loaders") + shutil.move(repo / "img" / "usbplug.bin", repo / "loaders") + shutil.move(repo / "tools" / "check-partition-overlap.py", repo / "install") + shutil.rmtree(repo / "config") + shutil.rmtree(repo / "img") + shutil.rmtree(repo / "tools") + backup = parent / "backup" + spi = parent / "spi.bin" + before = spi.read_bytes() + result = flash(env, "spi-nor", backup) + require(result.returncode == 0, f"mock SPI-NOR flash failed: {result.stderr}") + payload = (repo / "chainload" / "uboot_yy3568_spi.img").read_bytes() + after = spi.read_bytes() + require(after[:len(payload)] == payload and after[len(payload):] == before[len(payload):], + "SPI-NOR installer changed bytes outside the image range") + require((backup / "complete-spi-nor.bin").read_bytes() == before, + "SPI-NOR full-device backup is incomplete") + log = (parent / "state" / "commands.log").read_text(encoding="utf-8") + require("cs 9\n" in log and "rid\n" in log and not any( + line.startswith("ef") for line in log.splitlines()), + "SPI-NOR selection/JEDEC/no-erase contract failed") + + +def check_failures(parent: pathlib.Path) -> None: + cases = parent / "failures" + cases.mkdir() + + _, env = fixture(cases / "confirmation") + result = flash(env, "emmc", cases / "confirmation" / "backup", "wrong") + require(result.returncode != 0 and "CONFIRM" in result.stderr, + "malformed confirmation was accepted") + require(not (cases / "confirmation" / "state" / "wrote").exists(), + "confirmation failure touched storage") + + _, env = fixture(cases / "devices") + env["MOCK_DEVICES"] = "2" + result = flash(env, "emmc", cases / "devices" / "backup") + require(result.returncode != 0 and "exactly one" in result.stderr, + "multiple Rockchip devices were accepted") + require(not (cases / "devices" / "state" / "wrote").exists(), + "multiple-device failure touched storage") + + _, env = fixture(cases / "pid") + env["MOCK_PID"] = "330c" + result = flash(env, "emmc", cases / "pid" / "backup") + require(result.returncode != 0 and "PID 0x350a" in result.stderr, + "unsupported Rockchip PID was accepted") + + _, env = fixture(cases / "storage") + env["MOCK_CS_FAIL"] = "1" + result = flash(env, "emmc", cases / "storage" / "backup") + require(result.returncode != 0 and + not (cases / "storage" / "state" / "wrote").exists(), + "storage-selection failure did not abort before writing") + + _, env = fixture(cases / "capacity") + env["MOCK_CAPACITY"] = "2" + result = flash(env, "spi-nor", cases / "capacity" / "backup") + require(result.returncode != 0 and "capacity" in result.stderr, + "insufficient SPI-NOR capacity was accepted") + require(not (cases / "capacity" / "state" / "wrote").exists(), + "capacity failure touched storage") + + _, env = fixture(cases / "jedec") + env["MOCK_FLASH_ID"] = "Flash ID: 00 00 00 00 00" + result = flash(env, "spi-nor", cases / "jedec" / "backup") + require(result.returncode != 0 and "JEDEC manufacturer" in result.stderr, + "invalid SPI-NOR JEDEC response was accepted") + require(not (cases / "jedec" / "state" / "wrote").exists(), + "JEDEC failure touched storage") + + _, env = fixture(cases / "short-backup") + env["MOCK_SHORT_READ"] = "1" + result = flash(env, "emmc", cases / "short-backup" / "backup") + require(result.returncode != 0 and "backup has an unexpected size" in result.stderr, + "short eMMC backup was accepted") + require(not (cases / "short-backup" / "state" / "wrote").exists(), + "short-backup failure touched storage") + + _, env = fixture(cases / "overlap") + emmc = cases / "overlap" / "emmc.bin" + data = bytearray(emmc.read_bytes()) + data[446 + 4] = 0x83 + struct.pack_into(" int: + test_root = ROOT / "build" / "host-tests" + test_root.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix="rk-chainload-flash-", dir=test_root) as temporary: + parent = pathlib.Path(temporary) + check_partition_tables(parent) + check_emmc_install_restore(parent / "emmc") + check_spi_install(parent / "spi") + check_failures(parent) + print("guarded chainload flashing checks passed") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (AssertionError, OSError, subprocess.SubprocessError) as error: + print(f"chainload flashing check failed: {error}", file=sys.stderr) + raise SystemExit(1) diff --git a/tests/check_release.py b/tests/check_release.py new file mode 100644 index 0000000..1069bde --- /dev/null +++ b/tests/check_release.py @@ -0,0 +1,440 @@ +#!/usr/bin/env python3 +"""Validate the deterministic GitHub release distribution contract.""" + +from __future__ import annotations + +import hashlib +import io +import os +import pathlib +import shutil +import subprocess +import sys +import tarfile +import tempfile + +ROOT = pathlib.Path(__file__).resolve().parents[1] +PACKAGER = ROOT / "tools" / "release-dist.sh" +VERSION = "v1.2.3" + +PACKAGES = { + "pinebook-pro": { + "BOARD.md", + "BUILD-INFO.txt", + "LICENSE", + "README.md", + "SHA256SUMS", + "firmware/demo_pinebook.bin", + "firmware/pinebook.bin", + "images/demo_pinebook.img", + "images/pinebook.img", + "loaders/pinebook-ddr.bin", + "loaders/pinebook-poc-ddr.bin", + }, + "genbook": { + "BOARD.md", + "BUILD-INFO.txt", + "LICENSE", + "README.md", + "SHA256SUMS", + "firmware/demo_genbook.bin", + "firmware/genbook.bin", + "images/genbook.img", + "images/genbook_demo.img", + "loaders/genbook-ddr.bin", + }, + "roc3566": { + "BOARD.md", + "BUILD-INFO.txt", + "LICENSE", + "README.md", + "SHA256SUMS", + "firmware/demo_roc3566.bin", + "firmware/roc3566.bin", + "images/demo_roc3566.img", + "images/roc3566.img", + "licenses/ROCKCHIP-BINARY-LICENSE", + "loaders/rk3566_ddr_1056MHz_v1.25.bin", + "loaders/rk356x_usbplug_v1.17.bin", + "provenance/rkbin-README.md", + }, + "yy3568": { + "BOARD.md", + "BUILD-INFO.txt", + "LICENSE", + "README.md", + "SHA256SUMS", + "firmware/demo_yy3568.bin", + "firmware/yy3568.bin", + "images/demo_yy3568.img", + "images/yy3568.img", + "licenses/ROCKCHIP-BINARY-LICENSE", + "loaders/rk3568_ddr_1560MHz_v1.25.bin", + "loaders/rk356x_usbplug_v1.17.bin", + "provenance/rkbin-README.md", + }, + "rock3a": { + "BOARD.md", + "BUILD-INFO.txt", + "LICENSE", + "README.md", + "SHA256SUMS", + "firmware/demo_rock3a.bin", + "firmware/rock3a.bin", + "images/demo_rock3a.img", + "images/rock3a.img", + "licenses/ROCKCHIP-BINARY-LICENSE", + "loaders/rk3568_ddr_1560MHz_v1.25.bin", + "loaders/rk356x_usbplug_v1.17.bin", + "provenance/rkbin-README.md", + }, +} + + +def chainload_files(board: str) -> set[str]: + return { + "chainloading.md", + "chainload/MANIFEST.json", + "chainload/README.md", + f"chainload/uboot_{board}.bin", + f"chainload/uboot_{board}.img", + f"chainload/uboot_{board}_idbloader.img", + f"chainload/uboot_{board}_spi.img", + f"chainload/{board}-u-boot.itb", + "install/check-partition-overlap.py", + "install/flash-chainload.sh", + "loaders/rk3568_bl31_v1.46.elf", + f"sources/{board}-u-boot-source.tar.xz", + } + + +def require(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) + + +def sha256(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def run_packager(output: pathlib.Path, version: str = VERSION, + extra_env: dict[str, str] | None = None, + check: bool = True) -> subprocess.CompletedProcess[str]: + env = os.environ.copy() + env["CHAINLOAD_RELEASE"] = "0" + if extra_env: + env.update(extra_env) + return subprocess.run( + ["bash", str(PACKAGER), version, str(output)], + cwd=ROOT, + env=env, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=check, + ) + + +def parse_sums(data: bytes) -> dict[str, str]: + result: dict[str, str] = {} + for raw_line in data.decode("utf-8").splitlines(): + require(len(raw_line) >= 67 and raw_line[64:66] in (" ", " *"), + f"invalid SHA256SUMS line: {raw_line}") + digest = raw_line[:64] + name = raw_line[66:] + require(len(digest) == 64, f"invalid SHA-256 digest: {digest}") + require(name not in result, f"duplicate checksum entry: {name}") + result[name] = digest + return result + + +def check_archive(archive: pathlib.Path, slug: str, commit: str, + source_date_epoch: int, chainload: bool = False) -> None: + root_name = f"rk-{VERSION}-{slug}" + expected = set(PACKAGES[slug]) + if chainload and slug in ("yy3568", "rock3a"): + expected.update(chainload_files(slug)) + with tarfile.open(archive, "r:xz") as bundle: + members = bundle.getmembers() + files: dict[str, bytes] = {} + modes: dict[str, int] = {} + for member in members: + path = pathlib.PurePosixPath(member.name) + require(not path.is_absolute(), f"{archive.name}: absolute archive path") + require(".." not in path.parts, f"{archive.name}: parent path in archive") + require(path.parts and path.parts[0] == root_name, + f"{archive.name}: multiple top-level roots") + require(member.isdir() or member.isfile(), + f"{archive.name}: links and special files are forbidden") + require(int(member.mtime) == source_date_epoch, + f"{archive.name}: non-reproducible timestamp") + if member.isfile(): + extracted = bundle.extractfile(member) + require(extracted is not None, f"{member.name}: cannot read archive member") + relative = path.relative_to(root_name).as_posix() + files[relative] = extracted.read() + modes[relative] = member.mode + + require(set(files) == expected, + f"{archive.name}: file allowlist mismatch: " + f"missing={sorted(expected - set(files))}, " + f"extra={sorted(set(files) - expected)}") + + prohibited = ("makeboot.out", "rock.out", "xrock", "rkdeveloptool", + "opi5.bin", "opi5.img") + for name in files: + allowed_bl31 = name == "loaders/rk3568_bl31_v1.46.elf" + require(allowed_bl31 or not name.endswith((".elf", ".o", ".out")), + f"{archive.name}: build product leaked: {name}") + require(pathlib.PurePosixPath(name).name not in prohibited, + f"{archive.name}: excluded artifact leaked: {name}") + + internal = parse_sums(files["SHA256SUMS"]) + checksummed = {f"./{name}" for name in files if name != "SHA256SUMS"} + require(set(internal) == checksummed, + f"{archive.name}: internal checksum allowlist mismatch") + for name, digest in internal.items(): + require(sha256(files[name.removeprefix("./")]) == digest, + f"{archive.name}: internal checksum mismatch for {name}") + + build_info = files["BUILD-INFO.txt"].decode("utf-8") + require(f"version={VERSION}\n" in build_info, + f"{archive.name}: version missing from BUILD-INFO") + require(f"commit={commit}\n" in build_info, + f"{archive.name}: commit missing from BUILD-INFO") + if chainload and slug in ("yy3568", "rock3a"): + require(sha256(files["loaders/rk3568_bl31_v1.46.elf"]) == + "c81ac7e8e1fd727cf7f0db62a9aaea760bde2b270e34d98eb264a264b86df749", + f"{slug} release contains an unpinned BL31") + with tarfile.open(fileobj=io.BytesIO( + files[f"sources/{slug}-u-boot-source.tar.xz"]), mode="r:xz") as source: + names = [pathlib.PurePosixPath(item.name) for item in source.getmembers()] + require(names and all(not name.is_absolute() and name.parts[0] == + f"{slug}-u-boot-source" for name in names), + "corresponding U-Boot source archive has an unsafe root") + if slug == "rock3a": + require(any(name.name == "README.rk-chainload" for name in names), + "ROCK 3A corresponding source lacks rebuild instructions") + require(modes["install/flash-chainload.sh"] == 0o755 and + modes["install/check-partition-overlap.py"] == 0o755, + f"{slug} release installer is not executable") + + +def check_distribution(output: pathlib.Path, chainload: bool = False) -> None: + archive_names = { + f"rk-{VERSION}-{slug}.tar.xz" for slug in PACKAGES + } + expected_assets = archive_names | {"SHA256SUMS"} + actual_assets = {path.name for path in output.iterdir()} + require(actual_assets == expected_assets, + f"release asset allowlist mismatch: {sorted(actual_assets)}") + + outer = parse_sums((output / "SHA256SUMS").read_bytes()) + require(set(outer) == archive_names, "top-level checksum allowlist mismatch") + for name, digest in outer.items(): + require(sha256((output / name).read_bytes()) == digest, + f"top-level checksum mismatch for {name}") + + commit = subprocess.run( + ["git", "rev-parse", "HEAD"], cwd=ROOT, check=True, + text=True, stdout=subprocess.PIPE, + ).stdout.strip().lower() + epoch = int(subprocess.run( + ["git", "show", "-s", "--format=%ct", "HEAD"], cwd=ROOT, check=True, + text=True, stdout=subprocess.PIPE, + ).stdout.strip()) + for slug in PACKAGES: + check_archive(output / f"rk-{VERSION}-{slug}.tar.xz", slug, commit, epoch, + chainload) + + +def make_chainload_source(parent: pathlib.Path) -> pathlib.Path: + destination = parent / "chainload-source" + shutil.copytree(ROOT, destination, ignore=shutil.ignore_patterns( + ".git", "dist", "build", "*-u-boot.itb", "uboot_*", + "*-u-boot-source.tar.xz", + )) + for board in ("yy3568", "rock3a"): + fit = f"synthetic, parser-tested {board} FIT\n".encode() + stage = (ROOT / "build" / "chainload" / board / "stage.bin").read_bytes() + (destination / f"{board}-u-boot.itb").write_bytes(fit) + (destination / f"uboot_{board}.bin").write_bytes(stage + fit) + (destination / f"uboot_{board}.img").write_bytes(b"synthetic RKNS image\n") + (destination / f"uboot_{board}_idbloader.img").write_bytes( + b"synthetic RKNS eMMC ID block\n") + (destination / f"uboot_{board}_spi.img").write_bytes( + b"synthetic Rockchip SPI-NOR image\n") + source_archive = destination / f"{board}-u-boot-source.tar.xz" + payload = b"synthetic corresponding-source fixture\n" + with tarfile.open(source_archive, "w:xz") as archive: + readme = "README.rk-chainload" if board == "rock3a" else "README" + info = tarfile.TarInfo(f"{board}-u-boot-source/{readme}") + info.size = len(payload) + info.mtime = 1 + info.mode = 0o644 + archive.addfile(info, io.BytesIO(payload)) + return destination + + +def check_reproducibility(first: pathlib.Path, second: pathlib.Path) -> None: + first_files = {path.name: path.read_bytes() for path in first.iterdir()} + second_files = {path.name: path.read_bytes() for path in second.iterdir()} + require(first_files.keys() == second_files.keys(), + "repeated packaging changed the asset list") + for name in first_files: + require(first_files[name] == second_files[name], + f"repeated packaging changed {name}") + + +def check_failures(parent: pathlib.Path) -> None: + invalid = run_packager(parent / "invalid", "1.2.3", check=False) + require(invalid.returncode != 0 and "vMAJOR.MINOR.PATCH" in invalid.stderr, + "invalid version was not rejected") + require(not (parent / "invalid").exists(), + "invalid version created an output directory") + + occupied = parent / "occupied" + occupied.mkdir() + sentinel = occupied / "keep.txt" + sentinel.write_text("do not overwrite\n", encoding="utf-8") + nonempty = run_packager(occupied, check=False) + require(nonempty.returncode != 0 and "not empty" in nonempty.stderr, + "non-empty destination was not rejected") + require(sentinel.read_text(encoding="utf-8") == "do not overwrite\n", + "non-empty destination was modified") + + empty_source = parent / "missing-source" + empty_source.mkdir() + missing = run_packager( + parent / "missing-output", + extra_env={ + "RK_RELEASE_SOURCE_DIR": str(empty_source), + "RK_RELEASE_COMMIT": "0" * 40, + "SOURCE_DATE_EPOCH": "1", + }, + check=False, + ) + require(missing.returncode != 0 and "missing required regular file" in missing.stderr, + "missing package input was not rejected") + missing_output = parent / "missing-output" + require(missing_output.is_dir() and not any(missing_output.iterdir()), + "failed packaging exposed partial release assets") + + bad_flag = run_packager(parent / "bad-flag", + extra_env={"CHAINLOAD_RELEASE": "yes"}, check=False) + require(bad_flag.returncode != 0 and "must be auto, 0, or 1" in bad_flag.stderr, + "invalid chainloader release mode was not rejected") + + +def check_workflow_contracts() -> None: + ci = (ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8") + release = (ROOT / ".github" / "workflows" / "release.yml").read_text( + encoding="utf-8" + ) + docs = (ROOT / ".github" / "workflows" / "docs.yml").read_text( + encoding="utf-8" + ) + require("actions/checkout@v7" in ci and "contents: read" in ci, + "CI checkout or permissions are not hardened") + runner_selector = ( + "runs-on: ${{ fromJSON(vars.RK_RUNNER_LABELS || " + "'[\"self-hosted\",\"Linux\",\"X64\"]') }}" + ) + require(ci.count(runner_selector) == 2 and + "pull_request:" not in ci and "mktemp -d" in ci, + "CI runner selection or master-only trigger is incorrect") + require(runner_selector in release and runner_selector in docs, + "release/docs workflows do not use the configurable runner selector") + require("actions/checkout@v7" in docs and + "actions/setup-python" not in docs and + "python3-venv" in docs and "docs-venv/bin/mkdocs" in docs, + "docs workflow depends on the GitHub-hosted Python tool cache") + require("branches: [ master ]" in ci and + "refs/remotes/origin/master" in release and + "refs/remotes/origin/main" not in release, + "CI/release workflows do not follow the repository default branch") + require("persist-credentials: false" in ci and "make SHELL=/bin/bash check" in ci, + "CI must run checks without persisted credentials") + for marker in ( + "tags:", "v*.*.*", "^v(0|[1-9][0-9]*)", "contents: write", + "fetch-depth: 0", + "git merge-base --is-ancestor", "make SHELL=/bin/bash check", + "release-dist", "gh release create", "--draft", "gh release edit", + "gh release delete", "already exists and will not be modified", + "load_release_record", "gh api graphql", "release(tagName:$tag)", + "[.databaseId, .isDraft]", + "releases/${release_id}", "compare_remote_assets", "gh release download", + "matches the rebuilt assets; retry is complete", + "--latest", "cancel-in-progress: false", + ): + require(marker in release, f"release workflow lacks contract marker: {marker}") + require("matrix:" in ci and "yy3568" in ci and "rock3a" in ci and + "chainload-check" in ci, + "CI lacks the board-keyed chainloader job") + for artifact in ("artifacts.idblock", "artifacts.spi_nor", + "reference-idblock.img", "reference-spi.img"): + require(artifact in ci, f"chainloader CI lacks media check: {artifact}") + require("CHAINLOAD_RELEASE=1" in release and + "chainload BOARD=yy3568" in release and + "chainload BOARD=rock3a" in release and + "rk-${version}-rock3a.tar.xz" in release, + "release workflow does not explicitly build/package both chainloaders") + + +def main() -> int: + with tempfile.TemporaryDirectory(prefix="rk-release-check-") as temporary: + parent = pathlib.Path(temporary) + first = parent / "first" + second = parent / "second" + run_packager(first) + check_distribution(first) + run_packager(second) + check_distribution(second) + check_reproducibility(first, second) + chain_source = make_chainload_source(parent) + commit = subprocess.run( + ["git", "rev-parse", "HEAD"], cwd=ROOT, check=True, + text=True, stdout=subprocess.PIPE, + ).stdout.strip() + epoch = subprocess.run( + ["git", "show", "-s", "--format=%ct", "HEAD"], cwd=ROOT, check=True, + text=True, stdout=subprocess.PIPE, + ).stdout.strip() + chain_env = { + "CHAINLOAD_RELEASE": "auto", + "RK_RELEASE_SOURCE_DIR": str(chain_source), + "RK_RELEASE_COMMIT": commit, + "SOURCE_DATE_EPOCH": epoch, + } + chain_first = parent / "chain-first" + chain_second = parent / "chain-second" + run_packager(chain_first, extra_env=chain_env) + check_distribution(chain_first, chainload=True) + run_packager(chain_second, extra_env=chain_env) + check_distribution(chain_second, chainload=True) + check_reproducibility(chain_first, chain_second) + (chain_source / "uboot_rock3a.img").unlink() + partial_auto = run_packager(parent / "chain-partial-auto", + extra_env=chain_env, check=False) + require(partial_auto.returncode != 0 and + "chainloader release inputs are incomplete" in partial_auto.stderr, + "auto packaging accepted a partial board matrix") + incomplete_env = dict(chain_env) + incomplete_env["CHAINLOAD_RELEASE"] = "1" + incomplete = run_packager(parent / "chain-incomplete", + extra_env=incomplete_env, check=False) + require(incomplete.returncode != 0 and + "missing required regular file: uboot_rock3a.img" in incomplete.stderr, + "incomplete chainloader release inputs were not rejected") + check_failures(parent) + check_workflow_contracts() + print("release distribution checks passed") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (AssertionError, OSError, subprocess.CalledProcessError, tarfile.TarError) as error: + print(f"release check failed: {error}", file=sys.stderr) + raise SystemExit(1) diff --git a/tests/host_stubs.c b/tests/host_stubs.c new file mode 100644 index 0000000..c9a4987 --- /dev/null +++ b/tests/host_stubs.c @@ -0,0 +1,16 @@ +#include + +/* Hardware-only helpers referenced by dead OHCI paths in the host test link. */ +uint64_t asm_get_cpu_timer(void) { + static uint64_t now; + return ++now; +} + +void msleep(unsigned int ms) { + (void)ms; +} + +void debug(const char *label, uint64_t value) { + (void)label; + (void)value; +} diff --git a/tests/msvc_compat.h b/tests/msvc_compat.h new file mode 100644 index 0000000..c241b46 --- /dev/null +++ b/tests/msvc_compat.h @@ -0,0 +1,3 @@ +#ifdef _MSC_VER +#define __attribute__(attributes) +#endif diff --git a/tests/unit.c b/tests/unit.c new file mode 100644 index 0000000..d3e5cec --- /dev/null +++ b/tests/unit.c @@ -0,0 +1,186 @@ +#include +#include +#include +#include +#include "edid.h" +#include "hid_keyboard.h" +#include "input.h" +#include "ohci.h" +#include "usb.h" + +static void checksum(uint8_t block[128]) { + uint8_t sum = 0; + block[127] = 0; + for (unsigned int i = 0; i < 127; i++) sum += block[i]; + block[127] = (uint8_t)(0 - sum); +} + +static void base_edid(uint8_t block[128]) { + static const uint8_t header[8] = + { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; + memset(block, 0, 128); + memcpy(block, header, sizeof(header)); + block[18] = 1; + block[19] = 4; +} + +static void dtd(uint8_t *d, uint32_t clock_khz, uint16_t width, + uint16_t hblank, uint16_t hfront, uint16_t hsync, + uint16_t height, uint16_t vblank, uint16_t vfront, uint16_t vsync, + uint8_t flags) { + uint16_t clock = (uint16_t)(clock_khz / 10); + memset(d, 0, 18); + d[0] = (uint8_t)clock; d[1] = (uint8_t)(clock >> 8); + d[2] = (uint8_t)width; d[3] = (uint8_t)hblank; + d[4] = (uint8_t)(((width >> 8) << 4) | (hblank >> 8)); + d[5] = (uint8_t)height; d[6] = (uint8_t)vblank; + d[7] = (uint8_t)(((height >> 8) << 4) | (vblank >> 8)); + d[8] = (uint8_t)hfront; d[9] = (uint8_t)hsync; + d[10] = (uint8_t)((vfront << 4) | vsync); + d[11] = (uint8_t)(((hfront >> 8) << 6) | ((hsync >> 8) << 4) | + ((vfront >> 4) << 2) | (vsync >> 4)); + d[17] = flags; +} + +static void test_edid(void) { + uint8_t edid[256]; + struct VideoMode mode; + base_edid(edid); + dtd(edid + 54, 148500, 1920, 280, 88, 44, 1080, 45, 4, 5, 0x1e); + checksum(edid); + assert(edid_block_valid(edid)); + assert(edid_select_mode(edid, 1, &mode)); + assert(mode.hactive == 1920 && mode.vactive == 1080 && mode.refresh_hz == 60); + + base_edid(edid); + edid[126] = 1; + checksum(edid); + memset(edid + 128, 0, 128); + edid[128] = 0x02; edid[129] = 0x03; edid[130] = 6; + edid[132] = 0x41; edid[133] = 0x80 | 95; + checksum(edid + 128); + assert(edid_select_mode(edid, 2, &mode)); + assert(mode.hactive == 3840 && mode.vactive == 2160 && mode.refresh_hz == 30); + + /* 4K60 is unsupported and a Y420-only block does not become RGB. */ + edid[133] = 97; + checksum(edid + 128); + assert(!edid_select_mode(edid, 2, &mode)); + edid[132] = (7 << 5) | 2; edid[133] = 0x0e; edid[134] = 95; + edid[130] = 7; + checksum(edid + 128); + assert(!edid_select_mode(edid, 2, &mode)); + + /* A native CTA DTD wins over a larger non-native DTD. */ + base_edid(edid); + edid[126] = 1; + checksum(edid); + memset(edid + 128, 0, 128); + edid[128] = 0x02; edid[129] = 0x03; edid[130] = 4; edid[131] = 1; + dtd(edid + 132, 74250, 1280, 370, 110, 40, 720, 30, 5, 5, 0x1e); + dtd(edid + 150, 148500, 1920, 280, 88, 44, 1080, 45, 4, 5, 0x1e); + checksum(edid + 128); + assert(edid_select_mode(edid, 2, &mode)); + assert(mode.hactive == 1280 && mode.vactive == 720); + + /* Non-native choices rank by pixel area and then refresh rate. */ + memset(edid + 128, 0, 128); + edid[128] = 0x02; edid[129] = 0x03; edid[130] = 7; + edid[132] = 0x42; edid[133] = 32; edid[134] = 16; + checksum(edid + 128); + assert(edid_select_mode(edid, 2, &mode)); + assert(mode.hactive == 1920 && mode.vactive == 1080 && mode.refresh_hz == 60); + edid[130] = 8; edid[132] = 0x43; edid[135] = 93; + checksum(edid + 128); + assert(edid_select_mode(edid, 2, &mode)); + assert(mode.hactive == 3840 && mode.vactive == 2160 && mode.refresh_hz == 24); + + base_edid(edid); + dtd(edid + 54, 74250, 1920, 280, 88, 44, 1080, 45, 4, 5, 0x80); + checksum(edid); + assert(!edid_select_mode(edid, 1, &mode)); + edid[20] ^= 1; + assert(!edid_select_mode(edid, 1, &mode)); + edid_fallback_mode(&mode); + assert(mode.hactive == 1280 && mode.vactive == 720); +} + +static void report(uint8_t modifier, uint8_t usage) { + uint8_t data[8] = { modifier, 0, usage, 0, 0, 0, 0, 0 }; + hid_keyboard_report(data); +} + +static void release_keys(void) { + uint8_t data[8] = { 0 }; + hid_keyboard_report(data); +} + +static void test_hid(void) { + input_reset(); hid_keyboard_reset(); + report(0, 0x04); assert(input_get_char() == 'a'); + report(0, 0x04); assert(!input_available()); + release_keys(); report(0x02, 0x04); assert(input_get_char() == 'A'); + release_keys(); report(0, 0x39); release_keys(); + report(0, 0x04); assert(input_get_char() == 'A'); + release_keys(); report(0x02, 0x1e); assert(input_get_char() == '!'); + release_keys(); report(0, 0x28); assert(input_get_char() == '\r'); + release_keys(); report(0, 0x2a); assert(input_get_char() == '\b'); + release_keys(); report(0, 0x2b); assert(input_get_char() == '\t'); + release_keys(); report(0, 0x29); assert(input_get_char() == 0x1b); + release_keys(); report(0, 0x4f); assert(!input_available()); + release_keys(); report(0, 0x3a); assert(!input_available()); + release_keys(); report(0x04, 0x04); assert(!input_available()); + release_keys(); + uint8_t rollover[8] = { 0, 0, 1, 1, 1, 1, 1, 1 }; + hid_keyboard_report(rollover); assert(!input_available()); + + input_reset(); + for (unsigned int i = 0; i < 63; i++) assert(input_enqueue('x')); + assert(!input_enqueue('y')); + for (unsigned int i = 0; i < 32; i++) assert(input_get_char() == 'x'); + for (unsigned int i = 0; i < 31; i++) assert(input_enqueue('z')); + for (unsigned int i = 0; i < 31; i++) assert(input_get_char() == 'x'); + for (unsigned int i = 0; i < 31; i++) assert(input_get_char() == 'z'); + assert(!input_available()); +} + +static void test_usb(void) { + uint8_t composite[] = { + 9, USB_DT_CONFIG, 34, 0, 2, 1, 0, 0x80, 50, + 9, USB_DT_INTERFACE, 0, 0, 1, 8, 6, 80, 0, + 9, USB_DT_INTERFACE, 2, 0, 1, 3, 1, 1, 0, + 7, USB_DT_ENDPOINT, 0x83, 3, 8, 0, 10, + }; + struct UsbBootKeyboard keyboard; + assert(!usb_find_boot_keyboard(composite, sizeof(composite), &keyboard)); + assert(keyboard.interface_number == 2 && keyboard.endpoint == 3); + assert(keyboard.max_packet == 8 && keyboard.interval == 10); + composite[9 + 9 + 9 + 6] = 0; + assert(usb_find_boot_keyboard(composite, sizeof(composite), &keyboard)); + composite[9 + 9 + 9 + 6] = 10; + composite[9 + 9 + 3] = 1; + assert(usb_find_boot_keyboard(composite, sizeof(composite), &keyboard)); + composite[9 + 9 + 3] = 0; + composite[9 + 9 + 7] = 2; + assert(usb_find_boot_keyboard(composite, sizeof(composite), &keyboard)); + + static uint8_t arena[1024 + 256]; + uintptr_t start = (uintptr_t)arena; + ohci_dma_configure(start, start + sizeof(arena)); + void *a = ohci_dma_alloc(17, 16); + void *b = ohci_dma_alloc(256, 256); + assert(a && b && !((uintptr_t)a & 15) && !((uintptr_t)b & 255)); + assert(ohci_dma_contains((uintptr_t)a, 17)); + assert(!ohci_dma_contains(start - 1, 1)); + assert(ohci_dma_contains(start + sizeof(arena) - 1, 1)); + assert(!ohci_dma_contains(start + sizeof(arena) - 1, 2)); + assert(!ohci_dma_alloc(sizeof(arena), 16)); +} + +int main(void) { + test_edid(); + test_hid(); + test_usb(); + puts("RK356x host unit tests passed"); + return 0; +} diff --git a/tools/build-chainload-media.sh b/tools/build-chainload-media.sh new file mode 100644 index 0000000..a2b08df --- /dev/null +++ b/tools/build-chainload-media.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: Apache-2.0 +set -euo pipefail + +board=${1:-} +if [[ -z "$board" || ! "$board" =~ ^[a-z0-9][a-z0-9_-]*$ ]]; then + echo "usage: $0 BOARD" >&2 + exit 2 +fi + +repo=$(cd "$(dirname "$0")/.." && pwd) +manifest="$repo/config/chainload/$board.json" +[[ -f "$manifest" ]] || { echo "chainload-media: board '$board' has no manifest" >&2; exit 2; } +manifest_tool="$repo/tools/chainload-manifest.py" +python3 "$manifest_tool" validate "$board" + +json() { + python3 "$manifest_tool" get "$board" "$1" +} + +[[ $(json board) == "$board" ]] || { echo "chainload-media: manifest identity mismatch" >&2; exit 2; } +soc=$(json soc) +ddr="$repo/$(json boot_media.ddr)" +combined="$repo/$(json artifacts.binary)" +idblock="$repo/$(json artifacts.idblock)" +spi="$repo/$(json artifacts.spi_nor)" +sd="$repo/$(json artifacts.image)" +build_root=${CHAINLOAD_BUILD_DIR:-"$repo/build/chainload/$board"} +default_mkimage="$build_root/source/tools/mkimage" +mkimage=${MKIMAGE:-$default_mkimage} + +[[ -f "$ddr" ]] || { echo "chainload-media: missing DDR image: $ddr" >&2; exit 2; } +[[ -f "$combined" ]] || { echo "chainload-media: missing chainloader image: $combined" >&2; exit 2; } +[[ -x "$mkimage" ]] || { + echo "chainload-media: pinned mkimage is unavailable: $mkimage" >&2 + echo "build pinned U-Boot first or set MKIMAGE=/path/to/tools/mkimage" >&2 + exit 2 +} + +tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/rk-chainload-media.XXXXXX") +cleanup() { rm -rf -- "$tmp_dir"; } +trap cleanup EXIT + +"$mkimage" -n "$soc" -T rksd -d "$ddr:$combined" "$tmp_dir/idblock.img" +"$mkimage" -n "$soc" -T rkspi -d "$ddr:$combined" "$tmp_dir/spi.img" + +# The raw rksd ID block is written at LBA 64 on eMMC. The SD convenience +# image carries the same bytes at that offset so it can be written as a disk. +truncate -s $((64 * 512)) "$tmp_dir/sd.img" +cat "$tmp_dir/idblock.img" >> "$tmp_dir/sd.img" + +mv "$tmp_dir/idblock.img" "$idblock" +mv "$tmp_dir/spi.img" "$spi" +mv "$tmp_dir/sd.img" "$sd" +echo "chainload-media: built $(basename "$idblock"), $(basename "$spi"), and $(basename "$sd")" diff --git a/tools/build-chainload-uboot.sh b/tools/build-chainload-uboot.sh new file mode 100644 index 0000000..7eb22cc --- /dev/null +++ b/tools/build-chainload-uboot.sh @@ -0,0 +1,177 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: Apache-2.0 +set -euo pipefail + +board=${1:-} +if [[ -z "$board" || ! "$board" =~ ^[a-z0-9][a-z0-9_-]*$ ]]; then + echo "usage: $0 BOARD" >&2 + exit 2 +fi + +repo=$(cd "$(dirname "$0")/.." && pwd) +manifest="$repo/config/chainload/$board.json" +if [[ ! -f "$manifest" ]]; then + echo "chainload: board '$board' has no manifest" >&2 + exit 2 +fi + +manifest_tool="$repo/tools/chainload-manifest.py" +python3 "$manifest_tool" validate "$board" + +json() { + python3 "$manifest_tool" get "$board" "$1" +} + +manifest_board=$(json board) +[[ "$manifest_board" == "$board" ]] || { + echo "chainload: manifest identity mismatch" >&2 + exit 2 +} + +fit_name=$(json artifacts.fit) +source_name=$(json artifacts.source) +output="$repo/$fit_name" +source_output="$repo/$source_name" + +if [[ -n "${UBOOT_ITB:-}" ]]; then + prebuilt=$(cd "$(dirname "$UBOOT_ITB")" && pwd)/$(basename "$UBOOT_ITB") + [[ -f "$prebuilt" ]] || { echo "UBOOT_ITB does not exist: $prebuilt" >&2; exit 2; } + rm -f -- "$source_output" + if [[ "$prebuilt" != "$output" ]]; then + cp "$prebuilt" "$output" + fi + echo "chainload: copied development FIT to $fit_name" + exit 0 +fi + +rm -f -- "$output" "$source_output" + +uboot_repo=$(json uboot.repository) +uboot_backend=$(json uboot.backend) +uboot_ref=$(json uboot.ref) +uboot_commit=$(json uboot.commit) +defconfig=$(json uboot.defconfig) +overlay_rel=$(json uboot.overlay) +bl31_rel=$(json bl31.path) +bl31_hash=$(json bl31.sha256) +bl31_size=$(json bl31.size) +overlay="$repo/$overlay_rel" +bl31="$repo/$bl31_rel" + +[[ -d "$overlay" ]] || { echo "chainload: missing board overlay: $overlay_rel" >&2; exit 2; } +[[ -f "$bl31" ]] || { echo "chainload: missing BL31: $bl31_rel" >&2; exit 2; } +actual_hash=$(sha256sum "$bl31" | awk '{print $1}') +actual_size=$(wc -c < "$bl31" | tr -d '[:space:]') +[[ "$actual_hash" == "$bl31_hash" && "$actual_size" == "$bl31_size" ]] || { + echo "chainload: BL31 provenance check failed" >&2 + exit 2 +} + +build_root=${CHAINLOAD_BUILD_DIR:-"$repo/build/chainload/$board"} +mkdir -p "$build_root/cache" +build_root=$(cd "$build_root" && pwd) +case "$build_root" in + /|"$repo"|"$repo/build"|"$repo/build/chainload") + echo "chainload: refusing unsafe build directory: $build_root" >&2 + exit 2 + ;; +esac + +if [[ -n "${UBOOT_SRC:-}" ]]; then + source_git=$(cd "$UBOOT_SRC" && pwd) + git -C "$source_git" cat-file -e "$uboot_commit^{commit}" 2>/dev/null || { + echo "chainload: UBOOT_SRC does not contain pinned commit $uboot_commit" >&2 + exit 2 + } +else + source_git="$build_root/cache/u-boot.git" + if [[ ! -d "$source_git" ]]; then + git init --bare "$source_git" + git -C "$source_git" remote add origin "$uboot_repo" + fi + if ! git -C "$source_git" cat-file -e "$uboot_commit^{commit}" 2>/dev/null; then + git -C "$source_git" fetch --depth=1 origin "$uboot_ref" + git -C "$source_git" cat-file -e "$uboot_commit^{commit}" 2>/dev/null || { + echo "chainload: $uboot_ref no longer contains pinned commit $uboot_commit" >&2 + exit 2 + } + fi +fi + +snapshot="$build_root/source" +if [[ -e "$snapshot" ]]; then + case "$snapshot" in "$build_root"/*) rm -rf -- "$snapshot" ;; *) exit 2 ;; esac +fi +mkdir -p "$snapshot" +git -C "$source_git" archive "$uboot_commit" | tar -x -C "$snapshot" +cp -a "$overlay/." "$snapshot/" +if [[ -f "$snapshot/arch/arm/mach-rockchip/decode_bl31.py" ]]; then + chmod 0755 "$snapshot/arch/arm/mach-rockchip/decode_bl31.py" +fi + +epoch=$(git -C "$source_git" show -s --format=%ct "$uboot_commit") +source_tmp="$build_root/$source_name.tmp" +tar --sort=name --owner=0 --group=0 --numeric-owner \ + --mode='u=rwX,go=rX' --mtime="@$epoch" \ + --transform="s,^\./,$board-u-boot-source/," \ + -c -C "$snapshot" . | xz -9 -T1 > "$source_tmp" +mv "$source_tmp" "$source_output" + +jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1)} +cross=${CROSS_COMPILE:-aarch64-linux-gnu-} +export BL31="$bl31" +export ROCKCHIP_TPL="$repo/$(json boot_media.ddr)" +export SOURCE_DATE_EPOCH="$epoch" +export KBUILD_BUILD_TIMESTAMP="@$epoch" +export KBUILD_BUILD_USER=rk-chainload +export KBUILD_BUILD_HOST=github-actions +export GIT_CEILING_DIRECTORIES="$build_root" +make -C "$snapshot" CROSS_COMPILE="$cross" "$defconfig" +case "$uboot_backend" in +vendor-fit) + # Armbian's pinned RK35xx flow explicitly builds u-boot.dtb before + # u-boot.itb and demotes diagnostics that modern GCC promotes in this + # vendor tree. Keep those accommodations inside the vendor backend. + vendor_cflags=( + -fdiagnostics-color=always + -Wno-error=maybe-uninitialized + -Wno-error=misleading-indentation + -Wno-error=attributes + -Wno-error=address-of-packed-member + -Wno-error=implicit-function-declaration + -Wno-error=implicit-int + -Wno-error=int-conversion + -Wno-error=incompatible-pointer-types + -Wno-error=array-parameter + ) + vendor_flags="${vendor_cflags[*]}" + make -C "$snapshot" CROSS_COMPILE="$cross" \ + CFLAGS="$vendor_flags" KCFLAGS="$vendor_flags" -j"$jobs" \ + u-boot.bin tools u-boot.its u-boot.dtb + [[ -s "$snapshot/u-boot.dtb" ]] || { echo "chainload: vendor U-Boot created an empty u-boot.dtb" >&2; exit 2; } + # Use inline FIT data: the dedicated chainloader intentionally rejects + # the vendor make target's external-data (-E) representation. + (cd "$snapshot" && ./tools/mkimage -f u-boot.its u-boot.itb) + fit_listing=$(cd "$snapshot" && ./tools/dumpimage -l u-boot.itb) + if grep -Eq 'Data Size:[[:space:]]+0 Bytes' <<< "$fit_listing"; then + echo "chainload: vendor U-Boot FIT contains a zero-byte image" >&2 + exit 2 + fi + ;; +mainline-fit) + fragment="$repo/$(json uboot.config_fragment)" + [[ -f "$fragment" ]] || { echo "chainload: missing config fragment" >&2; exit 2; } + (cd "$snapshot" && bash scripts/kconfig/merge_config.sh -m .config "$fragment") + make -C "$snapshot" CROSS_COMPILE="$cross" olddefconfig + make -C "$snapshot" CROSS_COMPILE="$cross" -j"$jobs" \ + all + ;; +*) + echo "chainload: unsupported U-Boot backend: $uboot_backend" >&2 + exit 2 + ;; +esac +[[ -x "$snapshot/tools/mkimage" ]] || { echo "chainload: U-Boot did not build tools/mkimage" >&2; exit 2; } +[[ -f "$snapshot/u-boot.itb" ]] || { echo "chainload: U-Boot did not create u-boot.itb" >&2; exit 2; } +cp "$snapshot/u-boot.itb" "$output" +echo "chainload: built $fit_name and $source_name" diff --git a/tools/chainfit.c b/tools/chainfit.c new file mode 100644 index 0000000..31e4c2a --- /dev/null +++ b/tools/chainfit.c @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include +#include +#include +#include +#include + +#include "chainload/chainload.h" + +#define MAX_BL31_RANGES 8 + +static int parse_number(const char *text, uintptr_t *result) { + char *end; + unsigned long long value; + errno = 0; + value = strtoull(text, &end, 0); + if (errno || !*text || *end || value > UINTPTR_MAX) + return -1; + *result = (uintptr_t)value; + return 0; +} + +int main(int argc, char **argv) { + struct ChainFitPlan plan; + const char *reason; + unsigned char *data; + FILE *file; + long size; + struct ChainPlatform platform = { 0 }; + struct ChainAddressRange ranges[MAX_BL31_RANGES]; + uintptr_t segments; + unsigned int range_count; + if (argc < 12 || ((argc - 10) & 1)) { + fprintf(stderr, "usage: %s BOARD FIT FIT_START FIT_END PARAMS " + "BL31_ENTRY BL33_ENTRY BL33_LIMIT SEGMENTS RANGE_START RANGE_END [...]\n", + argv[0]); + return 2; + } + range_count = (unsigned int)(argc - 10) / 2; + if (range_count > MAX_BL31_RANGES || + parse_number(argv[3], &platform.fit_stage_start) || + parse_number(argv[4], &platform.fit_stage_end) || + parse_number(argv[5], &platform.params_addr) || + parse_number(argv[6], &platform.expected_bl31_entry) || + parse_number(argv[7], &platform.expected_bl33_entry) || + parse_number(argv[8], &platform.bl33_limit) || + parse_number(argv[9], &segments) || segments > UINT32_MAX) { + fprintf(stderr, "invalid manifest-derived address policy\n"); + return 2; + } + for (unsigned int index = 0; index < range_count; index++) { + if (parse_number(argv[10 + index * 2], &ranges[index].start) || + parse_number(argv[11 + index * 2], &ranges[index].end)) { + fprintf(stderr, "invalid manifest-derived BL31 range\n"); + return 2; + } + } + platform.board = argv[1]; + platform.soc = "rk3568"; + platform.handoff_protocol = CHAIN_HANDOFF_TFA_V1_BL33_EL2; + platform.bl31_ranges = ranges; + platform.bl31_range_count = range_count; + platform.expected_bl31_segments = (uint32_t)segments; + file = fopen(argv[2], "rb"); + if (!file) { + perror(argv[2]); + return 2; + } + if (fseek(file, 0, SEEK_END) || (size = ftell(file)) <= 0 || + fseek(file, 0, SEEK_SET)) { + fprintf(stderr, "cannot size FIT\n"); + return 2; + } + data = malloc((size_t)size); + if (!data || fread(data, 1, (size_t)size, file) != (size_t)size) { + fprintf(stderr, "cannot read FIT\n"); + return 2; + } + fclose(file); + if (chain_fit_parse(data, (size_t)size, &platform, &plan, &reason)) { + fprintf(stderr, "invalid FIT: %s\n", reason); + return 1; + } + printf("board=%s bl31_segments=%u bl31_entry=0x%lx " + "bl33_entry=0x%lx control_fdt=0x%lx\n", argv[1], plan.bl31_count, + (unsigned long)plan.bl31_entry, (unsigned long)plan.bl33_entry, + (unsigned long)plan.control_fdt); + free(data); + return 0; +} diff --git a/tools/chainload-manifest.py b/tools/chainload-manifest.py new file mode 100644 index 0000000..40048a2 --- /dev/null +++ b/tools/chainload-manifest.py @@ -0,0 +1,447 @@ +#!/usr/bin/env python3 +"""Validate and query board-scoped chainloader manifests.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import pathlib +import re +import sys +from typing import Any + +ROOT = pathlib.Path(__file__).resolve().parents[1] +MANIFEST_DIR = ROOT / "config" / "chainload" +BOARD_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*$") +BACKENDS = {"vendor-fit", "mainline-fit"} +ARTIFACT_KEYS = ("fit", "binary", "image", "idblock", "spi_nor", "source") +BOOT_ORDER = ["spi-nor", "spi-nand", "nand", "emmc", "sd", "usb"] +AUTOMATIC_MEDIA_ORDER = ["nvme", "sd", "usb", "emmc"] + + +class ManifestError(ValueError): + pass + + +def fail(message: str) -> None: + raise ManifestError(message) + + +def integer(value: Any, field: str) -> int: + if isinstance(value, bool): + fail(f"{field} must be an integer") + try: + parsed = int(value, 0) if isinstance(value, str) else int(value) + except (TypeError, ValueError): + fail(f"{field} must be an integer") + if parsed < 0: + fail(f"{field} may not be negative") + return parsed + + +def relative_path(value: Any, field: str) -> pathlib.PurePosixPath: + if not isinstance(value, str) or not value: + fail(f"{field} must be a non-empty path") + path = pathlib.PurePosixPath(value) + if path.is_absolute() or ".." in path.parts or "\\" in value: + fail(f"{field} must stay inside the repository") + return path + + +def repository_entry(value: Any, field: str, directory: bool = False) -> pathlib.Path: + relative = relative_path(value, field) + path = ROOT.joinpath(*relative.parts) + valid = path.is_dir() if directory else path.is_file() + if not valid or path.is_symlink(): + fail(f"{field} does not name a safe {'directory' if directory else 'file'}") + return path + + +def mapping(value: Any, field: str) -> dict[str, Any]: + if not isinstance(value, dict): + fail(f"{field} must be an object") + return value + + +def nonempty_string(value: Any, field: str) -> str: + if not isinstance(value, str) or not value: + fail(f"{field} must be a non-empty string") + return value + + +def require_keys(value: dict[str, Any], keys: set[str], field: str, + optional: set[str] | None = None) -> None: + optional = optional or set() + missing = keys - value.keys() + extra = value.keys() - keys - optional + if missing: + fail(f"{field} is missing: {', '.join(sorted(missing))}") + if extra: + fail(f"{field} has unknown fields: {', '.join(sorted(extra))}") + + +def load_raw(board: str) -> dict[str, Any]: + if not BOARD_RE.fullmatch(board): + fail(f"invalid board identifier: {board}") + path = MANIFEST_DIR / f"{board}.json" + if not path.is_file(): + fail(f"board '{board}' has no chainload manifest") + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as error: + fail(f"cannot read {path.relative_to(ROOT)}: {error}") + if not isinstance(data, dict): + fail(f"{path.name} must contain an object") + return data + + +def validate(board: str) -> dict[str, Any]: + data = load_raw(board) + require_keys(data, { + "schema", "board", "identity", "soc", "platform", "uboot", "bl31", + "layout", "artifacts", "boot_media", "host_tools", "boot_policy", + }, board) + if data["schema"] != 3: + fail(f"{board}: unsupported manifest schema") + if data["board"] != board: + fail(f"{board}: manifest identity does not match its filename") + if data["soc"] != "rk3568" or data["platform"] != "rk3568": + fail(f"{board}: unsupported SoC/platform") + nonempty_string(data["identity"], f"{board}.identity") + + uboot = mapping(data["uboot"], f"{board}.uboot") + require_keys(uboot, { + "backend", "repository", "ref", "commit", "defconfig", "overlay", + "armbian_repository", "armbian_commit", + }, f"{board}.uboot", {"config_fragment", "armbian_path"}) + if uboot["backend"] not in BACKENDS: + fail(f"{board}: unsupported U-Boot backend") + for field in ("repository", "ref", "defconfig", "armbian_repository"): + nonempty_string(uboot[field], f"{board}.uboot.{field}") + if not re.fullmatch(r"[0-9a-f]{40}", str(uboot["commit"])): + fail(f"{board}: U-Boot commit must be a full SHA-1") + if not re.fullmatch(r"[0-9a-f]{40}", str(uboot["armbian_commit"])): + fail(f"{board}: Armbian commit must be a full SHA-1") + overlay = relative_path(uboot["overlay"], f"{board}.uboot.overlay") + expected_overlay = pathlib.PurePosixPath("config", "chainload", board, "overlay") + if overlay != expected_overlay: + fail(f"{board}: overlay must be isolated under {expected_overlay}") + overlay_path = repository_entry(uboot["overlay"], f"{board}.uboot.overlay", + directory=True) + if any(path.is_symlink() for path in overlay_path.rglob("*")): + fail(f"{board}: overlay may not contain symbolic links") + if uboot["backend"] == "mainline-fit": + fragment = relative_path(uboot.get("config_fragment"), + f"{board}.uboot.config_fragment") + if fragment.parts[:len(overlay.parts)] != overlay.parts: + fail(f"{board}: config fragment escapes its board overlay") + repository_entry(uboot["config_fragment"], + f"{board}.uboot.config_fragment") + relative_path(uboot.get("armbian_path"), f"{board}.uboot.armbian_path") + elif "config_fragment" in uboot: + fail(f"{board}: vendor-fit does not accept a config fragment") + + bl31 = mapping(data["bl31"], f"{board}.bl31") + require_keys(bl31, { + "path", "rkbin_repository", "rkbin_commit", "rkbin_path", "size", "sha256", + }, f"{board}.bl31") + bl31_path = repository_entry(bl31["path"], f"{board}.bl31.path") + bl31_size = integer(bl31["size"], f"{board}.bl31.size") + if bl31_size == 0: + fail(f"{board}: BL31 may not be empty") + if not re.fullmatch(r"[0-9a-f]{64}", str(bl31["sha256"])): + fail(f"{board}: BL31 SHA-256 is invalid") + if bl31_path.stat().st_size != bl31_size or \ + hashlib.sha256(bl31_path.read_bytes()).hexdigest() != bl31["sha256"]: + fail(f"{board}: BL31 file does not match its provenance") + + layout = mapping(data["layout"], f"{board}.layout") + require_keys(layout, { + "stage_limit", "fit_stage_start", "fit_stage_end", "bl31_params", + "bl31_entry", "bl31_ranges", "expected_bl31_segments", "bl33_load", + "bl33_stack", "handoff_protocol", + }, f"{board}.layout") + stage_limit = integer(layout["stage_limit"], f"{board}.layout.stage_limit") + fit_start = integer(layout["fit_stage_start"], f"{board}.layout.fit_stage_start") + fit_end = integer(layout["fit_stage_end"], f"{board}.layout.fit_stage_end") + params = integer(layout["bl31_params"], f"{board}.layout.bl31_params") + bl31_entry = integer(layout["bl31_entry"], f"{board}.layout.bl31_entry") + bl33_load = integer(layout["bl33_load"], f"{board}.layout.bl33_load") + bl33_stack = integer(layout["bl33_stack"], f"{board}.layout.bl33_stack") + segments = integer(layout["expected_bl31_segments"], + f"{board}.layout.expected_bl31_segments") + if stage_limit != 0x40000 or not (fit_start < fit_end) or not (bl33_load < bl33_stack): + fail(f"{board}: invalid stage/FIT/BL33 bounds") + if max(bl33_load, fit_start) < min(bl33_stack, fit_end): + fail(f"{board}: BL33 range overlaps the FIT staging arena") + if params >= 0x200000 or segments == 0: + fail(f"{board}: invalid TF-A parameter or segment policy") + if layout["handoff_protocol"] != "tf-a-v1-bl33-aarch64-el2": + fail(f"{board}: unsupported handoff protocol") + ranges = layout["bl31_ranges"] + if not isinstance(ranges, list) or not ranges: + fail(f"{board}: BL31 ranges must be a non-empty list") + parsed_ranges: list[tuple[int, int]] = [] + for index, item in enumerate(ranges): + if not isinstance(item, list) or len(item) != 2: + fail(f"{board}: BL31 range {index} must contain start and end") + start = integer(item[0], f"{board}.layout.bl31_ranges[{index}][0]") + end = integer(item[1], f"{board}.layout.bl31_ranges[{index}][1]") + if start >= end: + fail(f"{board}: BL31 range {index} is empty") + parsed_ranges.append((start, end)) + if not any(start <= bl31_entry < end for start, end in parsed_ranges): + fail(f"{board}: BL31 entry is outside its permitted ranges") + for index, first in enumerate(parsed_ranges): + for second in parsed_ranges[index + 1:]: + if max(first[0], second[0]) < min(first[1], second[1]): + fail(f"{board}: permitted BL31 ranges overlap") + named_regions = ( + ("stage", 0, stage_limit), + ("FIT staging", fit_start, fit_end), + ("BL33", bl33_load, bl33_stack), + ) + for index, first in enumerate(named_regions): + for second in named_regions[index + 1:]: + if max(first[1], second[1]) < min(first[2], second[2]): + fail(f"{board}: {first[0]} and {second[0]} ranges overlap") + if any(start <= params < end for _, start, end in named_regions): + fail(f"{board}: TF-A parameters overlap a loaded/staging range") + + artifacts = mapping(data["artifacts"], f"{board}.artifacts") + require_keys(artifacts, set(ARTIFACT_KEYS), f"{board}.artifacts") + expected_artifacts = { + "fit": f"{board}-u-boot.itb", + "binary": f"uboot_{board}.bin", + "image": f"uboot_{board}.img", + "idblock": f"uboot_{board}_idbloader.img", + "spi_nor": f"uboot_{board}_spi.img", + "source": f"{board}-u-boot-source.tar.xz", + } + if artifacts != expected_artifacts: + fail(f"{board}: artifact names must be board-qualified") + for key, value in artifacts.items(): + path = relative_path(value, f"{board}.artifacts.{key}") + if len(path.parts) != 1: + fail(f"{board}: artifacts must be emitted at the repository root") + + media = mapping(data["boot_media"], f"{board}.boot_media") + require_keys(media, {"bootrom_order", "ddr", "usbplug", "sd", "emmc", "spi-nor"}, + f"{board}.boot_media") + if media["bootrom_order"] != BOOT_ORDER: + fail(f"{board}: RK3568 BootROM order changed") + repository_entry(media["ddr"], f"{board}.boot_media.ddr") + repository_entry(media["usbplug"], f"{board}.boot_media.usbplug") + expected_media_artifacts = { + "sd": artifacts["image"], "emmc": artifacts["idblock"], + "spi-nor": artifacts["spi_nor"], + } + sd = mapping(media["sd"], f"{board}.boot_media.sd") + emmc = mapping(media["emmc"], f"{board}.boot_media.emmc") + spi = mapping(media["spi-nor"], f"{board}.boot_media.spi-nor") + require_keys(sd, {"format", "idblock_lba", "artifact"}, + f"{board}.boot_media.sd") + require_keys(emmc, {"format", "storage_id", "write_lba", "capacity_policy", + "preserve_partition_table", "required_pinctrl", "artifact"}, + f"{board}.boot_media.emmc") + require_keys(spi, {"format", "storage_id", "write_lba", "capacity_policy", + "required_pinctrl", "artifact"}, + f"{board}.boot_media.spi-nor") + if sd["format"] != "rksd" or emmc["format"] != "rksd" or spi["format"] != "rkspi": + fail(f"{board}: unsupported Rockchip media format") + if integer(sd["idblock_lba"], f"{board}.boot_media.sd.idblock_lba") != 64 or \ + integer(emmc["write_lba"], f"{board}.boot_media.emmc.write_lba") != 64 or \ + integer(spi["write_lba"], f"{board}.boot_media.spi-nor.write_lba") != 0: + fail(f"{board}: BootROM media offsets are invalid") + if emmc["preserve_partition_table"] is not True: + fail(f"{board}: eMMC policy must preserve the partition table") + if emmc["capacity_policy"] != "detected-size-must-cover-image" or \ + spi["capacity_policy"] != "detected-size-must-cover-image": + fail(f"{board}: unsupported detected-capacity policy") + for name, entry in (("sd", sd), ("emmc", emmc), ("spi-nor", spi)): + if entry["artifact"] != expected_media_artifacts[name]: + fail(f"{board}: {name} selects another board's artifact") + for name, entry in (("emmc", emmc), ("spi-nor", spi)): + integer(entry["storage_id"], f"{board}.boot_media.{name}.storage_id") + nonempty_string(entry["required_pinctrl"], + f"{board}.boot_media.{name}.required_pinctrl") + + host_tools = mapping(data["host_tools"], f"{board}.host_tools") + require_keys(host_tools, {"xrock", "rkdeveloptool"}, f"{board}.host_tools") + for name in ("xrock", "rkdeveloptool"): + tool = mapping(host_tools[name], f"{board}.host_tools.{name}") + require_keys(tool, {"repository", "commit"}, f"{board}.host_tools.{name}") + nonempty_string(tool["repository"], f"{board}.host_tools.{name}.repository") + if not re.fullmatch(r"[0-9a-f]{40}", str(tool["commit"])): + fail(f"{board}: {name} commit must be a full SHA-1") + + policy = mapping(data["boot_policy"], f"{board}.boot_policy") + require_keys(policy, { + "automatic_scan", "boot_delay_seconds", "baud_rate", "interactive_only", + "formats", + }, f"{board}.boot_policy") + scan = mapping(policy["automatic_scan"], f"{board}.boot_policy.automatic_scan") + require_keys(scan, {"order", "targets"}, + f"{board}.boot_policy.automatic_scan") + if scan["order"] != AUTOMATIC_MEDIA_ORDER: + fail(f"{board}: automatic scan order must be NVMe, SD, USB, then eMMC") + targets = mapping(scan["targets"], + f"{board}.boot_policy.automatic_scan.targets") + require_keys(targets, set(AUTOMATIC_MEDIA_ORDER), + f"{board}.boot_policy.automatic_scan.targets") + target_patterns = { + "nvme": r"nvme(?:[0-9]+)?", + "sd": r"mmc[0-9]+", + "usb": r"usb(?:[0-9]+)?", + "emmc": r"mmc[0-9]+", + } + flattened: list[str] = [] + for medium in AUTOMATIC_MEDIA_ORDER: + entries = targets[medium] + if not isinstance(entries, list) or not entries or any( + not isinstance(target, str) or + not re.fullmatch(target_patterns[medium], target) + for target in entries): + fail(f"{board}: invalid automatic {medium} targets") + flattened.extend(entries) + if len(flattened) != len(set(flattened)): + fail(f"{board}: automatic boot targets overlap across media") + if not isinstance(policy["interactive_only"], list) or \ + not all(isinstance(item, str) and item for item in policy["interactive_only"]): + fail(f"{board}: interactive-only commands must be a string list") + if set(policy["interactive_only"]) & {"nvme", "mmc", "usb"}: + fail(f"{board}: automatically scanned media cannot be interactive-only") + if not isinstance(policy["formats"], list) or \ + not all(isinstance(item, str) and item for item in policy["formats"]): + fail(f"{board}: boot formats must be a string list") + integer(policy["boot_delay_seconds"], f"{board}.boot_policy.boot_delay_seconds") + if integer(policy["baud_rate"], f"{board}.boot_policy.baud_rate") != 1500000: + fail(f"{board}: UART must remain at 1.5 Mbaud") + return data + + +def manifests() -> list[str]: + return [path.stem for path in sorted(MANIFEST_DIR.glob("*.json"))] + + +def validate_all() -> dict[str, dict[str, Any]]: + boards = manifests() + if not boards: + fail("no chainloader manifests found") + result = {board: validate(board) for board in boards} + owners: dict[str, str] = {} + for board, data in result.items(): + for artifact in data["artifacts"].values(): + previous = owners.setdefault(artifact, board) + if previous != board: + fail(f"artifact collision: {artifact} belongs to {previous} and {board}") + return result + + +def lookup(data: Any, dotted: str) -> Any: + value = data + for component in dotted.split("."): + if not isinstance(value, dict) or component not in value: + fail(f"manifest field does not exist: {dotted}") + value = value[component] + return value + + +def c_hex(value: Any) -> str: + return f"0x{integer(value, 'generated value'):x}UL" + + +def generate_header(board: str, output: pathlib.Path) -> None: + data = validate(board) + layout = data["layout"] + ranges = layout["bl31_ranges"] + range_lines = (" " + chr(92) + "\n").join( + f"\t{{ {c_hex(item[0])}, {c_hex(item[1])} }}," for item in ranges + ) + content = f"""/* Generated from config/chainload/{board}.json; do not edit. */ +#ifndef CHAINLOAD_BOARD_CONFIG_H +#define CHAINLOAD_BOARD_CONFIG_H + +#define CHAIN_BOARD_NAME {json.dumps(data['identity'])} +#define CHAIN_SOC_NAME "Rockchip RK3568" +#define CHAIN_FIT_STAGE_START {c_hex(layout['fit_stage_start'])} +#define CHAIN_FIT_STAGE_END {c_hex(layout['fit_stage_end'])} +#define CHAIN_PARAMS_ADDR {c_hex(layout['bl31_params'])} +#define CHAIN_EXPECTED_BL31_ENTRY {c_hex(layout['bl31_entry'])} +#define CHAIN_EXPECTED_BL33_ENTRY {c_hex(layout['bl33_load'])} +#define CHAIN_BL33_LIMIT {c_hex(layout['bl33_stack'])} +#define CHAIN_EXPECTED_BL31_SEGMENTS {integer(layout['expected_bl31_segments'], 'segments')} +#define CHAIN_BL31_RANGE_COUNT {len(ranges)} +#define CHAIN_BL31_RANGE_INITIALIZER \\ +{range_lines} + +#endif +""" + output.parent.mkdir(parents=True, exist_ok=True) + temporary = output.with_suffix(output.suffix + ".tmp") + temporary.write_text(content, encoding="utf-8", newline="\n") + temporary.replace(output) + + +def main() -> int: + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest="command", required=True) + subparsers.add_parser("list") + validate_parser = subparsers.add_parser("validate") + validate_parser.add_argument("board", nargs="?") + validate_parser.add_argument("--all", action="store_true") + get_parser = subparsers.add_parser("get") + get_parser.add_argument("board") + get_parser.add_argument("field") + artifacts_parser = subparsers.add_parser("artifacts") + artifacts_parser.add_argument("board") + fit_args_parser = subparsers.add_parser("chainfit-args") + fit_args_parser.add_argument("board") + header_parser = subparsers.add_parser("generate-header") + header_parser.add_argument("board") + header_parser.add_argument("output", type=pathlib.Path) + args = parser.parse_args() + + if args.command == "list": + validate_all() + print(" ".join(manifests())) + elif args.command == "validate": + if args.all: + validate_all() + elif args.board: + validate(args.board) + else: + fail("validate requires BOARD or --all") + elif args.command == "get": + value = lookup(validate(args.board), args.field) + if isinstance(value, (dict, list)): + print(json.dumps(value, separators=(",", ":"))) + elif isinstance(value, bool): + print("true" if value else "false") + else: + print(value) + elif args.command == "artifacts": + data = validate(args.board) + print(" ".join(str(data["artifacts"][key]) for key in ARTIFACT_KEYS)) + elif args.command == "chainfit-args": + layout = validate(args.board)["layout"] + values = [ + layout["fit_stage_start"], layout["fit_stage_end"], + layout["bl31_params"], layout["bl31_entry"], layout["bl33_load"], + layout["bl33_stack"], layout["expected_bl31_segments"], + ] + for start, end in layout["bl31_ranges"]: + values.extend((start, end)) + print(" ".join(str(value) for value in values)) + elif args.command == "generate-header": + generate_header(args.board, args.output) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except ManifestError as error: + print(f"chainload manifest error: {error}", file=sys.stderr) + raise SystemExit(2) diff --git a/tools/check-partition-overlap.py b/tools/check-partition-overlap.py new file mode 100644 index 0000000..25f3e3f --- /dev/null +++ b/tools/check-partition-overlap.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""Reject an eMMC ID-block write that overlaps an existing MBR/GPT partition.""" + +from __future__ import annotations + +import pathlib +import struct +import sys +import zlib + + +def fail(message: str) -> "NoReturn": + raise ValueError(message) + + +def overlaps(first: int, last: int, target_first: int, target_last: int) -> bool: + return first <= target_last and target_first <= last + + +def check_gpt(data: bytes, target_first: int, target_last: int) -> bool: + sector = 512 + if len(data) < sector * 2 or data[sector:sector + 8] != b"EFI PART": + return False + header = data[sector:sector * 2] + header_size, header_crc = struct.unpack_from(" 4096 or entry_count > 4096: + fail("unsupported GPT entry table") + table_start = entry_lba * sector + table_size = entry_count * entry_size + table_end = table_start + table_size + if table_end > len(data): + fail("GPT entry table is outside the backed-up metadata") + table = data[table_start:table_end] + if zlib.crc32(table) & 0xFFFFFFFF != entries_crc: + fail("invalid GPT entry-table CRC") + for index in range(entry_count): + entry = table[index * entry_size:(index + 1) * entry_size] + if entry[:16] == b"\0" * 16: + continue + first, last = struct.unpack_from(" last: + fail(f"GPT partition {index + 1} has an invalid range") + if overlaps(first, last, target_first, target_last): + fail( + f"GPT partition {index + 1} ({first}-{last}) overlaps " + f"the firmware range {target_first}-{target_last}" + ) + print("eMMC GPT partitions do not overlap the firmware range") + return True + + +def check_mbr(data: bytes, target_first: int, target_last: int) -> bool: + if len(data) < 512 or data[510:512] != b"\x55\xaa": + return False + found = False + for index in range(4): + entry = data[446 + index * 16:446 + (index + 1) * 16] + kind = entry[4] + first, count = struct.unpack_from(" int: + if len(sys.argv) != 4: + print(f"usage: {sys.argv[0]} METADATA START_LBA SECTORS", file=sys.stderr) + return 2 + data = pathlib.Path(sys.argv[1]).read_bytes() + start = int(sys.argv[2], 0) + sectors = int(sys.argv[3], 0) + if start < 0 or sectors <= 0: + fail("invalid firmware LBA range") + last = start + sectors - 1 + gpt = check_gpt(data, start, last) + mbr = check_mbr(data, start, last) + if not gpt and not mbr: + print("no MBR/GPT partition table detected in the eMMC metadata") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (OSError, ValueError) as error: + print(f"partition check failed: {error}", file=sys.stderr) + raise SystemExit(1) diff --git a/tools/flash-chainload.sh b/tools/flash-chainload.sh new file mode 100644 index 0000000..684d874 --- /dev/null +++ b/tools/flash-chainload.sh @@ -0,0 +1,337 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: Apache-2.0 +set -euo pipefail + +repo=${CHAINLOAD_REPO:-$(cd "$(dirname "$0")/.." && pwd)} +repo=$(cd "$repo" && pwd) +xrock=${XROCK:-xrock} +rkdeveloptool=${RKDEVELOPTOOL:-rkdeveloptool} + +die() { + echo "chainload-flash: $*" >&2 + exit 2 +} + +need_tool() { + command -v "$1" >/dev/null 2>&1 || die "required tool not found: $1" +} + +json() { + python3 - "$1" "$2" <<'PY' +import json, sys +value = json.load(open(sys.argv[1], encoding="utf-8")) +for component in sys.argv[2].split("."): + value = value[component] +print(value) +PY +} + +find_manifest() { + local board=$1 primary bundled + primary="$repo/config/chainload/$board.json" + bundled="$repo/chainload/MANIFEST.json" + if [[ -f "$primary" && ! -L "$primary" ]]; then + printf '%s\n' "$primary" + elif [[ -f "$bundled" && ! -L "$bundled" ]] && [[ $(json "$bundled" board) == "$board" ]]; then + printf '%s\n' "$bundled" + else + return 1 + fi +} + +resolve_input() { + local relative=$1 candidate + for candidate in "$repo/$relative" "$repo/chainload/$(basename "$relative")" \ + "$repo/loaders/$(basename "$relative")"; do + if [[ -f "$candidate" && ! -L "$candidate" ]]; then + printf '%s\n' "$candidate" + return + fi + done + return 1 +} + +partition_checker() { + local candidate + for candidate in "$repo/tools/check-partition-overlap.py" \ + "$repo/install/check-partition-overlap.py"; do + [[ -f "$candidate" && ! -L "$candidate" ]] && { printf '%s\n' "$candidate"; return; } + done + return 1 +} + +require_sector_file() { + local file=$1 sectors=$2 description=$3 actual + [[ -f "$file" && ! -L "$file" ]] || die "$description was not created as a regular file" + actual=$(wc -c < "$file" | tr -d '[:space:]') + (( actual == sectors * 512 )) || die "$description has an unexpected size" +} + +record_jedec_id() { + local destination=$1 output id manufacturer + output=$("$rkdeveloptool" rid) + printf '%s\n' "$output" | tee "$destination" + id=$(printf '%s\n' "$output" | + sed -n 's/^Flash ID: \([0-9A-F][0-9A-F]\( [0-9A-F][0-9A-F]\)\{4\}\).*/\1/p' | + tail -n 1) + [[ -n "$id" ]] || die "SPI NOR JEDEC identification failed" + manufacturer=${id%% *} + [[ "$manufacturer" != 00 && "$manufacturer" != FF ]] || + die "SPI NOR returned an invalid JEDEC manufacturer ID" +} + +sha256_of() { + sha256sum "$1" | awk '{print $1}' +} + +device_list() { + "$rkdeveloptool" ld +} + +require_one_rk356x() { + local listing count + listing=$(device_list) + count=$(printf '%s\n' "$listing" | grep -Eic 'Vid=0x2207') + [[ $count -eq 1 ]] || die "expected exactly one Rockchip device, found $count" + printf '%s\n' "$listing" | grep -Eqi 'Pid=0x350a' || die "connected Rockchip device is not RK356x PID 0x350a" + printf '%s\n' "$listing" +} + +enter_loader() { + local ddr=$1 usbplug=$2 listing ready=0 + listing=$(require_one_rk356x) + if printf '%s\n' "$listing" | grep -Eqi 'Mode=Loader'; then + echo "chainload-flash: device is already in loader mode" + return + fi + printf '%s\n' "$listing" | grep -Eqi 'Mode=Maskrom' || die "device is neither MaskROM nor loader mode" + "$xrock" maskrom "$ddr" "$usbplug" --rc4-off + for _ in $(seq 1 20); do + listing=$(device_list 2>/dev/null || true) + if [[ $(printf '%s\n' "$listing" | grep -Eic 'Vid=0x2207') -eq 1 ]] && + printf '%s\n' "$listing" | grep -Eqi 'Pid=0x350a.*Mode=Loader'; then + ready=1 + break + fi + sleep 0.25 + done + [[ $ready -eq 1 ]] || die "RK356x did not enter loader mode" +} + +select_storage() { + local storage_id=$1 output + output=$("$rkdeveloptool" cs "$storage_id") + printf '%s\n' "$output" + printf '%s\n' "$output" | grep -q 'Change Storage OK' || die "storage selection was not confirmed" +} + +flash_info() { + local output + output=$("$rkdeveloptool" rfi) + printf '%s\n' "$output" | tee "$1" >&2 + printf '%s\n' "$output" | sed -n 's/.*Flash Size: \([0-9][0-9]*\) Sectors.*/\1/p' | tail -n 1 +} + +write_backup_manifest() { + python3 - "$@" <<'PY' +import hashlib, json, pathlib, sys +out, board, media, storage, start, installed_sectors, capacity, backup_file, backup_sectors, installed, manifest = sys.argv[1:] +backup = pathlib.Path(out).parent / backup_file +payload = pathlib.Path(installed) +doc = { + "schema": 2, + "board": board, + "media": media, + "storage_id": int(storage), + "write_lba": int(start), + "installed_sectors": int(installed_sectors), + "capacity_sectors": int(capacity), + "backup_file": backup_file, + "backup_sectors": int(backup_sectors), + "backup_sha256": hashlib.sha256(backup.read_bytes()).hexdigest(), + "installed_sha256": hashlib.sha256(payload.read_bytes()).hexdigest(), + "manifest_sha256": hashlib.sha256(pathlib.Path(manifest).read_bytes()).hexdigest(), +} +pathlib.Path(out).write_text(json.dumps(doc, indent=2, sort_keys=True) + "\n", encoding="utf-8") +PY +} + +prepare_backup_dir() { + local destination=$1 + case "$destination" in /*|[A-Za-z]:/*) ;; *) die "BACKUP_DIR must be an absolute path" ;; esac + [[ ! -L "$destination" ]] || die "BACKUP_DIR may not be a symbolic link" + if [[ -e "$destination" ]]; then + [[ -d "$destination" ]] || die "BACKUP_DIR is not a directory" + [[ -z $(find "$destination" -mindepth 1 -maxdepth 1 -print -quit) ]] || die "BACKUP_DIR must be empty" + else + mkdir -p -- "$destination" + fi +} + +flash_media() { + local board=$1 media=$2 backup_dir=$3 confirm=$4 + [[ "$confirm" == "$board:$media" ]] || die "CONFIRM must equal '$board:$media'" + [[ "$board" =~ ^[a-z0-9][a-z0-9_-]*$ ]] || die "invalid board identifier" + [[ "$media" == emmc || "$media" == spi-nor ]] || die "MEDIA must be emmc or spi-nor" + need_tool python3 + local manifest artifact ddr usbplug storage start bytes sectors capacity info_file backup_file backup_sectors checker + manifest=$(find_manifest "$board") || die "board '$board' has no chainload manifest" + [[ $(json "$manifest" board) == "$board" ]] || die "chainload manifest identity mismatch" + artifact=$(resolve_input "$(json "$manifest" "boot_media.$media.artifact")") || die "media image is missing" + ddr=$(resolve_input "$(json "$manifest" boot_media.ddr)") || die "DDR loader is missing" + usbplug=$(resolve_input "$(json "$manifest" boot_media.usbplug)") || die "USB-plug loader is missing" + storage=$(json "$manifest" "boot_media.$media.storage_id") + start=$(json "$manifest" "boot_media.$media.write_lba") + bytes=$(wc -c < "$artifact" | tr -d '[:space:]') + (( bytes > 0 && bytes % 512 == 0 )) || die "media image is not sector aligned" + sectors=$((bytes / 512)) + + need_tool "$xrock" + need_tool "$rkdeveloptool" + need_tool sha256sum + "$rkdeveloptool" -h 2>&1 | grep -q 'ChangeStorage' || die "rkdeveloptool lacks storage selection support" + prepare_backup_dir "$backup_dir" + trap 'echo "chainload-flash: operation failed; the device was not reset" >&2; echo "chainload-flash: backup directory: '"$backup_dir"'" >&2' ERR + enter_loader "$ddr" "$usbplug" + require_one_rk356x >/dev/null + select_storage "$storage" + info_file="$backup_dir/flash-info.txt" + capacity=$(flash_info "$info_file") + [[ "$capacity" =~ ^[0-9]+$ && $capacity -gt 0 ]] || die "unable to determine selected-storage capacity" + (( start >= 0 && start + sectors <= capacity )) || + die "media image range exceeds selected-storage capacity" + + if [[ "$media" == emmc ]]; then + backup_file=previous-idblock-region.bin + backup_sectors=$sectors + "$rkdeveloptool" rl 0 64 "$backup_dir/emmc-lba0-63.bin" + "$rkdeveloptool" rl "$start" "$sectors" "$backup_dir/$backup_file" + require_sector_file "$backup_dir/emmc-lba0-63.bin" 64 "eMMC metadata backup" + require_sector_file "$backup_dir/$backup_file" "$sectors" "eMMC destination backup" + checker=$(partition_checker) || die "partition-overlap checker is missing" + python3 "$checker" \ + "$backup_dir/emmc-lba0-63.bin" "$start" "$sectors" + else + backup_file=complete-spi-nor.bin + backup_sectors=$capacity + record_jedec_id "$backup_dir/flash-id.txt" + "$rkdeveloptool" rl 0 "$capacity" "$backup_dir/$backup_file" + require_sector_file "$backup_dir/$backup_file" "$capacity" "complete SPI-NOR backup" + fi + + write_backup_manifest "$backup_dir/backup.json" "$board" "$media" "$storage" \ + "$start" "$sectors" "$capacity" "$backup_file" "$backup_sectors" "$artifact" "$manifest" + sha256sum "$backup_dir/$backup_file" "$artifact" > "$backup_dir/SHA256SUMS" + "$rkdeveloptool" wl "$start" "$artifact" + "$rkdeveloptool" rl "$start" "$sectors" "$backup_dir/installed-readback.bin" + require_sector_file "$backup_dir/installed-readback.bin" "$sectors" "installed-image readback" + if [[ $(sha256_of "$artifact") != "$(sha256_of "$backup_dir/installed-readback.bin")" ]] || + ! cmp "$artifact" "$backup_dir/installed-readback.bin"; then + echo "chainload-flash: write verification mismatch; the device remains in loader mode" >&2 + echo "chainload-flash: restore with: make restore-chainload BACKUP=$backup_dir CONFIRM=restore:$board:$media" >&2 + exit 2 + fi + sha256sum "$backup_dir/installed-readback.bin" >> "$backup_dir/SHA256SUMS" + trap - ERR + "$rkdeveloptool" rd + echo "chainload-flash: $board $media installed and verified" +} + +restore_media() { + local backup_dir=$1 confirm=$2 manifest + manifest="$backup_dir/backup.json" + case "$backup_dir" in /*|[A-Za-z]:/*) ;; *) die "BACKUP must be an absolute path" ;; esac + [[ ! -L "$backup_dir" ]] || die "BACKUP may not be a symbolic link" + [[ -f "$manifest" && ! -L "$manifest" ]] || die "backup.json is missing or unsafe" + need_tool python3 + need_tool sha256sum + local schema board media storage start installed_sectors backup_file backup_sectors expected_hash expected_capacity expected_manifest_hash ddr usbplug source bytes capacity info_file + schema=$(json "$manifest" schema) + [[ "$schema" == 2 ]] || die "unsupported backup metadata schema" + board=$(json "$manifest" board) + media=$(json "$manifest" media) + [[ "$board" =~ ^[a-z0-9][a-z0-9_-]*$ ]] || die "backup contains an invalid board identifier" + [[ "$media" == emmc || "$media" == spi-nor ]] || die "backup contains an invalid media identifier" + [[ "$confirm" == "restore:$board:$media" ]] || die "CONFIRM must equal 'restore:$board:$media'" + storage=$(json "$manifest" storage_id) + start=$(json "$manifest" write_lba) + installed_sectors=$(json "$manifest" installed_sectors) + backup_file=$(json "$manifest" backup_file) + backup_sectors=$(json "$manifest" backup_sectors) + expected_hash=$(json "$manifest" backup_sha256) + expected_capacity=$(json "$manifest" capacity_sectors) + expected_manifest_hash=$(json "$manifest" manifest_sha256) + [[ "$storage" =~ ^[0-9]+$ && "$start" =~ ^[0-9]+$ && + "$installed_sectors" =~ ^[0-9]+$ && + "$backup_sectors" =~ ^[0-9]+$ && "$expected_capacity" =~ ^[0-9]+$ ]] || + die "backup contains invalid numeric fields" + (( installed_sectors > 0 && backup_sectors > 0 && expected_capacity > 0 && + start + installed_sectors <= expected_capacity )) || + die "backup contains an invalid saved range" + [[ "$expected_hash" =~ ^[0-9a-f]{64}$ ]] || die "backup contains an invalid checksum" + [[ "$expected_manifest_hash" =~ ^[0-9a-f]{64}$ ]] || die "backup contains an invalid manifest checksum" + if [[ "$media" == emmc ]]; then + [[ "$backup_file" == previous-idblock-region.bin && + "$backup_sectors" == "$installed_sectors" ]] || + die "eMMC backup saved range differs from the selected target" + else + [[ "$backup_file" == complete-spi-nor.bin && "$start" == 0 && + "$backup_sectors" == "$expected_capacity" ]] || + die "SPI-NOR backup saved range differs from the selected target" + fi + source="$backup_dir/$backup_file" + [[ -f "$source" && ! -L "$source" ]] || die "backup payload is missing or unsafe" + bytes=$(wc -c < "$source" | tr -d '[:space:]') + (( bytes == backup_sectors * 512 )) || die "backup payload size does not match its manifest" + [[ $(sha256_of "$source") == "$expected_hash" ]] || die "backup payload checksum mismatch" + local board_manifest + board_manifest=$(find_manifest "$board") || die "board '$board' has no chainload manifest" + [[ $(sha256_of "$board_manifest") == "$expected_manifest_hash" ]] || + die "backup was created for a different board manifest" + [[ "$storage" == "$(json "$board_manifest" "boot_media.$media.storage_id")" ]] || + die "backup storage ID conflicts with the board manifest" + [[ "$start" == "$(json "$board_manifest" "boot_media.$media.write_lba")" ]] || + die "backup write offset conflicts with the board manifest" + ddr=$(resolve_input "$(json "$board_manifest" boot_media.ddr)") || die "DDR loader is missing" + usbplug=$(resolve_input "$(json "$board_manifest" boot_media.usbplug)") || die "USB-plug loader is missing" + need_tool "$xrock" + need_tool "$rkdeveloptool" + "$rkdeveloptool" -h 2>&1 | grep -q 'ChangeStorage' || die "rkdeveloptool lacks storage selection support" + enter_loader "$ddr" "$usbplug" + require_one_rk356x >/dev/null + select_storage "$storage" + info_file="$backup_dir/restore-flash-info.txt" + capacity=$(flash_info "$info_file") + [[ "$capacity" =~ ^[0-9]+$ && $capacity -gt 0 ]] || die "unable to determine selected-storage capacity" + [[ "$capacity" == "$expected_capacity" ]] || die "selected-storage capacity differs from the backup" + (( start >= 0 && start + backup_sectors <= capacity )) || + die "backup range exceeds selected-storage capacity" + if [[ "$media" == spi-nor ]]; then + record_jedec_id "$backup_dir/restore-flash-id.txt" + fi + trap 'echo "chainload-flash: restore failed; the device was not reset" >&2' ERR + "$rkdeveloptool" wl "$start" "$source" + "$rkdeveloptool" rl "$start" "$backup_sectors" "$backup_dir/restore-readback.bin" + require_sector_file "$backup_dir/restore-readback.bin" "$backup_sectors" "restore readback" + if [[ $(sha256_of "$source") != "$(sha256_of "$backup_dir/restore-readback.bin")" ]] || + ! cmp "$source" "$backup_dir/restore-readback.bin"; then + die "restore verification mismatch; the device remains in loader mode" + fi + trap - ERR + "$rkdeveloptool" rd + echo "chainload-flash: restored $board $media from $backup_file" +} + +case ${1:-} in +flash) + [[ $# -eq 5 ]] || die "usage: $0 flash BOARD MEDIA BACKUP_DIR CONFIRM" + flash_media "$2" "$3" "$4" "$5" + ;; +restore) + [[ $# -eq 3 ]] || die "usage: $0 restore BACKUP_DIR CONFIRM" + restore_media "$2" "$3" + ;; +*) + die "usage: $0 flash BOARD MEDIA BACKUP_DIR CONFIRM | restore BACKUP_DIR CONFIRM" + ;; +esac diff --git a/tools/main.h b/tools/main.h index 9313a91..734dd86 100644 --- a/tools/main.h +++ b/tools/main.h @@ -25,6 +25,17 @@ struct __attribute__((packed)) RkHeaderV1 { // (512 bytes) }; +struct __attribute__((packed)) RkImageEntryV2 { + uint16_t offset; + uint16_t size; + uint16_t address; + uint16_t reserved0; + uint32_t flag; + uint32_t counter; + uint8_t res[8]; + uint8_t hash[0x40]; +}; + struct __attribute__((packed)) RkHeaderV2 { uint32_t signature; uint32_t res1; @@ -32,17 +43,14 @@ struct __attribute__((packed)) RkHeaderV2 { uint16_t n_images; uint32_t boot_flag; uint8_t res2[0x68]; - struct ImageEntry { - uint16_t offset; - uint16_t size; - uint16_t address; - uint32_t flag; - uint32_t counter; - uint8_t res[8]; - uint8_t hash[0x40]; - }images[4]; + struct RkImageEntryV2 images[4]; }; -_Static_assert(__builtin_offsetof(struct RkHeaderV2, images) == 0x78, "offset check failed"); +_Static_assert(sizeof(struct RkImageEntryV2) == 88, "RKNS v2 entry size changed"); +_Static_assert(__builtin_offsetof(struct RkImageEntryV2, flag) == 8, + "RKNS v2 entry padding changed"); +_Static_assert(__builtin_offsetof(struct RkHeaderV2, images) == 0x78, + "RKNS v2 image table offset changed"); +_Static_assert(sizeof(struct RkHeaderV2) == 0x1d8, "RKNS v2 header size changed"); static unsigned int crc_sum_16(unsigned int crc, uint8_t *ptr, unsigned int len) { const uint16_t table_16[] = {0x0, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0x0a14a, 0x0b16b, 0x0c18c, 0x0d1ad, 0x0e1ce, 0x0f1ef, 0x1231, 0x210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0x0b37b, 0x0a35a, 0x0d3bd, 0x0c39c, 0x0f3ff, 0x0e3de, 0x2462, 0x3443, 0x420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0x0a56a, 0x0b54b, 0x8528, 0x9509, 0x0e5ee, 0x0f5cf, 0x0c5ac, 0x0d58d, 0x3653, 0x2672, 0x1611, 0x630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0x0b75b, 0x0a77a, 0x9719, 0x8738, 0x0f7df, 0x0e7fe, 0x0d79d, 0x0c7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x840, 0x1861, 0x2802, 0x3823, 0x0c9cc, 0x0d9ed, 0x0e98e, 0x0f9af, 0x8948, 0x9969, 0x0a90a, 0x0b92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0x0dbfd, 0x0cbdc, 0x0fbbf, 0x0eb9e, 0x9b79, 0x8b58, 0x0bb3b, 0x0ab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0x0edae, 0x0fd8f, 0x0cdec, 0x0ddcd, 0x0ad2a, 0x0bd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0x0ff9f, 0x0efbe, 0x0dfdd, 0x0cffc, 0x0bf1b, 0x0af3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0x0b1ca, 0x0a1eb, 0x0d10c, 0x0c12d, 0x0f14e, 0x0e16f, 0x1080, 0x0a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0x0a3fb, 0x0b3da, 0x0c33d, 0x0d31c, 0x0e37f, 0x0f35e, 0x2b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0x0b5ea, 0x0a5cb, 0x95a8, 0x8589, 0x0f56e, 0x0e54f, 0x0d52c, 0x0c50d, 0x34e2, 0x24c3, 0x14a0, 0x481, 0x7466, 0x6447, 0x5424, 0x4405, 0x0a7db, 0x0b7fa, 0x8799, 0x97b8, 0x0e75f, 0x0f77e, 0x0c71d, 0x0d73c, 0x26d3, 0x36f2, 0x691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0x0d94c, 0x0c96d, 0x0f90e, 0x0e92f, 0x99c8, 0x89e9, 0x0b98a, 0x0a9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x8e1, 0x3882, 0x28a3, 0x0cb7d, 0x0db5c, 0x0eb3f, 0x0fb1e, 0x8bf9, 0x9bd8, 0x0abbb, 0x0bb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0x0fd2e, 0x0ed0f, 0x0dd6c, 0x0cd4d, 0x0bdaa, 0x0ad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0x0ef1f, 0x0ff3e, 0x0cf5d, 0x0df7c, 0x0af9b, 0x0bfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0}; diff --git a/tools/release-dist.sh b/tools/release-dist.sh new file mode 100644 index 0000000..5ce56a0 --- /dev/null +++ b/tools/release-dist.sh @@ -0,0 +1,259 @@ +#!/usr/bin/env bash + +set -euo pipefail + +export LC_ALL=C +export TZ=UTC + +die() { + printf 'release-dist: %s\n' "$*" >&2 + exit 1 +} + +usage() { + printf 'usage: %s vMAJOR.MINOR.PATCH OUTPUT_DIR\n' "$0" >&2 + exit 2 +} + +[[ $# -eq 2 ]] || usage + +version=$1 +output_dir=$2 + +if [[ ! $version =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + die "version must be stable SemVer in the form vMAJOR.MINOR.PATCH" +fi + +[[ -n $output_dir ]] || die "output directory must not be empty" + +script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P) +source_dir=${RK_RELEASE_SOURCE_DIR:-$(cd -- "$script_dir/.." && pwd -P)} +chainload_release=${CHAINLOAD_RELEASE:-auto} +[[ $chainload_release == auto || $chainload_release == 0 || $chainload_release == 1 ]] || + die "CHAINLOAD_RELEASE must be auto, 0, or 1" +if [[ $chainload_release == auto ]]; then + chainload_release=0 + chainload_inputs=( + yy3568-u-boot.itb uboot_yy3568.bin uboot_yy3568.img + uboot_yy3568_idbloader.img uboot_yy3568_spi.img yy3568-u-boot-source.tar.xz + rock3a-u-boot.itb uboot_rock3a.bin uboot_rock3a.img + uboot_rock3a_idbloader.img uboot_rock3a_spi.img rock3a-u-boot-source.tar.xz + ) + present=0 + for input in "${chainload_inputs[@]}"; do + [[ -f $source_dir/$input && ! -L $source_dir/$input ]] && present=$((present + 1)) + done + if [[ $present -eq ${#chainload_inputs[@]} ]]; then + chainload_release=1 + elif [[ $present -ne 0 ]]; then + die "chainloader release inputs are incomplete" + fi +fi + +for command in git install find sort xargs sha256sum tar xz mv; do + command -v "$command" >/dev/null 2>&1 || die "required command is unavailable: $command" +done + +if [[ -e $output_dir && ! -d $output_dir ]]; then + die "output path exists and is not a directory: $output_dir" +fi +if [[ -d $output_dir ]] && [[ -n $(find "$output_dir" -mindepth 1 -maxdepth 1 -print -quit) ]]; then + die "output directory is not empty: $output_dir" +fi +mkdir -p -- "$output_dir" +output_dir=$(cd -- "$output_dir" && pwd -P) + +source_commit=${RK_RELEASE_COMMIT:-} +if [[ -z $source_commit ]]; then + source_commit=$(git -C "$source_dir" rev-parse --verify HEAD 2>/dev/null) || + die "cannot determine source commit" +fi +if [[ ! $source_commit =~ ^[0-9a-fA-F]{40}$ ]]; then + die "source commit must be a 40-character Git object ID" +fi + +source_date_epoch=${SOURCE_DATE_EPOCH:-} +if [[ -z $source_date_epoch ]]; then + source_date_epoch=$(git -C "$source_dir" show -s --format=%ct HEAD 2>/dev/null) || + die "cannot determine source timestamp" +fi +if [[ ! $source_date_epoch =~ ^[0-9]+$ ]]; then + die "SOURCE_DATE_EPOCH must be an integer" +fi + +stage_dir=$(mktemp -d "${TMPDIR:-/tmp}/rk-release.XXXXXXXX") +trap 'rm -rf -- "$stage_dir"' EXIT +archive_dir="$stage_dir/assets" +mkdir -p -- "$archive_dir" + +package_root= +build_info= + +copy_file() { + local source=$1 + local destination=$2 + local role=$3 + + [[ -f $source_dir/$source && ! -L $source_dir/$source ]] || + die "missing required regular file: $source" + install -D -m 0644 -- "$source_dir/$source" "$package_root/$destination" + printf '%-45s %s\n' "$destination" "$role" >> "$build_info" +} + +copy_executable() { + local source=$1 + local destination=$2 + local role=$3 + + [[ -f $source_dir/$source && ! -L $source_dir/$source ]] || + die "missing required regular file: $source" + install -D -m 0755 -- "$source_dir/$source" "$package_root/$destination" + printf '%-45s %s\n' "$destination" "$role" >> "$build_info" +} + +start_package() { + local slug=$1 + local board=$2 + + package_name="rk-${version}-${slug}" + package_root="$stage_dir/$package_name" + mkdir -p -- "$package_root" + build_info="$package_root/BUILD-INFO.txt" + { + printf 'version=%s\n' "$version" + printf 'commit=%s\n' "${source_commit,,}" + printf 'source_date_epoch=%s\n' "$source_date_epoch" + printf 'board=%s\n' "$board" + printf '\nFiles:\n' + } > "$build_info" + copy_file README.md README.md "project overview" + copy_file LICENSE LICENSE "project license" +} + +finish_package() { + local archive="$archive_dir/$package_name.tar.xz" + + ( + cd -- "$package_root" + find . -type f ! -name SHA256SUMS -print0 | + sort -z | + xargs -0 sha256sum > SHA256SUMS + ) + + tar \ + --sort=name \ + --format=gnu \ + --mtime="@$source_date_epoch" \ + --owner=0 \ + --group=0 \ + --numeric-owner \ + --mode='u+rwX,go+rX,go-w' \ + -C "$stage_dir" \ + -cf - "$package_name" | + xz --threads=1 --check=crc64 -9e > "$archive" +} + +add_chainloader() { + local board=$1 + local fit="${board}-u-boot.itb" + local binary="uboot_${board}.bin" + local image="uboot_${board}.img" + local idblock="uboot_${board}_idbloader.img" + local spi="uboot_${board}_spi.img" + local source="${board}-u-boot-source.tar.xz" + + copy_file "$fit" "chainload/$fit" "pinned BL31/U-Boot FIT" + copy_file "$binary" "chainload/$binary" "dedicated first stage plus U-Boot FIT" + copy_file "$image" "chainload/$image" "RKNS v2 chainloader SD image" + copy_file "$idblock" "chainload/$idblock" "raw RKNS v2 eMMC ID block for LBA 0x40" + copy_file "$spi" "chainload/$spi" "Rockchip first-2-KiB-of-4-KiB SPI-NOR image" + copy_file "config/chainload/${board}.json" chainload/MANIFEST.json "chainloader board manifest" + copy_file config/chainload/README.md chainload/README.md "chainloader porting policy" + copy_file docs/rk356x/chainloading.md chainloading.md "RK356x chainloading guide" + copy_executable tools/flash-chainload.sh install/flash-chainload.sh "guarded eMMC/SPI-NOR installer and restore utility" + copy_executable tools/check-partition-overlap.py install/check-partition-overlap.py "MBR/GPT overlap guard used by the installer" + copy_file img/rk3568_bl31_v1.46.elf loaders/rk3568_bl31_v1.46.elf "Rockchip BL31 v1.46" + copy_file "$source" "sources/$source" "patched corresponding U-Boot source" +} + +start_package pinebook-pro "Pine64 Pinebook Pro" +copy_file docs/devices/pinebook.md BOARD.md "board documentation" +copy_file pinebook.bin firmware/pinebook.bin "firmware" +copy_file demo_pinebook.bin firmware/demo_pinebook.bin "firmware with demo payload" +copy_file pinebook.img images/pinebook.img "RKNS SD image" +copy_file demo_pinebook.img images/demo_pinebook.img "RKNS SD image with demo payload" +copy_file pinebook-ddr.bin loaders/pinebook-ddr.bin "source-built DDR loader" +copy_file pinebook-poc-ddr.bin loaders/pinebook-poc-ddr.bin "source-built direct/SD DDR loader" +finish_package + +start_package genbook "Cool-Pi Genbook" +copy_file docs/devices/genbook.md BOARD.md "board documentation" +copy_file genbook.bin firmware/genbook.bin "firmware" +copy_file demo_genbook.bin firmware/demo_genbook.bin "firmware with demo payload" +copy_file genbook.img images/genbook.img "RKNS v2 SD image" +copy_file genbook_demo.img images/genbook_demo.img "RKNS v2 SD image with demo payload" +copy_file genbook-ddr.bin loaders/genbook-ddr.bin "source-built DDR loader" +finish_package + +start_package roc3566 "Firefly ROC-RK3566-PC" +copy_file docs/rk356x/boards/roc3566.md BOARD.md "ROC-RK3566-PC board documentation" +copy_file roc3566.bin firmware/roc3566.bin "firmware" +copy_file demo_roc3566.bin firmware/demo_roc3566.bin "firmware with demo payload" +copy_file roc3566.img images/roc3566.img "RKNS v2 SD image" +copy_file demo_roc3566.img images/demo_roc3566.img "RKNS v2 SD image with demo payload" +copy_file img/rk3566_ddr_1056MHz_v1.25.bin loaders/rk3566_ddr_1056MHz_v1.25.bin "Rockchip DDR loader" +copy_file img/rk356x_usbplug_v1.17.bin loaders/rk356x_usbplug_v1.17.bin "Rockchip xrock USB-plug loader" +copy_file img/LICENSE licenses/ROCKCHIP-BINARY-LICENSE "Rockchip binary license" +copy_file img/README.md provenance/rkbin-README.md "Rockchip binary provenance" +finish_package + +start_package yy3568 "Youyeetoo YY3568" +copy_file docs/rk356x/boards/yy3568.md BOARD.md "YY3568 board documentation" +copy_file yy3568.bin firmware/yy3568.bin "firmware" +copy_file demo_yy3568.bin firmware/demo_yy3568.bin "firmware with demo payload" +copy_file yy3568.img images/yy3568.img "RKNS v2 SD image" +copy_file demo_yy3568.img images/demo_yy3568.img "RKNS v2 SD image with demo payload" +copy_file img/rk3568_ddr_1560MHz_v1.25.bin loaders/rk3568_ddr_1560MHz_v1.25.bin "Rockchip DDR loader" +copy_file img/rk356x_usbplug_v1.17.bin loaders/rk356x_usbplug_v1.17.bin "Rockchip xrock USB-plug loader" +copy_file img/LICENSE licenses/ROCKCHIP-BINARY-LICENSE "Rockchip binary license" +copy_file img/README.md provenance/rkbin-README.md "Rockchip binary provenance" +if [[ $chainload_release == 1 ]]; then + add_chainloader yy3568 +fi +finish_package + +start_package rock3a "Radxa ROCK 3A" +copy_file docs/rk356x/boards/rock3a.md BOARD.md "ROCK 3A board documentation" +copy_file rock3a.bin firmware/rock3a.bin "firmware" +copy_file demo_rock3a.bin firmware/demo_rock3a.bin "firmware with demo payload" +copy_file rock3a.img images/rock3a.img "RKNS v2 SD image" +copy_file demo_rock3a.img images/demo_rock3a.img "RKNS v2 SD image with demo payload" +copy_file img/rk3568_ddr_1560MHz_v1.25.bin loaders/rk3568_ddr_1560MHz_v1.25.bin "Rockchip DDR loader" +copy_file img/rk356x_usbplug_v1.17.bin loaders/rk356x_usbplug_v1.17.bin "Rockchip xrock USB-plug loader" +copy_file img/LICENSE licenses/ROCKCHIP-BINARY-LICENSE "Rockchip binary license" +copy_file img/README.md provenance/rkbin-README.md "Rockchip binary provenance" +if [[ $chainload_release == 1 ]]; then + add_chainloader rock3a +fi +finish_package + +( + cd -- "$archive_dir" + sha256sum \ + "rk-${version}-genbook.tar.xz" \ + "rk-${version}-pinebook-pro.tar.xz" \ + "rk-${version}-roc3566.tar.xz" \ + "rk-${version}-rock3a.tar.xz" \ + "rk-${version}-yy3568.tar.xz" > SHA256SUMS +) + +mv -- \ + "$archive_dir/rk-${version}-genbook.tar.xz" \ + "$archive_dir/rk-${version}-pinebook-pro.tar.xz" \ + "$archive_dir/rk-${version}-roc3566.tar.xz" \ + "$archive_dir/rk-${version}-rock3a.tar.xz" \ + "$archive_dir/rk-${version}-yy3568.tar.xz" \ + "$archive_dir/SHA256SUMS" \ + "$output_dir/" + +printf 'release-dist: wrote %s\n' "$output_dir" diff --git a/tools/rock.c b/tools/rock.c index eb8e5c7..cefc494 100644 --- a/tools/rock.c +++ b/tools/rock.c @@ -1,4 +1,4 @@ -// Tool to boot rk3399 and rk3588 devices through maskrom (otg boot) mode +// Tool to boot Rockchip devices through maskrom (OTG boot) mode. #include #include #include @@ -10,7 +10,8 @@ #define RK_SEND_IMG 0x472 #define RK_MAX 0x1000 -int send_blob(libusb_device *dev, int cmd, const char *filename, int do_rc4) { +static int send_blob(libusb_device *dev, int cmd, const char *filename, + int do_rc4) { printf("Sending '%s'...\n", filename); unsigned int crc = 0xffff; FILE *f = fopen(filename, "rb"); @@ -18,50 +19,58 @@ int send_blob(libusb_device *dev, int cmd, const char *filename, int do_rc4) { printf("%s not found\n", filename); return -1; } - fseek(f, 0, SEEK_END); - long file_size = ftell(f); - fseek(f, 0, SEEK_SET); - - libusb_device_handle *handle; - if (libusb_open(dev, &handle)) { - printf("libusb_open\n"); - return -1; + libusb_device_handle *handle = NULL; + int result = -1; + int open_result = libusb_open(dev, &handle); + if (open_result) { + printf("libusb_open: '%s'\n", libusb_strerror(open_result)); + goto out_file; } struct Rc4Encoder r; setup_rc4_encoder(&r, rockchip_key); - unsigned int total_read = 0; while (1) { uint8_t chunk[0x1004]; - unsigned int max = 0x1000; - unsigned int read = fread(chunk, 1, 0x1000, f); - total_read += read; + size_t payload_length = fread(chunk, 1, RK_MAX, f); + int final_chunk = payload_length != RK_MAX; + if (ferror(f)) { + printf("Error reading '%s'.\n", filename); + goto out_handle; + } if (do_rc4) { - rc4_encode_chunk(&r, chunk, read); + rc4_encode_chunk(&r, chunk, payload_length); } - crc = crc_sum_16(crc, chunk, read); - if (read != 0x1000) { - chunk[read] = crc >> 8; - chunk[read + 1] = crc & 0xff; - read += 2; + crc = crc_sum_16(crc, chunk, payload_length); + size_t transfer_length = payload_length; + if (final_chunk) { + chunk[transfer_length++] = crc >> 8; + chunk[transfer_length++] = crc & 0xff; } - int rc = libusb_control_transfer(handle, 0x40, 0xc, 0x0, cmd, chunk, read, 0); + int rc = libusb_control_transfer(handle, 0x40, 0xc, 0x0, cmd, + chunk, (uint16_t)transfer_length, 0); if (rc < 0) { printf("libusb_control_transfer: '%s'\n", libusb_strerror(rc)); - return -1; + goto out_handle; + } + if ((size_t)rc != transfer_length) { + printf("Short USB transfer: sent %d of %zu bytes.\n", rc, + transfer_length); + goto out_handle; + } + if (final_chunk) { + result = 0; + break; } - if (read != 0x1000) break; } + out_handle: libusb_close(handle); + out_file: fclose(f); - return 0; + return result; } int main(int argc, char **argv) { - libusb_context *ctx; - libusb_init(&ctx); - int version = 1; const char *ddr_file = "ddr.bin"; const char *main_file = "os.bin"; @@ -72,44 +81,93 @@ int main(int argc, char **argv) { } else if (!strcmp(argv[i], "--v1")) { version = 1; } else if (!strcmp(argv[i], "--ddr")) { + if (i + 1 >= argc) { + printf("--ddr requires a file name.\n"); + return -1; + } ddr_file = argv[i + 1]; i++; } else if (!strcmp(argv[i], "--os")) { + if (i + 1 >= argc) { + printf("--os requires a file name.\n"); + return -1; + } main_file = argv[i + 1]; i++; } else { printf("Unknown arg '%s'\n", argv[i]); + return -1; } } - // discover devices + libusb_context *ctx = NULL; + int init_result = libusb_init(&ctx); + if (init_result < 0) { + printf("libusb_init: '%s'\n", libusb_strerror(init_result)); + return -1; + } + + // Discover first, then act. Sending to the first device is unsafe when + // two boards are in MaskROM at the same time. libusb_device **list; libusb_device *found = NULL; ssize_t cnt = libusb_get_device_list(ctx, &list); - ssize_t i = 0; - int err = 0; + uint16_t found_pid = 0; + int rockchip_count = 0; if (cnt < 0) { - printf("Error getting device list\n"); + printf("Error getting device list: '%s'\n", + libusb_strerror((int)cnt)); + libusb_exit(ctx); return -1; } - for (i = 0; i < cnt; i++) { + for (ssize_t i = 0; i < cnt; i++) { libusb_device *device = list[i]; struct libusb_device_descriptor desc; int rc = libusb_get_device_descriptor(device, &desc); - if (rc) return -1; + if (rc) { + libusb_free_device_list(list, 1); + libusb_exit(ctx); + return -1; + } if (desc.idVendor != 0x2207) continue; - int do_rc4 = 0; - if (desc.idProduct == 0x330c) do_rc4 = 1; // rk3399 - if (desc.idProduct == 0x350b) do_rc4 = 0; // rk3588 - rc = send_blob(device, RK_SEND_DDR, ddr_file, do_rc4); - if (rc) return rc; - usleep(10000); - send_blob(device, RK_SEND_IMG, main_file, do_rc4); - if (rc) return rc; - return 0; + rockchip_count++; + found = device; + found_pid = desc.idProduct; + } + if (rockchip_count > 1) { + printf("Multiple Rockchip MaskROM devices found; connect exactly one.\n"); + libusb_free_device_list(list, 1); + libusb_exit(ctx); + return -1; + } + if (!rockchip_count) { + printf("No Rockchip devices found.\n"); + libusb_free_device_list(list, 1); + libusb_exit(ctx); + return -1; } - printf("No rockchip devices found.\n"); - return -1; + int do_rc4; + const char *soc; + switch (found_pid) { + case 0x330c: do_rc4 = 1; soc = "RK3399"; break; + case 0x350a: do_rc4 = 0; soc = "RK356x"; break; + case 0x350b: do_rc4 = 0; soc = "RK3588"; break; + default: + printf("Unsupported Rockchip PID 0x%04x.\n", found_pid); + libusb_free_device_list(list, 1); + libusb_exit(ctx); + return -1; + } + printf("Found %s (PID 0x%04x, RC4 %s, transfer profile v%d).\n", + soc, found_pid, do_rc4 ? "on" : "off", version); + int rc = send_blob(found, RK_SEND_DDR, ddr_file, do_rc4); + if (!rc) { + usleep(10000); + rc = send_blob(found, RK_SEND_IMG, main_file, do_rc4); + } + libusb_free_device_list(list, 1); + libusb_exit(ctx); + return rc; }