From c7ee074fec1e43605cb391656d9e4cff6804f454 Mon Sep 17 00:00:00 2001 From: Hongquan Li Date: Mon, 17 Aug 2026 17:50:19 +0800 Subject: [PATCH 1/2] hw/arm: add UART3 to the RK3588 machine model The RK3588 exposes UART3 (dw-apb-uart, 16550-compatible) at 0xfeb60000 with GIC SPI 334, but the machine model only wired up UART2. Guests that need a second serial channel, such as Zephyr images using uart2 as the console and uart3 as a dedicated data link, had no device to attach. Factor the existing UART2 creation into a per-UART helper and instantiate UART3 with the same serial_mm + DesignWare vendor window layout. In zephyr-ram mode UART3 takes serial_hd(2), keeping the existing assignments (0 = DWC3 UDC CDC bridge, 1 = UART2 console); otherwise it takes serial_hd(1). Describe UART3 in the generated device tree as well (new /serial@feb60000 node plus a serial3 alias) so direct Linux boots without -dtb can discover it and bind a driver. Signed-off-by: Hongquan Li --- hw/arm/rk3588.c | 73 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/hw/arm/rk3588.c b/hw/arm/rk3588.c index 91a58162db095..ba6a9bb77cb7a 100644 --- a/hw/arm/rk3588.c +++ b/hw/arm/rk3588.c @@ -304,6 +304,7 @@ enum { RK3588_IRAM, RK3588_BROM, RK3588_UART2, + RK3588_UART3, }; static const MemMapEntry rk3588_memmap[] = { @@ -386,6 +387,7 @@ static const MemMapEntry rk3588_memmap[] = { [RK3588_IRAM] = { 0xff000000, RK3588_IRAM_SIZE }, [RK3588_BROM] = { RK3588_BROM_TRAMPOLINE, 0x00001000 }, [RK3588_UART2] = { 0xfeb50000, 0x00000100 }, + [RK3588_UART3] = { 0xfeb60000, 0x00000100 }, }; static hwaddr rk3588_ram_base(const RK3588MachineState *s) @@ -416,6 +418,7 @@ enum { RK3588_PCIE3X4_SYS_SPI = 263, RK3588_GPIO0_SPI = 277, RK3588_UART2_SPI = 333, + RK3588_UART3_SPI = 334, }; static const char *rk3588_cpu_type(unsigned int n) @@ -517,29 +520,23 @@ static void rk3588_fdt_add_timer_node(void *fdt) qemu_fdt_setprop(fdt, timer, "always-on", NULL, 0); } -static void rk3588_fdt_add_uart_node(void *fdt) +static void rk3588_fdt_add_one_uart_node(void *fdt, const char *uart, + int memmap_idx, uint32_t spi) { - const char *uart = "/serial@feb50000"; static const char * const compat[] = { "rockchip,rk3588-uart", "snps,dw-apb-uart", "ns16550a", }; - qemu_fdt_add_subnode(fdt, "/aliases"); - qemu_fdt_setprop_string(fdt, "/aliases", "serial2", uart); - - qemu_fdt_add_subnode(fdt, "/chosen"); - qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", "serial2:1500000n8"); - qemu_fdt_add_subnode(fdt, uart); qemu_fdt_setprop_string_array(fdt, uart, "compatible", (char **)&compat, ARRAY_SIZE(compat)); qemu_fdt_setprop_sized_cells(fdt, uart, "reg", - 2, rk3588_memmap[RK3588_UART2].base, - 2, rk3588_memmap[RK3588_UART2].size); + 2, rk3588_memmap[memmap_idx].base, + 2, rk3588_memmap[memmap_idx].size); qemu_fdt_setprop_cells(fdt, uart, "interrupts", - FDT_GIC_SPI, RK3588_UART2_SPI, + FDT_GIC_SPI, spi, FDT_IRQ_TYPE_LEVEL_HIGH, 0); qemu_fdt_setprop_cell(fdt, uart, "clock-frequency", RK3588_GTIMER_HZ); qemu_fdt_setprop_cell(fdt, uart, "current-speed", RK3588_UART_BAUDBASE); @@ -547,6 +544,22 @@ static void rk3588_fdt_add_uart_node(void *fdt) qemu_fdt_setprop_cell(fdt, uart, "reg-io-width", 4); qemu_fdt_setprop_string(fdt, uart, "status", "okay"); } + +static void rk3588_fdt_add_uart_node(void *fdt) +{ + const char *uart2 = "/serial@feb50000"; + const char *uart3 = "/serial@feb60000"; + + qemu_fdt_add_subnode(fdt, "/aliases"); + qemu_fdt_setprop_string(fdt, "/aliases", "serial2", uart2); + qemu_fdt_setprop_string(fdt, "/aliases", "serial3", uart3); + + qemu_fdt_add_subnode(fdt, "/chosen"); + qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", "serial2:1500000n8"); + + rk3588_fdt_add_one_uart_node(fdt, uart2, RK3588_UART2, RK3588_UART2_SPI); + rk3588_fdt_add_one_uart_node(fdt, uart3, RK3588_UART3, RK3588_UART3_SPI); +} static uint32_t rk3588_fdt_add_fixed_clock_node(void *fdt) { const char *clk = "/xin24m"; @@ -2494,31 +2507,45 @@ static void rk3588_create_its(RK3588MachineState *s) } } -static void rk3588_create_uart(RK3588MachineState *s) +static void rk3588_create_one_uart(RK3588MachineState *s, int memmap_idx, + int spi, int serial_idx, + const char *vendor_name) { DeviceState *vendor; SysBusDevice *vendor_sbd; - /* - * UART2 is a Synopsys dw-apb-uart (16550-compatible). serial_mm models the - * standard 16550 range (8 registers, regshift 2 -> a 0x20-byte window). The - * DesignWare extension registers (USR @0x7c, DMASA, CPR/UCV/CTR) sit - * above that window. Cover only that range so it does not overlap serial_mm. - */ - serial_mm_init(get_system_memory(), rk3588_memmap[RK3588_UART2].base, 2, - qdev_get_gpio_in(s->gic, RK3588_UART2_SPI), + serial_mm_init(get_system_memory(), rk3588_memmap[memmap_idx].base, 2, + qdev_get_gpio_in(s->gic, spi), RK3588_UART_BAUDBASE, - serial_hd(s->zephyr_ram ? 1 : 0), DEVICE_LITTLE_ENDIAN); + serial_hd(serial_idx), DEVICE_LITTLE_ENDIAN); vendor = qdev_new(TYPE_DW_APB_UART_VENDOR); vendor_sbd = SYS_BUS_DEVICE(vendor); - object_property_add_child(OBJECT(s), "uart2-vendor", OBJECT(vendor)); + object_property_add_child(OBJECT(s), vendor_name, OBJECT(vendor)); sysbus_realize(vendor_sbd, &error_fatal); sysbus_mmio_map(vendor_sbd, 0, - rk3588_memmap[RK3588_UART2].base + + rk3588_memmap[memmap_idx].base + DW_APB_UART_VENDOR_BASE); } +static void rk3588_create_uart(RK3588MachineState *s) +{ + /* + * UART2/UART3 are Synopsys dw-apb-uart (16550-compatible). serial_mm + * models the standard 16550 range (8 registers, regshift 2 -> a 0x20-byte + * window). The DesignWare extension registers (USR @0x7c, DMASA, + * CPR/UCV/CTR) sit above that window. Cover only that range so it does + * not overlap serial_mm. + * + * In zephyr-ram mode serial_hd(0) is the DWC3 UDC CDC bridge and + * serial_hd(1) is the UART2 console, so UART3 takes serial_hd(2). + */ + rk3588_create_one_uart(s, RK3588_UART2, RK3588_UART2_SPI, + s->zephyr_ram ? 1 : 0, "uart2-vendor"); + rk3588_create_one_uart(s, RK3588_UART3, RK3588_UART3_SPI, + s->zephyr_ram ? 2 : 1, "uart3-vendor"); +} + static void rk3588_attach_emmc_card(RK3588MachineState *s) { DriveInfo *di = drive_get(IF_SD, 0, 0); From 6c9e3157fbd4988c28bfbfc648431a5934f4a6d9 Mon Sep 17 00:00:00 2001 From: Hongquan Li Date: Mon, 17 Aug 2026 19:17:23 +0800 Subject: [PATCH 2/2] hw/arm: hand off GIC interrupts to NonSecure for Zephyr-ram boot Zephyr-ram direct kernel boot on the RK3588 machine starts the guest in NonSecure state with no secure firmware having run. arm/boot.c only invokes the ARMLinuxBootIf handoff (which marks a TrustZone-aware GIC's interrupts as NonSecure Group 1 at reset) for raw Linux images; ELF kernels such as Zephyr skip it. The GIC then resets with every interrupt in Group 0 and DS=0, so a NonSecure guest's writes to GICD_IGROUPR/ISENABLER are RAZ/WI and it can never enable any SPI. This was observed as UART RX interrupts (e.g. uart3, SPI 334) never being delivered to a Zephyr guest while polled TX worked fine. Invoke the same arm_linux_init(false) hook before arm_load_kernel(), but only in zephyr-ram mode, which is known to boot NonSecure. Other ELF payloads (e.g. U-Boot or BL31) boot at secure EL3 and may rely on or test the hardware Group 0 reset state, so leave the GIC untouched for them. Signed-off-by: Hongquan Li --- hw/arm/rk3588.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/hw/arm/rk3588.c b/hw/arm/rk3588.c index ba6a9bb77cb7a..3d0accc9e7c52 100644 --- a/hw/arm/rk3588.c +++ b/hw/arm/rk3588.c @@ -3210,6 +3210,27 @@ static void rk3588_init(MachineState *machine) } if (machine->kernel_filename) { + /* + * arm/boot.c only invokes the ARMLinuxBootIf handoff for raw + * Linux images; ELF kernels skip it, leaving a TZ-aware GIC with + * all interrupts in Group 0 that NS guests can neither reassign + * nor enable. Zephyr-ram direct boot is known to run NonSecure, + * so run the same handoff for it: the GIC resets with all + * interrupts in NonSecure Group 1, as secure firmware would have + * configured it on real hardware. + * + * Other ELF payloads (e.g. U-Boot or BL31) boot at secure EL3 + * and may rely on or test the hardware Group 0 reset state, so + * leave the GIC untouched for them. + */ + if (s->zephyr_ram) { + ARMLinuxBootIf *albif = ARM_LINUX_BOOT_IF(s->gic); + ARMLinuxBootIfClass *albifc = ARM_LINUX_BOOT_IF_GET_CLASS(albif); + + if (albifc->arm_linux_init) { + albifc->arm_linux_init(albif, false); + } + } arm_load_kernel(s->cpu[0], machine, &s->bootinfo); } else { Error *local_err = NULL;