wolfBoot/hal/rtl8735b.c

511 lines
20 KiB
C

/* rtl8735b.c
*
* HAL for the RealTek RTL8735B SoC (Cortex-M33), as used on the AmebaPro2 EVB
* and compatible boards.
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot 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.
*
* wolfBoot 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*
* Model A (non-TrustZone): the RealTek bootloader stages wolfBoot into SRAM via
* the RealTek RAM_FUNCTION_START_TABLE below. wolfBoot then reads the
* BOOT/UPDATE/SWAP partitions from external SPI NOR (ext_flash_*), verifies the
* application, copies it into DDR (src/update_ram.c RAMBOOT path) and jumps.
*
* Flash/UART/cache backend, selected at build time with HAL_BACKEND (arch.mk):
* HAL_BACKEND=sdk (default) - use the RealTek SDK drivers (flash_api /
* hal_uart / hal_pinmux / hal_cache). Compiled with the SDK
* include dirs via -iquote, a stub cmsis_os.h (so the chain
* does not pull FreeRTOS), the bootloader CONFIG_* defines,
* and -mcmse. See hal/rtl8735b/README.
* HAL_BACKEND=bare - a smaller backend with no SDK dependency (direct
* register / ROM access). Scaffold only: links standalone
* but the ext_flash_* entry points return -1. The flash
* path is the hard part -- the ROM exposes the flash leaf
* functions (hal_flash_stubs) and SPIC primitives
* (hal_spic_stubs) via romsym, but the bootloader hands off
* no SPIC adaptor (see hal_init), so a bare backend must
* reimplement spic_init() (controller training/calibration)
* on hal_spic_stubs before it can read flash. Future work.
*
* NOTE: wolfBoot's own "hal.h" is intentionally NOT included here. The RealTek
* SDK also ships a "hal.h" (pulled by its objects.h), and in the SDK backend the
* SDK include dirs are passed via -iquote so that header resolves to the SDK
* one. Including wolfBoot's hal.h in the same translation unit would clash, so
* this file does not include it. The HAL entry points it implements (hal_init,
* ext_flash_*, hal_prepare_boot, ...) are prototyped by wolfBoot's hal.h in the
* translation units that call them; here they are simply defined below.
*/
#include <stdint.h>
#include <string.h>
#include "target.h"
#include "wolfboot/wolfboot.h"
#include "printf.h" /* wolfBoot_printf, uart_init, uart_write */
#ifdef HAL_BACKEND_SDK
/* The SDK's basic_types.h defines likely()/unlikely() too; drop wolfBoot's
* so the SDK definitions apply without a redefinition warning. */
#undef likely
#undef unlikely
/* RealTek SDK drivers: SPI NOR (flash_api.h), I/D cache maintenance
* (hal_cache.h), and the UART/pinmux register-level leaf functions used to
* bring up the DEBUG_UART console (hal_uart.h / hal_pinmux.h). */
#include "flash_api.h"
#include "hal_cache.h"
#include "hal_uart.h"
#include "hal_pinmux.h"
/* SDK flash driver's global adaptor pointer, bound by flash_init() below.
* (The bootloader does not hand off a usable adaptor -- see hal_init.) */
extern hal_spic_adaptor_t *pglob_spic_adaptor;
/* flash_init() is an SDK symbol not declared in flash_api.h. */
extern void flash_init(flash_t *obj);
static flash_t hal_flash_obj;
#endif /* HAL_BACKEND_SDK */
/* ===========================================================================
* RealTek RAM start-table: launched by the RealTek bootloader (backend common).
*
* Field order/types mirror the SDK rtl8735b_ramstart.h so the bootloader reads
* each field at the right offset. RamStartFun points at the trampoline below,
* which sets MSP and branches to wolfBoot's isr_reset. Embedding the table here
* makes wolfboot.elf directly launchable (no separate shim).
* ===========================================================================
*/
extern void wolfboot_ram_entry(void);
/* Scratch SRAM the bootloader writes through the RAM start-table pointers
* (pbl_peri_buf, phal_spic_adaptor, ...). It lives in .ram.noinit, which the
* linker keeps OUTSIDE the _start_bss.._end_bss range that isr_reset
* (src/boot_arm.c) zeroes -- otherwise anything the bootloader stored here
* before jumping to wolfBoot would be wiped before hal_init() runs. This is
* latent today (hal_init does a fresh flash_init and ignores the passed
* adaptor) but is required once the SPIC adaptor reuse is enabled. */
static uint8_t hal_bl_scratch[2048]
__attribute__((section(".ram.noinit"), aligned(32)));
/* Exactly the 10 bytes "AmebaPro2\xff" (the trailing 0xff matters; a NUL pad
* fails as "Invalid FW Image Signature"). */
const unsigned char hal_ram_img_sig[10]
__attribute__((section(".ram.img.signature"), aligned(4))) = {
'A', 'm', 'e', 'b', 'a', 'P', 'r', 'o', '2', 0xff
};
typedef struct {
void *Signature;
void (*RamStartFun)(void);
void (*RamWakeupFun)(void);
void (*RamPatchFun0)(void);
void (*RamPatchFun1)(void);
void *sys_cp_fw_info;
void *pbl_peri_buf;
void *pxip_sce_restore;
uint32_t entry_start;
uint32_t entry_end;
uint8_t *hash_data;
uint32_t ddr_hash_start1;
uint32_t ddr_hash_end1;
uint32_t ddr_hash_start2;
uint32_t ddr_hash_end2;
uint8_t *ddr_hash_data;
uint32_t boot_cfg_w;
uint32_t msp_start;
uint32_t msp_limit;
uint32_t start_tbl_size;
void *phal_spic_adaptor;
uint32_t flash_user_data_offset;
uint32_t flash_user_data_len;
void *pbl_shared_buf;
uint32_t init_flags;
uint32_t boot_status;
uint8_t reserved1;
uint8_t sys_tmr_id;
uint16_t pad;
void *pfw_image_info;
void *pbl_ld_voe_info;
void *pSnand_layout_info;
uint32_t reserved2[2];
} ram_start_table_t;
const ram_start_table_t ram_start_table
__attribute__((section(".ram.func.table"), used)) = {
.Signature = (void *)hal_ram_img_sig,
.RamStartFun = wolfboot_ram_entry,
.msp_start = 0x20120000u, /* valid early SRAM stack */
.msp_limit = 0x2011f000u,
.start_tbl_size = sizeof(ram_start_table_t),
.pbl_peri_buf = &hal_bl_scratch[0],
.pbl_shared_buf = &hal_bl_scratch[256],
.pfw_image_info = &hal_bl_scratch[512],
.pbl_ld_voe_info = &hal_bl_scratch[768],
.pSnand_layout_info = &hal_bl_scratch[1024],
.sys_cp_fw_info = &hal_bl_scratch[1280],
.phal_spic_adaptor = &hal_bl_scratch[1536],
};
/* Trampoline the bootloader jumps to: set MSP to the top of wolfBoot's SRAM
* region and branch into wolfBoot's normal reset path (src/boot_arm.c). This
* removes any dependency on the loader honoring the ARM vector table's word0. */
__attribute__((naked, used, section(".ram.code_text")))
void wolfboot_ram_entry(void)
{
__asm__ volatile(
"ldr r0, =END_STACK\n\t"
"msr msp, r0\n\t"
"ldr r0, =isr_reset\n\t"
"bx r0\n\t"
);
}
/* ===========================================================================
* Debug UART (wolfBoot banner / logs).
* ===========================================================================
*/
#ifdef DEBUG_UART
/* Bare-metal console on UART1 (0x40040400), which the RealTek Zephyr port uses
* as its "loguart" (DTS serial@40040400, pins PORT_F 4=TX/3=RX = 0xA4/0xA3,
* function PID_UART1). It reaches the EVB FT232 / ttyUSB5. Registers:
* TFLVR @ 0x54 (tx_fifo_lv bits[4:0]), THR @ 0x24, TX FIFO depth 16.
*
* The RealTek ROM printf is NOT used (its ROM stdio_port putc is unregistered
* from wolfBoot -> INVSTATE fault) and the SDK hal_uart_init() hangs (it pulls
* IRQ/GDMA/OS primitives). uart_init() instead brings UART1 up self-containedly:
* init the pinmux manager, route PF4/PF3 to UART1, enable the UART1 clock, then
* program baud + 8N1 via the register-level leaf functions with a hand-populated
* adapter (the leaf set_baudrate only needs the baud tables + sclk). Verified on
* hardware: the wolfBoot banner prints cleanly on ttyUSB5. */
#ifndef RTL8735B_LOGUART_BASE
#define RTL8735B_LOGUART_BASE 0x40040400UL /* UART1 (Zephyr loguart serial@40040400) */
#endif
#define RTL8735B_UART_TFLVR (*(volatile uint32_t *)(RTL8735B_LOGUART_BASE + 0x54))
#define RTL8735B_UART_THR (*(volatile uint32_t *)(RTL8735B_LOGUART_BASE + 0x24))
#define RTL8735B_UART_TX_FIFO 16u
/* Console pins (per the RealTek Zephyr board pinctrl): PORT_F pin4 = TXD,
* pin3 = RXD, function PID_UART1. Pin name = (port<<5)|pin; UART1 func id =
* (FUNC_UART<<28)|1 = 0x60000001. The RealTek bootloader muxes these pins to
* its own console peripheral, so wolfBoot must (re)route them to UART1
* (0x40040400) before its writes reach the FT232/ttyUSB5 console. */
#define RTL8735B_PF4_TXD 0xA4u
#define RTL8735B_PF3_RXD 0xA3u
#define RTL8735B_PID_UART1 0x60000001u
#ifdef HAL_BACKEND_SDK
/* UART baud tables for hal_uart_set_baudrate (which only needs the adapter's
* table pointers + sclk populated; it then writes DLL/DLM + OVSR). These are
* the SDK fwlib def_*_40m_patch[] values, EXCEPT index 13 (115200) which is
* retuned for this board's measured UART1 clock (~50 MHz, not the SDK comment's
* 40 MHz PXP value): OVSR 16 * DIV 27 = 432, 50e6/432 ~= 115740 (within 0.5%).
* wolfBoot only ever requests 115200, so the other (40 MHz) entries are
* reference-only. */
static const uint32_t rtl8735b_baud_tbl[] = {
110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600,
76800, 115200, 128000, 153600, 230400, 380400, 460800, 500000, 921600,
1000000, 1500000, 1536000, 2000000, 2500000, 3000000, 3500000, 4000000,
6000000, 8000000, 10000000, 12000000, 16000000, 20000000
};
static const uint8_t rtl8735b_ovsr_tbl[] = {
20, 20, 20, 20, 20, 17, 17, 15, 10, 11, 10, 11, 10, 16, 12, 10, 10, 15, 17,
20, 14, 20, 14, 9, 13, 7, 20, 19, 7, 13, 6, 5, 5, 10, 6
};
static const uint16_t rtl8735b_div_tbl[] = {
18173, 6664, 3332, 1666, 833, 490, 245, 185, 208, 126, 104, 63, 52, 27, 26,
26, 17, 7, 5, 4, 3, 2, 2, 3, 2, 3, 1, 1, 2, 1, 2, 2, 2, 1, 1
};
static const uint8_t rtl8735b_adj10_tbl[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 4, 0, 5, 0, 5, 2, 3,
2, 0, 0, 2, 3, 1, 4, 3, 0, 7
};
static const uint8_t rtl8735b_adj9_tbl[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 3, 0, 4, 0, 4, 2, 3,
2, 0, 0, 2, 3, 1, 4, 3, 0, 6
};
static const uint8_t rtl8735b_adj8_tbl[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 3, 0, 4, 0, 4, 2, 3,
2, 0, 0, 2, 3, 1, 3, 3, 0, 5
};
static hal_uart_adapter_t rtl8735b_uart1;
static hal_pin_mux_mang_t rtl8735b_pinmux_mgr;
#endif /* HAL_BACKEND_SDK */
void uart_init(void)
{
#ifdef HAL_BACKEND_SDK
/* Initialize the pinmux manager (the bootloader's state does not carry into
* wolfBoot), then route the console pins to UART1 (the bootloader leaves
* them on its own console peripheral). */
hal_pinmux_manager_init(&rtl8735b_pinmux_mgr);
(void)hal_pinmux_register(RTL8735B_PF4_TXD, RTL8735B_PID_UART1);
(void)hal_pinmux_register(RTL8735B_PF3_RXD, RTL8735B_PID_UART1);
/* Bring up UART1 without the hanging hal_uart_init: hand-populate the
* adapter (the SDK 40 MHz baud tables + the UartSCLK_40M enum), enable the
* clock, then use the register-level leaf functions for 8N1 format and baud.
* The board's real UART1 clock is ~50 MHz (see the baud-table note above):
* the sclk enum stays 40M, but the 115200 table entry is retuned (OVSR/DIV)
* so the programmed divisor matches the actual clock. */
memset(&rtl8735b_uart1, 0, sizeof(rtl8735b_uart1));
rtl8735b_uart1.base_addr = (UART_Type *)RTL8735B_LOGUART_BASE;
rtl8735b_uart1.uart_idx = (uint8_t)Uart1;
rtl8735b_uart1.uart_sclk = (uint8_t)UartSCLK_40M;
rtl8735b_uart1.pdef_baudrate_tbl = rtl8735b_baud_tbl;
rtl8735b_uart1.pdef_ovsr_tbl = rtl8735b_ovsr_tbl;
rtl8735b_uart1.pdef_div_tbl = rtl8735b_div_tbl;
rtl8735b_uart1.pdef_ovsradjbit_tbl10 = rtl8735b_adj10_tbl;
rtl8735b_uart1.pdef_ovsradjbit_tbl9 = rtl8735b_adj9_tbl;
rtl8735b_uart1.pdef_ovsradjbit_tbl8 = rtl8735b_adj8_tbl;
hal_uart_en_ctrl((uint8_t)Uart1, ON);
(void)hal_uart_set_format(&rtl8735b_uart1, 8, 0, 1);
(void)hal_uart_set_baudrate(&rtl8735b_uart1, 115200);
#endif
}
static void rtl8735b_uart_putc(char c)
{
uint32_t timeout = 0;
/* Wait while the TX FIFO is full (level >= depth); bounded so a
* misconfigured base cannot hang. */
while ((RTL8735B_UART_TFLVR & 0x1Fu) >= RTL8735B_UART_TX_FIFO) {
if (++timeout > 1000000u) {
break;
}
}
RTL8735B_UART_THR = (uint32_t)(unsigned char)c;
}
void uart_write(const char *buf, unsigned int sz)
{
unsigned int i;
for (i = 0; i < sz; i++) {
if (buf[i] == '\n') {
rtl8735b_uart_putc('\r');
}
rtl8735b_uart_putc(buf[i]);
}
}
#endif /* DEBUG_UART */
/* ===========================================================================
* HAL init / boot handoff.
* ===========================================================================
*/
void hal_init(void)
{
#ifdef DEBUG_UART
/* AON boot-reason register (AON_BASE 0x40009000 + 0x104): bit 0 = Vendor
* watchdog, bit 1 = AON watchdog, bit 4 = brown-out. */
volatile uint32_t *aon_boot_reason = (volatile uint32_t *)0x40009104u;
uint32_t rr;
#endif
/* Enable the FPU (CP10/CP11 in CPACR) before any other code runs. arch.mk
* builds every backend with -mfpu=fpv5-sp-d16, so the compiler may emit VFP
* instructions (and the RealTek SDK drivers certainly do); they fault if the
* FPU is left disabled after reset. This is backend-independent, so it is
* not gated on HAL_BACKEND_SDK. */
*((volatile uint32_t *)0xE000ED88) |= (0xFu << 20);
__asm__ volatile("dsb\n\tisb\n\t");
#ifdef DEBUG_UART
uart_init();
#endif
wolfBoot_printf("wolfBoot HAL: RTL8735B (AmebaPro2) init\n");
#ifdef DEBUG_UART
/* Report why the SoC last reset, then W1C the latched status bits so each
* boot shows its own cause (helps spot e.g. an unfed watchdog reset). */
rr = *aon_boot_reason;
wolfBoot_printf("Reset reason: 0x%x%s%s%s\n", rr,
(rr & 0x1u) ? " VNDR-WDT" : "",
(rr & 0x2u) ? " AON-WDT" : "",
(rr & 0x10u) ? " BOD" : "");
*aon_boot_reason = rr & 0x33u;
#endif
#ifdef HAL_BACKEND_SDK
/* Initialize the external SPI NOR with a fresh spic_init() (flash_init()
* binds a new adaptor). The RealTek bootloader does NOT hand off its own
* initialized SPIC adaptor to wolfBoot -- verified on hardware: the RAM
* start-table phal_spic_adaptor field is left at our placeholder buffer, and
* that buffer holds no valid adaptor (a real adaptor has spic_dev pointing at
* the SPIC base 0x40006000; the handoff buffer is unrelated data). So a fresh
* bind is required, not merely preferred -- there is no adaptor to reuse. */
memset(&hal_flash_obj, 0, sizeof(hal_flash_obj));
flash_init(&hal_flash_obj);
wolfBoot_printf("wolfBoot HAL: flash init done\n");
#endif
}
void hal_prepare_boot(void)
{
/* The application was written into DDR via ext_flash_read()/memcpy. The
* RTL8735B has its own I/D cache (not Arm SCB): clean+invalidate D-cache so
* the writes hit DDR, then invalidate I-cache so stale lines do not shadow
* the new app, before do_boot() jumps. */
#ifdef HAL_BACKEND_SDK
dcache_clean_invalidate();
icache_invalidate();
#endif
__asm__ volatile("dsb\n\tisb\n\t");
}
/* ===========================================================================
* Internal flash: physically unused in this design. wolfBoot is SRAM-resident
* and ALL partitions (BOOT/UPDATE/SWAP) live in external SPI NOR (PART_*_EXT),
* so libwolfboot never routes a write/erase through these entry points -- they
* are intentional no-ops, not unimplemented stubs. (A future config that placed
* a partition in internal flash would need a real implementation here.)
*
* These return 0 (success) rather than an error. That is the wolfBoot
* convention for a target that does not self-write internal flash: hal/skeleton.c
* ships the same no-op "return 0" success, as do the zynq, raspi3, sama5d3,
* x86 and library ports. Returning -1 here was considered (fail loud on a
* misconfiguration) but rejected: the internal-flash paths in src/libwolfboot.c
* that DO check the return value (the NVM trailer cache, partition_magic_write,
* the encrypted-key sector, the NSC update writes) test ret != 0 / ret < 0 and
* would abort inconsistently, while copy_flash/self-update ignore the return
* entirely -- so an error return would neither cleanly disable those paths nor
* be uniformly detected. A real driver, not an error stub, is the correct fix
* if a partition is ever placed in internal flash.
* ===========================================================================
*/
void hal_flash_unlock(void)
{
}
void hal_flash_lock(void)
{
}
int hal_flash_write(uint32_t address, const uint8_t *data, int len)
{
(void)address;
(void)data;
(void)len;
return 0;
}
int hal_flash_erase(uint32_t address, int len)
{
(void)address;
(void)len;
return 0;
}
/* ===========================================================================
* External SPI NOR (raw byte offsets).
* ===========================================================================
*/
void ext_flash_unlock(void)
{
#ifdef HAL_BACKEND_SDK
flash_global_unlock();
#endif
}
void ext_flash_lock(void)
{
#ifdef HAL_BACKEND_SDK
flash_global_lock();
#endif
}
int ext_flash_read(uintptr_t address, uint8_t *data, int len)
{
if (len < 0) {
return -1;
}
if (len == 0) {
return 0; /* zero-length read is a no-op; callers check == len */
}
#ifdef HAL_BACKEND_SDK
/* flash_stream_read returns 1 on success; propagate a read failure rather
* than handing potentially-stale data to the verifier. */
if (flash_stream_read(&hal_flash_obj, (uint32_t)address, (uint32_t)len,
data) != 1) {
return -1;
}
return len;
#else
/* TODO(bare): implement via direct SPIC / ROM hal_flash_stream_read. */
(void)address;
(void)data;
return -1;
#endif
}
int ext_flash_write(uintptr_t address, const uint8_t *data, int len)
{
if (len < 0) {
return -1;
}
if (len == 0) {
return 0; /* zero-length write is a no-op success */
}
#ifdef HAL_BACKEND_SDK
/* flash_stream_write returns 1 on success; propagate a write failure. */
if (flash_stream_write(&hal_flash_obj, (uint32_t)address, (uint32_t)len,
(uint8_t *)data) != 1) {
return -1;
}
return 0;
#else
/* TODO(bare): implement via direct SPIC / ROM hal_flash_burst_write. */
(void)address;
(void)data;
return -1;
#endif
}
int ext_flash_erase(uintptr_t address, int len)
{
uint32_t sector_addr;
uint32_t end_addr;
if (len < 0 || (uint32_t)len > UINT32_MAX - (uint32_t)address) {
return -1;
}
if (len == 0) {
return 0; /* nothing to erase */
}
/* Erase every WOLFBOOT_SECTOR_SIZE sector spanning [address, address+len).
* Unlike flash_stream_read/write, the SDK flash_erase_sector returns void
* (no status to propagate), so there is no per-sector failure to check. */
sector_addr = (uint32_t)address & ~((uint32_t)WOLFBOOT_SECTOR_SIZE - 1);
end_addr = (uint32_t)address + (uint32_t)len;
while (sector_addr < end_addr) {
#ifdef HAL_BACKEND_SDK
flash_erase_sector(&hal_flash_obj, sector_addr);
#else
/* TODO(bare): implement via direct SPIC / ROM hal_flash_sector_erase. */
return -1;
#endif
sector_addr += WOLFBOOT_SECTOR_SIZE;
}
return 0;
}