Address rpardini review round 2: major cleanup

- Use SERIALCON instead of systemd override for serial console
- Remove fdtfile from bootenv (use BOOT_FDT_FILE instead)
- Remove ATF_COMPILER (arm64 default)
- Inline OPTEE variables into fetch_sources_tools hook
- Remove empty family_tweaks() and family_tweaks_bsp()
- Use only debug mode for ATF build
- Build fiptool in atf_custom_postprocess
- Move header creation inline into uboot_custom_postprocess
- Use <<- heredoc for Python script
This commit is contained in:
TuAFBogey 2026-01-11 15:34:32 +03:00 committed by Igor
parent e5752ad9ee
commit 60650cf015
3 changed files with 117 additions and 341 deletions

View File

@ -11,20 +11,7 @@ BOOT_FDT_FILE="nuvoton/ma35d1-iot-512m.dtb"
BOOT_SCENARIO="blobless"
IMAGE_PARTITION_TABLE="msdos"
DEFAULT_CONSOLE="serial"
SERIALCON="ttyS0:115200"
# Hardware features
HAS_VIDEO_OUTPUT="yes"
function post_family_tweaks__numaker_iot_ma35d16f90() {
display_alert "$BOARD" "Applying NuMaker IoT MA35D16F90 tweaks" "info"
# Serial console 115200 baud
mkdir -p "$SDCARD/etc/systemd/system/serial-getty@ttyS0.service.d/"
cat <<- EOF > "$SDCARD/etc/systemd/system/serial-getty@ttyS0.service.d/override.conf"
[Service]
ExecStart=
ExecStart=-/sbin/agetty -o '-p -- \\u' 115200 ttyS0 linux
EOF
return 0
}

View File

@ -1,3 +1,2 @@
bootlogo=false
console=serial
fdtfile=nuvoton/ma35d1-iot-512m.dtb

View File

@ -11,10 +11,6 @@
# Boot flow: Mask ROM (IBR) -> BL2 (TF-A) -> BL31 -> BL32 (OP-TEE) -> BL33 (U-Boot) -> Linux
ARCH="arm64"
# Keep kernel version clean (5.10.140 instead of 5.10.140-vendor-nuvoton-ma35d1)
# Nuvoton expects exact version for compatibility
declare -g LOCALVERSION=""
# Disable BTF - 5.10.y kernel has issues with newer pahole/toolchain
declare -g KERNEL_BTF="no"
@ -28,45 +24,20 @@ BOOTPATCHDIR="u-boot-nuvoton-ma35d1"
BOOTSOURCE='https://github.com/OpenNuvoton/MA35D1_u-boot-v2020.07.git'
BOOTBRANCH='branch:master'
BOOTDIR='u-boot-nuvoton-ma35d1'
# MA35D1 uses TF-A (BL2/BL31) + OP-TEE, not U-Boot SPL
# All boot files: header.bin, bl2.bin, bl31.bin, bl2-ma35d1.dtb, tee-header_v2.bin, tee-pager_v2.bin, fip.bin, u-boot.bin
UBOOT_TARGET_MAP=";;u-boot.bin header.bin fip.bin bl2.bin bl31.bin bl2-ma35d1.dtb tee-header_v2.bin tee-pager_v2.bin"
# TF-A source from Nuvoton
declare -g ATF_USE_GCC="> 8.0"
declare -g ATF_COMPILER="aarch64-linux-gnu-"
declare -g ATFSOURCE="https://github.com/OpenNuvoton/MA35D1_arm-trusted-firmware-v2.3.git"
declare -g ATFDIR="arm-trusted-firmware-ma35d1"
declare -g ATFBRANCH="branch:master"
declare -g ATFPATCHDIR="atf-nuvoton-ma35d1"
# Fix RWX segment warning with binutils 2.39+ (skip -Wl, prefix for LD flags)
declare -g ATF_SKIP_LDFLAGS_WL="yes"
# OP-TEE source from Nuvoton
declare -g OPTEE_SOURCE="https://github.com/OpenNuvoton/MA35D1_optee_os-v3.9.0.git"
declare -g OPTEE_DIR="optee-os-ma35d1"
declare -g OPTEE_BRANCH="branch:master"
declare -g OPTEE_COMPILER="aarch64-linux-gnu-"
# Host dependencies for OP-TEE build (pycryptodome for signing, pyelftools for ELF parsing)
function add_host_dependencies__ma35d1_optee_deps() {
display_alert "Adding MA35D1 OP-TEE host dependencies" "python3-pycryptodome python3-pyelftools" "info"
declare -g EXTRA_BUILD_DEPS="${EXTRA_BUILD_DEPS} python3-pycryptodome python3-pyelftools"
}
# Fetch OP-TEE source using Armbian's fetch_from_repo mechanism
function fetch_sources_tools__ma35d1_fetch_optee() {
display_alert "Fetching OP-TEE source" "for MA35D1" "info"
fetch_from_repo "${OPTEE_SOURCE}" "${OPTEE_DIR}" "${OPTEE_BRANCH}"
}
# DDR configuration DTB - 512MB for IoT board
# Available options: ma35d1-cpu800-wb-512m, ma35d1-cpu1g-wb-512m, etc.
declare -g MA35D1_DDR_DTB="${MA35D1_DDR_DTB:-ma35d1-cpu800-wb-512m}"
MA35D1_DDR_DTB="${MA35D1_DDR_DTB:-ma35d1-cpu800-wb-512m}"
# TF-A build: BL2 + BL31 + DTB (FIP is created later in uboot_custom_postprocess)
# DEBUG=1 matches platform.mk default, output goes to build/ma35d1/debug/
# dtbs target is needed to build DDR configuration DTB
declare -g ATF_TARGET_MAP="PLAT=ma35d1 DEBUG=1 DTB_FILE_NAME=${MA35D1_DDR_DTB}.dtb bl2 bl31 dtbs;;build/ma35d1/debug/bl2.bin build/ma35d1/debug/bl31.bin build/ma35d1/debug/fdts/${MA35D1_DDR_DTB}.dtb:bl2-ma35d1.dtb"
LINUXFAMILY="nuvoton-ma35d1"
@ -76,13 +47,7 @@ IMAGE_PARTITION_TABLE="msdos"
BOOTSCRIPT="boot-nuvoton-ma35d1.cmd:boot.cmd"
BOOTENV_FILE="nuvoton-ma35d1.txt"
# Clean kernel version - disabled for now, causes packaging issues
# Clean kernel version - required for Nuvoton precompiled modules (dcultrafb.ko, ma35d1-vc8000.ko etc.)
# These modules have vermagic "5.10.140" and won't load with suffix
# enable_extension "nuvoton-clean-kernel-version"
case "${BRANCH}" in
vendor)
declare -g KERNEL_MAJOR_MINOR="5.10"
KERNELSOURCE='https://github.com/OpenNuvoton/MA35D1_linux-5.10.y.git'
@ -90,7 +55,6 @@ case "${BRANCH}" in
KERNELPATCHDIR="nuvoton-ma35d1-${BRANCH}"
LINUXCONFIG="linux-nuvoton-ma35d1-${BRANCH}"
;;
esac
KERNEL_TARGET="vendor"
@ -98,93 +62,59 @@ CPUMIN=180000
CPUMAX=800000
GOVERNOR="ondemand"
family_tweaks() {
:
# Host dependencies for OP-TEE build
function add_host_dependencies__ma35d1_optee_deps() {
display_alert "Adding MA35D1 OP-TEE host dependencies" "python3-pycryptodome python3-pyelftools" "info"
declare -g EXTRA_BUILD_DEPS="${EXTRA_BUILD_DEPS} python3-pycryptodome python3-pyelftools"
}
family_tweaks_bsp() {
:
# Fetch OP-TEE source using Armbian's fetch_from_repo mechanism
function fetch_sources_tools__ma35d1_fetch_optee() {
local optee_source="https://github.com/OpenNuvoton/MA35D1_optee_os-v3.9.0.git"
local optee_dir="optee-os-ma35d1"
local optee_branch="branch:master"
display_alert "Fetching OP-TEE source" "for MA35D1" "info"
fetch_from_repo "${optee_source}" "${optee_dir}" "${optee_branch}"
}
# Custom ATF build for MA35D1 - we need BL2 with DTB embedded
# NOTE: atf_custom_postprocess runs BEFORE atftempdir is created!
# We are in the ATF source directory at this point (pwd = $SRC/cache/sources/$ATFSOURCEDIR)
# ATF post-processing: verify build outputs and build fiptool
atf_custom_postprocess() {
display_alert "Post-processing ATF" "MA35D1" "info"
# We're in the ATF source directory, build output is in ./build/
local atf_src_dir="$(pwd)"
local atf_build_dir=""
local atf_build_dir="${atf_src_dir}/build/ma35d1/debug"
# Debug: show current directory
display_alert "ATF source dir" "${atf_src_dir}" "info"
# Try release directory first (DEBUG=0), then debug (DEBUG=1)
if [[ -d "${atf_src_dir}/build/ma35d1/release" ]]; then
atf_build_dir="${atf_src_dir}/build/ma35d1/release"
display_alert "Using release build" "${atf_build_dir}" "info"
elif [[ -d "${atf_src_dir}/build/ma35d1/debug" ]]; then
atf_build_dir="${atf_src_dir}/build/ma35d1/debug"
display_alert "Using debug build" "${atf_build_dir}" "info"
else
display_alert "Build directory not found, listing contents" "debug" "warn"
ls -la "${atf_src_dir}/build/" 2>/dev/null || echo "No build directory"
ls -la "${atf_src_dir}/build/ma35d1/" 2>/dev/null || echo "No ma35d1 directory"
exit_with_error "ATF build directory not found in ${atf_src_dir}/build/ma35d1/"
if [[ ! -d "${atf_build_dir}" ]]; then
exit_with_error "ATF build directory not found: ${atf_build_dir}"
fi
# List what was built
display_alert "ATF build contents" "$(ls ${atf_build_dir}/*.bin 2>/dev/null | xargs -n1 basename 2>/dev/null || echo 'none')" "info"
# Verify required files
[[ -f "${atf_build_dir}/bl2.bin" ]] || exit_with_error "bl2.bin not found in ${atf_build_dir}"
[[ -f "${atf_build_dir}/bl31.bin" ]] || exit_with_error "bl31.bin not found in ${atf_build_dir}"
[[ -f "${atf_build_dir}/fdts/${MA35D1_DDR_DTB}.dtb" ]] || exit_with_error "DTB ${MA35D1_DDR_DTB}.dtb not found"
# Verify bl2.bin and bl31.bin exist
if [[ ! -f "${atf_build_dir}/bl2.bin" ]]; then
exit_with_error "bl2.bin not found in ${atf_build_dir}"
fi
# Build fiptool for use in uboot_custom_postprocess
display_alert "Building fiptool" "MA35D1" "info"
run_host_command_logged make -C "${atf_src_dir}/tools/fiptool" HOSTCC=gcc
[[ -x "${atf_src_dir}/tools/fiptool/fiptool" ]] || exit_with_error "fiptool build failed"
if [[ ! -f "${atf_build_dir}/bl31.bin" ]]; then
exit_with_error "bl31.bin not found in ${atf_build_dir}"
fi
# Look for DTB
local dtb_file=""
if [[ -f "${atf_build_dir}/fdts/${MA35D1_DDR_DTB}.dtb" ]]; then
dtb_file="${atf_build_dir}/fdts/${MA35D1_DDR_DTB}.dtb"
display_alert "DTB found" "${dtb_file}" "info"
else
display_alert "DTB not found in fdts/" "listing available DTBs" "warn"
ls -la "${atf_build_dir}/fdts/"*.dtb 2>/dev/null || echo "No DTB files found"
fi
# Export the build directory path for use by the target_files copy
# The DTB will be copied via ATF_TARGET_MAP, we just verify it exists here
display_alert "ATF post-processing complete" "bl2.bin + bl31.bin ready" "info"
display_alert "ATF post-processing complete" "bl2.bin + bl31.bin + fiptool ready" "info"
}
# Compile OP-TEE for MA35D1
# Called from uboot_custom_postprocess to build OP-TEE before FIP creation
compile_optee_ma35d1() {
local optee_src_dir="${SRC}/cache/sources/${OPTEE_DIR}"
local optee_src_dir="${SRC}/cache/sources/optee-os-ma35d1"
local optee_out_dir="${optee_src_dir}/out/arm-plat-nuvoton/core"
display_alert "Compiling OP-TEE" "MA35D1" "info"
# OP-TEE source is fetched via fetch_sources_tools__ma35d1_fetch_optee()
# Build dependencies are declared via add_host_dependencies__ma35d1_optee_deps()
if [[ ! -d "${optee_src_dir}" ]]; then
exit_with_error "OP-TEE source not found at ${optee_src_dir} - fetch_sources_tools hook may have failed"
fi
[[ -d "${optee_src_dir}" ]] || exit_with_error "OP-TEE source not found at ${optee_src_dir}"
# Determine cross compiler - use system compiler directly
local cross_compile="aarch64-linux-gnu-"
# Clean previous build to avoid stale artifacts
run_host_command_logged make -C "${optee_src_dir}" clean PLATFORM=nuvoton-MA35D1 2>/dev/null || true
# Build OP-TEE
# Note: Using system cross-compiler
# LDFLAGS: --no-warn-rwx-segments suppresses RWX segment warning in binutils 2.39+
# NOWERROR=1: Treats warnings as warnings, not errors
# V=1: Verbose output to see actual linker command and errors
display_alert "Building OP-TEE" "PLATFORM=nuvoton-MA35D1" "info"
run_host_command_logged make -C "${optee_src_dir}" \
CROSS_COMPILE_core="${cross_compile}" \
@ -194,74 +124,44 @@ compile_optee_ma35d1() {
CFG_TEE_CORE_LOG_LEVEL=1 \
LDFLAGS="--no-warn-rwx-segments" \
NOWERROR=1 \
V=1 \
-j$(nproc)
# Verify output files
if [[ ! -f "${optee_out_dir}/tee-header_v2.bin" ]]; then
exit_with_error "OP-TEE build failed: tee-header_v2.bin not found"
fi
if [[ ! -f "${optee_out_dir}/tee-pager_v2.bin" ]]; then
exit_with_error "OP-TEE build failed: tee-pager_v2.bin not found"
fi
[[ -f "${optee_out_dir}/tee-header_v2.bin" ]] || exit_with_error "OP-TEE build failed: tee-header_v2.bin not found"
[[ -f "${optee_out_dir}/tee-pager_v2.bin" ]] || exit_with_error "OP-TEE build failed: tee-pager_v2.bin not found"
display_alert "OP-TEE built successfully" "$(ls -la ${optee_out_dir}/tee-*.bin 2>/dev/null | wc -l) files" "info"
display_alert "OP-TEE built successfully" "MA35D1" "info"
}
# Post-process U-Boot: create FIP and Nuvoton boot header
uboot_custom_postprocess() {
display_alert "Creating MA35D1 boot images" "BL2 + OP-TEE + FIP + Header" "info"
# Debug: list available files from ATF
display_alert "U-Boot build directory" "$(pwd)" "info"
display_alert "Available .bin files" "$(ls -la *.bin 2>/dev/null | head -5)" "debug"
# ATF source directory (for fiptool and DTB)
local atf_dir="${SRC}/cache/sources/${ATFDIR}/${ATFBRANCH##*:}"
local atf_build_dir="${atf_dir}/build/ma35d1/debug"
local fiptool="${atf_dir}/tools/fiptool/fiptool"
# Find ATF build directory (debug or release)
local atf_build_dir=""
if [[ -d "${atf_dir}/build/ma35d1/debug" ]]; then
atf_build_dir="${atf_dir}/build/ma35d1/debug"
elif [[ -d "${atf_dir}/build/ma35d1/release" ]]; then
atf_build_dir="${atf_dir}/build/ma35d1/release"
fi
# Verify fiptool exists (built in atf_custom_postprocess)
[[ -x "${fiptool}" ]] || exit_with_error "fiptool not found at ${fiptool}"
# Copy DTB from ATF build if not already present
if [[ -n "${atf_build_dir}" ]] && [[ -f "${atf_build_dir}/fdts/${MA35D1_DDR_DTB}.dtb" ]]; then
display_alert "Copying DTB from ATF" "${MA35D1_DDR_DTB}.dtb" "info"
if [[ ! -f "bl2-ma35d1.dtb" ]] && [[ -f "${atf_build_dir}/fdts/${MA35D1_DDR_DTB}.dtb" ]]; then
cp "${atf_build_dir}/fdts/${MA35D1_DDR_DTB}.dtb" "bl2-ma35d1.dtb"
else
display_alert "DTB not found in ATF build" "boot may fail without DDR config" "warn"
fi
# Build fiptool if not exists
if [[ ! -x "${fiptool}" ]]; then
display_alert "Building fiptool" "MA35D1" "info"
run_host_command_logged make -C "${atf_dir}/tools/fiptool" HOSTCC=gcc
fi
# Check required files - show what's available if not found
if [[ ! -f "bl2.bin" ]]; then
display_alert "Files in directory" "$(ls -la)" "warn"
exit_with_error "bl2.bin not found in u-boot build directory"
fi
if [[ ! -f "bl31.bin" ]]; then
exit_with_error "bl31.bin not found in u-boot build directory"
fi
# Verify required files
[[ -f "bl2.bin" ]] || exit_with_error "bl2.bin not found"
[[ -f "bl31.bin" ]] || exit_with_error "bl31.bin not found"
[[ -f "bl2-ma35d1.dtb" ]] || exit_with_error "bl2-ma35d1.dtb not found"
# Build OP-TEE
compile_optee_ma35d1
# Copy OP-TEE binaries
local optee_out_dir="${SRC}/cache/sources/${OPTEE_DIR}/out/arm-plat-nuvoton/core"
local optee_out_dir="${SRC}/cache/sources/optee-os-ma35d1/out/arm-plat-nuvoton/core"
cp "${optee_out_dir}/tee-header_v2.bin" .
cp "${optee_out_dir}/tee-pager_v2.bin" .
# Create fip.bin: bl31 + OP-TEE (tee-header + tee-pager) + u-boot
# Boot flow: BL31 -> BL32 (OP-TEE) -> BL33 (U-Boot)
# Create FIP
display_alert "Creating FIP" "bl31 + optee + u-boot" "info"
run_host_command_logged "${fiptool}" create \
--soc-fw bl31.bin \
@ -271,213 +171,103 @@ uboot_custom_postprocess() {
fip.bin
# Create Nuvoton boot header for SD card boot
# Header format: https://github.com/OpenNuvoton/MA35D1_NuWriter
create_ma35d1_header
display_alert "Creating Nuvoton boot header" "SD card" "info"
python3 <<- 'HEADER_SCRIPT'
import struct
import os
import binascii
HEADER_VERSION = 0x20210107
BL2_ENTRY = 0x28000000
BL2_DTB_LOAD = 0x28023000
BL2_LOAD = 0x28000000
IMG_TYPE_DATA = 3
IMG_TYPE_EXEC = 4
dtb_offset = 0x20000
bl2_offset = 0x30000
bl2_size = os.path.getsize('bl2.bin')
dtb_size = os.path.getsize('bl2-ma35d1.dtb') if os.path.exists('bl2-ma35d1.dtb') else 0
header = bytearray(512)
struct.pack_into('<I', header, 0, 0x4E565420)
struct.pack_into('<I', header, 8, 0xC0)
struct.pack_into('<I', header, 12, HEADER_VERSION)
struct.pack_into('<H', header, 16, 2048)
struct.pack_into('<H', header, 18, 64)
struct.pack_into('<H', header, 20, 64)
struct.pack_into('<B', header, 22, 0x6B)
struct.pack_into('<B', header, 23, 0x05)
struct.pack_into('<B', header, 24, 0x01)
struct.pack_into('<B', header, 25, 0x02)
struct.pack_into('<B', header, 26, 0)
struct.pack_into('<B', header, 27, 1)
struct.pack_into('<B', header, 28, 1)
for i in range(29, 32):
header[i] = 0xFF
struct.pack_into('<I', header, 32, BL2_ENTRY)
struct.pack_into('<I', header, 36, 2)
desc_offset = 40
DESC_SIZE = 80
struct.pack_into('<I', header, desc_offset + 0, dtb_offset)
struct.pack_into('<I', header, desc_offset + 4, BL2_DTB_LOAD)
struct.pack_into('<I', header, desc_offset + 8, dtb_size)
struct.pack_into('<I', header, desc_offset + 12, IMG_TYPE_DATA)
for i in range(desc_offset + 16, desc_offset + DESC_SIZE):
header[i] = 0xFF
desc_offset += DESC_SIZE
struct.pack_into('<I', header, desc_offset + 0, bl2_offset)
struct.pack_into('<I', header, desc_offset + 4, BL2_LOAD)
struct.pack_into('<I', header, desc_offset + 8, bl2_size)
struct.pack_into('<I', header, desc_offset + 12, IMG_TYPE_EXEC)
for i in range(desc_offset + 16, desc_offset + DESC_SIZE):
header[i] = 0xFF
total_header_size = 200
checksum = binascii.crc32(header[8:total_header_size]) & 0xFFFFFFFF
struct.pack_into('<I', header, 4, checksum)
with open('header.bin', 'wb') as f:
f.write(header[:total_header_size])
print(f"Header created: {total_header_size} bytes, CRC=0x{checksum:08x}")
HEADER_SCRIPT
[[ -f "header.bin" ]] || exit_with_error "Failed to create boot header"
display_alert "MA35D1 boot images created" "header.bin + bl2.bin + fip.bin" "info"
}
# Create Nuvoton boot header for SD card
# Based on AN0000 MA35D1 Secure Boot documentation and NuWriter source
create_ma35d1_header() {
display_alert "Creating Nuvoton boot header" "SD card" "info"
# Header layout (512 bytes) - from NuWriter:
# 0x00-0x03: Boot marker 0x4E565420 ("NVT ")
# 0x04-0x07: CRC32 checksum
# 0x08-0x0B: Header length
# 0x0C-0x0F: Version
# 0x10-0x23: SPI info (20 bytes) - zeros for SD boot
# 0x24-0x27: Entry point (BL2 entry = 0x28000000)
# 0x28-0x2B: Image count
# 0x2C+: Image descriptors (each 80 bytes)
# Image descriptor (80 bytes):
# 0x00-0x03: Flash offset
# 0x04-0x07: Load address
# 0x08-0x0B: Image size
# 0x0C-0x0F: Image type (3=data, 4=executable)
# 0x10-0x2F: R signature (32 bytes) - zeros for non-secure
# 0x30-0x4B: S signature (32 bytes) - zeros for non-secure
local header_file="header.bin"
local bl2_file="bl2.bin"
local bl2_dtb_file="bl2-ma35d1.dtb"
local fip_file="fip.bin"
# SD card layout (sector = 512 bytes):
# Sector 0: MBR (reserved)
# Sector 1: Reserved
# Sector 2: Header 0
# Sector 3: Header 1 (backup)
# Images follow at specified offsets
# Image offsets (in bytes from start of device)
# These must match write_uboot_platform offsets AND Yocto/NuWriter layout
# See: meta-ma35d1/recipes-devtools/python/files/pack-sdcard.json
local dtb_offset=0x20000 # 128KB (Yocto: 0x20000)
local bl2_offset=0x30000 # 192KB (Yocto: 0x30000)
local fip_offset=0xC0000 # 768KB (Yocto: 0xC0000)
# Create header using Python
python3 << 'HEADER_SCRIPT'
import struct
import os
import binascii
# Header constants - matching Yocto/NuWriter format
HEADER_VERSION = 0x20210107 # Match Yocto version
BL2_ENTRY = 0x28000000
BL2_DTB_LOAD = 0x28023000 # Yocto uses 0x28023000, not 0x28030000
BL2_LOAD = 0x28000000
# Image types
IMG_TYPE_DATA = 3
IMG_TYPE_EXEC = 4
# Offsets matching Yocto layout
dtb_offset = 0x20000 # 128KB
bl2_offset = 0x30000 # 192KB
# Note: FIP is NOT in header, BL2 knows where to find it
# Get file sizes
bl2_size = os.path.getsize('bl2.bin')
dtb_exists = os.path.exists('bl2-ma35d1.dtb')
dtb_size = os.path.getsize('bl2-ma35d1.dtb') if dtb_exists else 0
# Header structure:
# - Magic + CRC: 8 bytes (0x00-0x07)
# - Length + Version + SPI + Entry + Count: 32 bytes (0x08-0x27)
# - 2 descriptors: 160 bytes (0x28-0xC7)
# Total: 200 bytes (0xC8), padded to 512 for sector alignment
# Header length field (0xC0) = size excluding magic+CRC (192 bytes)
header = bytearray(512)
# === HEADER STRUCTURE (matching NuWriter/Yocto) ===
# Offset 0x00-0x03: Magic "NVT " (0x4E565420)
# Offset 0x04-0x07: CRC32 checksum
# Offset 0x08-0x0B: Header length (0xC0 = 192, excluding magic+CRC)
# Offset 0x0C-0x0F: Version
# Offset 0x10-0x1F: SPI info (16 bytes)
# Offset 0x20-0x23: Entry point
# Offset 0x24-0x27: Image count
# Offset 0x28+: Image descriptors (80 bytes each)
# Boot marker (offset 0) - little-endian integer like Yocto!
struct.pack_into('<I', header, 0, 0x4E565420)
# Checksum placeholder (offset 4) - calculated later
# Header length (offset 8)
struct.pack_into('<I', header, 8, 0xC0)
# Version (offset 12)
struct.pack_into('<I', header, 12, HEADER_VERSION)
# SPI info (offset 16-31, 16 bytes total)
struct.pack_into('<H', header, 16, 2048) # pagesize
struct.pack_into('<H', header, 18, 64) # sparearea
struct.pack_into('<H', header, 20, 64) # pageperblk
struct.pack_into('<B', header, 22, 0x6B) # quadread
struct.pack_into('<B', header, 23, 0x05) # readsts
struct.pack_into('<B', header, 24, 0x01) # writests
struct.pack_into('<B', header, 25, 0x02) # stsvalue
struct.pack_into('<B', header, 26, 0) # dummy1
struct.pack_into('<B', header, 27, 1) # dummy2
struct.pack_into('<B', header, 28, 1) # suspintvl
# Fill remaining SPI info bytes with 0xFF (offset 29-31)
for i in range(29, 32):
header[i] = 0xFF
# Entry point (offset 0x20 = 32)
struct.pack_into('<I', header, 32, BL2_ENTRY)
# Image count (offset 0x24 = 36)
img_count = 2
struct.pack_into('<I', header, 36, img_count)
# Image descriptors start at offset 0x28 = 40
desc_offset = 40
DESC_SIZE = 80 # Each descriptor: 16 bytes metadata + 64 bytes ECDSA signatures
# Image 0: BL2 DTB (data type)
struct.pack_into('<I', header, desc_offset + 0, dtb_offset) # Flash offset
struct.pack_into('<I', header, desc_offset + 4, BL2_DTB_LOAD) # Load address
struct.pack_into('<I', header, desc_offset + 8, dtb_size) # Size
struct.pack_into('<I', header, desc_offset + 12, IMG_TYPE_DATA) # Type
# Fill signature fields with 0xFF (offset 16-79 within descriptor)
for i in range(desc_offset + 16, desc_offset + DESC_SIZE):
header[i] = 0xFF
desc_offset += DESC_SIZE
# Image 1: BL2 (executable type)
struct.pack_into('<I', header, desc_offset + 0, bl2_offset) # Flash offset
struct.pack_into('<I', header, desc_offset + 4, BL2_LOAD) # Load address
struct.pack_into('<I', header, desc_offset + 8, bl2_size) # Size
struct.pack_into('<I', header, desc_offset + 12, IMG_TYPE_EXEC) # Type
# Fill signature fields with 0xFF
for i in range(desc_offset + 16, desc_offset + DESC_SIZE):
header[i] = 0xFF
# NO FIP in header - BL2 loads it from known location (0xC0000)
# Calculate actual header size: 40 (fixed) + 2*80 (descriptors) = 200 bytes
header_data_size = 0xC0 # 192 bytes (excluding magic+CRC)
total_header_size = 8 + header_data_size # 200 bytes = 0xC8
# CRC32 covers bytes from offset 8 to end of header (bytes 8-199)
checksum = binascii.crc32(header[8:total_header_size]) & 0xFFFFFFFF
struct.pack_into('<I', header, 4, checksum)
# Write header (only the actual header bytes, not padding)
with open('header.bin', 'wb') as f:
f.write(header[:total_header_size])
print(f"Header created: {total_header_size} bytes, {img_count} images, CRC=0x{checksum:08x}")
print(f" DTB: offset=0x{dtb_offset:x}, load=0x{BL2_DTB_LOAD:x}, size={dtb_size}")
print(f" BL2: offset=0x{bl2_offset:x}, load=0x{BL2_LOAD:x}, size={bl2_size}")
HEADER_SCRIPT
if [[ ! -f "header.bin" ]]; then
exit_with_error "Failed to create Nuvoton boot header"
fi
}
# Write bootloader to SD card/image
# Layout based on Nuvoton AN0000 documentation
# Note: This only works during image creation, not on target board
write_uboot_platform() {
local src_dir="$1"
local target_dev="$2"
display_alert "Writing MA35D1 bootloader" "${target_dev}" "info"
# Files to write
local header_bin="${src_dir}/header.bin"
local bl2_dtb_bin="${src_dir}/bl2-ma35d1.dtb"
local bl2_bin="${src_dir}/bl2.bin"
local fip_bin="${src_dir}/fip.bin"
# SD card layout (matching Yocto/NuWriter - pack-sdcard.json):
# Sector 2 (0x400): Header 0
# Sector 3 (0x600): Header 1 (backup)
# 0x20000 (128KB): BL2 DTB
# 0x30000 (192KB): BL2
# 0xC0000 (768KB): FIP
[[ -f "${header_bin}" ]] || { display_alert "Missing" "header.bin" "err"; return 1; }
[[ -f "${bl2_bin}" ]] || { display_alert "Missing" "bl2.bin" "err"; return 1; }
[[ -f "${fip_bin}" ]] || { display_alert "Missing" "fip.bin" "err"; return 1; }
# Write Header 0 at sector 2 (offset 1024 = 0x400)
dd if="${header_bin}" of="${target_dev}" bs=512 seek=2 conv=notrunc status=none
# Write Header 1 (backup) at sector 3 (offset 1536 = 0x600)
dd if="${header_bin}" of="${target_dev}" bs=512 seek=3 conv=notrunc status=none
# Write BL2 DTB at 128KB offset (0x20000)
if [[ -f "${bl2_dtb_bin}" ]]; then
dd if="${bl2_dtb_bin}" of="${target_dev}" bs=1024 seek=128 conv=notrunc status=none
fi
[[ -f "${bl2_dtb_bin}" ]] && dd if="${bl2_dtb_bin}" of="${target_dev}" bs=1024 seek=128 conv=notrunc status=none
# Write BL2 at 192KB offset (0x30000)
dd if="${bl2_bin}" of="${target_dev}" bs=1024 seek=192 conv=notrunc status=none
# Write FIP at 768KB offset (0xC0000)
dd if="${fip_bin}" of="${target_dev}" bs=1024 seek=768 conv=notrunc status=none
display_alert "Bootloader written" "header + bl2 + fip" "info"