đź“ť 12 Nov 2023
Last week we booted Linux on the Pine64 Ox64 64-bit RISC-V SBC (pic below), powered by Bouffalo Lab BL808 SoC…
And we wondered if a tiny 64-bit RTOS (Real-Time Operating System) like Apache NuttX RTOS might run more efficiently on Ox64.
(With only 64 MB of RAM)
Let’s make it happen! In this article we…
Begin with NuttX for Star64 JH7110 RISC-V SBC
Boot it unmodified (!) on our Ox64 BL808 RISC-V SBC
Add Debug Logs in RISC-V Assembly
Tweak the NuttX UART Driver to print on Ox64
Fix the Platform-Level Interrupt Controller
Track down why RISC-V Exceptions aren’t dumped correctly
And plan for the upcoming Initial RAM Disk
We’re booting Star64 NuttX on Ox64? Unmodified?!
Yeah it feels like we’re Shredding a Toaster inside a Blender (with plenty of Smoke and Noise)…
But we’re starting with NuttX for Star64 JH7110 anyway! That’s because we have a very strong hunch (or just plainly stubborn) that NuttX will boot well across RISC-V SoCs.
(We ported NuttX QEMU to Star64 in only a few weeks!)
But Star64 runs on SiFive Cores. Ox64 uses T-Head Cores!
If RISC-V ain’t RISC-V on SiFive vs T-Head: We’ll find out!
This is how we download and build NuttX for Star64 JH7110 RISC-V SBC…
## Download WIP NuttX Source Code
git clone \
--branch ox64 \
https://github.com/lupyuen2/wip-nuttx \
nuttx
git clone \
--branch ox64 \
https://github.com/lupyuen2/wip-nuttx-apps \
apps
## Build NuttX for Star64
cd nuttx
tools/configure.sh star64:nsh
make
## Dump the RISC-V Disassembly for NuttX Kernel
riscv64-unknown-elf-objdump \
-t -S --demangle --line-numbers --wide \
nuttx \
>nuttx.S \
2>&1
(Remember to install the Build Prerequisites and Toolchain)
(And enable Scheduler Info Output)
Next we prepare a Linux microSD for Ox64 as described in the previous article.
(Remember to flash OpenSBI and U-Boot Bootloader)
Then we do the Linux-To-NuttX Switcheroo: Overwrite the microSD Linux Image by the NuttX Kernel…
## Export the NuttX Kernel
## to `nuttx.bin`
riscv64-unknown-elf-objcopy \
-O binary \
nuttx \
nuttx.bin
## Overwrite the Linux Image
## on Ox64 microSD
cp nuttx.bin \
"/Volumes/NO NAME/Image"
diskutil unmountDisk /dev/disk2
Insert the microSD into Ox64 and power up Ox64.
Ox64 boots OpenSBI, which starts U-Boot Bootloader, which starts NuttX Kernel.
And we see… Absolutely Nothing!
Retrieving file: /extlinux/../Image
append: root=PARTLABEL=rootfs rootwait rw rootfstype=ext4 console=ttyS0,2000000 loglevel=8 earlycon=sbi
Retrieving file: /extlinux/../bl808-pine64-ox64.dtb
Flattened Device Tree blob at 51ff8000
Booting using the fdt blob at 0x51ff8000
Working FDT set to 51ff8000
Loading Device Tree to 0000000053f22000, end 0000000053f25fab ... OK
Working FDT set to 53f22000
Starting kernel...
Shouldn’t we see a Crash Dump?
Yeah we’re hoping that NuttX would crash and OpenSBI (Supervisor Binary Interface) could dump a meaningful Stack Trace. But nope!
We haven’t configured NuttX for Ox64 UART and…
NuttX is probably stuck in a loop waiting for Star64 UART
Is NuttX alive? We can check…
We have a strong hunch that NuttX is actually booting on Ox64… How to prove it?
We’ll print something in the NuttX Boot Code. Which is in RISC-V Assembly!
Ox64’s BL808 UART looks super familiar. When we compare these UARTs…
BL808 UART Controller
BL602 UART Controller
We discover that BL808 UART works the same way as BL602!
Thus we seek guidance from the NuttX Driver for BL602 UART.
Thanks! But how do we print to BL808 UART?
BL602 UART Driver prints to the Serial Console like so: bl602_serial.c
// Output FIFO Offset is 0x88
#define BL602_UART_FIFO_WDATA_OFFSET 0x000088
#define BL602_UART_FIFO_WDATA(n) (BL602_UART_BASE(n) + BL602_UART_FIFO_WDATA_OFFSET)
// Write a character to UART
void bl602_send(struct uart_dev_s *dev, int ch) {
...
// Wait for FIFO to be empty
while ((getreg32(BL602_UART_FIFO_CONFIG_1(uart_idx)) & \
UART_FIFO_CONFIG_1_TX_CNT_MASK) == 0);
// Write character to Output FIFO
putreg32(ch, BL602_UART_FIFO_WDATA(uart_idx));
}
For BL808: We do the same. We simply write the character to…
UART3 Base Address: 0x3000
2000
Output FIFO Offset: 0x88
Based on our Star64 Debug Code, we write this in RISC-V Assembly to print “123
”…
/* Load UART3 Base Address to Register t0 */
li t0, 0x30002000
/* Load `1` to Register t1 */
li t1, 0x31
/* Store byte from Register t1 to UART3 Base Address, Offset 0x88 */
sb t1, 0x88(t0)
/* Load `2` to Register t1 */
li t1, 0x32
/* Store byte from Register t1 to UART3 Base Address, Offset 0x88 */
sb t1, 0x88(t0)
/* Load `3` to Register t1 */
li t1, 0x33
/* Store byte from Register t1 to UART3 Base Address, Offset 0x88 */
sb t1, 0x88(t0)
(li
loads a Value into a Register)
(sb
stores a byte from a Register into an Address Offset)
We insert the code above into our NuttX Boot Code: jh7110_head.S
And we see (pic above)…
Starting kernel...
123
Our hunch is 100% correct, NuttX is ALIVE on Ox64 yay!
Anything else we changed in the NuttX Boot Code?
OpenSBI boots on Ox64 with Hart ID 0 (instead of 1). Which means we remove this adjustment for Hart ID: jh7110_head.S
/* We assume that OpenSBI has passed Hart ID (value 1) in Register a0.
* But NuttX expects Hart ID to start at 0, so we subtract 1.
* Previously: addi a0, a0, -1 */
Surely Ox64 boots at a different RAM Address from Star64?
Yep! Next we fix the NuttX Boot Address for Ox64.
From the U-Boot Bootloader we see that Ox64 boots Linux at this address…
$ printenv
kernel_addr_r=0x50200000
Based on the Boot Address, we define these Memory Regions for NuttX…
Memory Region | Start Address | Size |
---|---|---|
I/O Region | 0x0000 0000 | 0x5000 0000 |
Kernel Code | 0x5020 0000 | 2 MB |
Kernel Data | 0x5040 0000 | 2 MB |
Page Pool | 0x5060 0000 | 4 MB |
RAM Disk | 0x5060 0000 | 16 MB |
(Page Pool will be used by NuttX Apps)
(RAM Disk will contain the NuttX Shell and Apps)
We update the Memory Regions in the NuttX Linker Script: ld.script
MEMORY
{
kflash (rx) : ORIGIN = 0x50200000, LENGTH = 2048K /* w/ cache */
ksram (rwx) : ORIGIN = 0x50400000, LENGTH = 2048K /* w/ cache */
pgram (rwx) : ORIGIN = 0x50600000, LENGTH = 4096K /* w/ cache */
ramdisk (rwx) : ORIGIN = 0x50A00000, LENGTH = 16M /* w/ cache */
} /* TODO: Use up the entire 64 MB RAM */
We make the same changes to the NuttX Build Configuration: nsh/defconfig
CONFIG_RAM_START=0x50200000
CONFIG_RAM_SIZE=1048576
CONFIG_ARCH_PGPOOL_PBASE=0x50600000
CONFIG_ARCH_PGPOOL_VBASE=0x50600000
CONFIG_ARCH_PGPOOL_SIZE=4194304
And we update the NuttX Memory Map: jh7110_mm_init.c
// Map the whole I/O Memory
// with Virtual Address = Physical Address
// TODO: Interrupt Controller is missing!
#define MMU_IO_BASE (0x00000000ul)
#define MMU_IO_SIZE (0x50000000ul)
What’s this Memory Map?
Inside the BL808 SoC is the Sv39 Memory Management Unit (MMU). (Same for Star64 JH7110)
The MMU maps Virtual Memory Addresses to Physical Memory Addresses. And stops the NuttX Kernel from accessing Invalid Addresses.
At startup, NuttX configures the MMU with the Memory Map, the Range of Memory Addresses that the NuttX Kernel is allowed to access.
The code above says that NuttX is allowed to access any address from 0x0000
0000
to 0x5000
0000
. (Because of Memory-Mapped I/O)
Time to make NuttX talk…
NuttX on Ox64 has been awfully quiet…
How to fix the UART Driver so that NuttX can print things?
NuttX is still running the JH7110 UART Driver (16550).
To print to the Ox64 Serial Console, we make a quick patch to the NuttX UART Driver.
For now, we hardcode the UART3 Base Address (from above) and Output FIFO Offset: uart_16550.c
// Write one character to the UART
void u16550_putc(FAR struct u16550_s *priv, int ch) {
// Hardcode the UART3 Base Address and Output FIFO Offset
*(volatile uint8_t *) 0x30002088 = ch;
// Previously:
// while ((u16550_serialin(priv, UART_LSR_OFFSET) & UART_LSR_THRE) == 0);
// u16550_serialout(priv, UART_THR_OFFSET, (uart_datawidth_t)ch);
}
(Yeah the UART Buffer might overflow, we’ll fix later)
For Other UART Registers: We skip the reading and writing of the registers, because we’ll patch them later: uart_16550.c
// Read from UART Register
uart_datawidth_t u16550_serialin(FAR struct u16550_s *priv, int offset) {
return 0;
// Commented out the rest
}
// Write to UART Register
void u16550_serialout(FAR struct u16550_s *priv, int offset, uart_datawidth_t value) {
// Commented out the rest
}
And we won’t wait for UART Ready, since we don’t access the Line Control Register: uart_16550.c
// Wait until UART is not busy. This is needed before writing to Line Control Register.
// Otherwise we will get spurious interrupts on Synopsys DesignWare 8250.
int u16550_wait(FAR struct u16550_s *priv) {
// Nopez! No waiting for now
return OK;
}
After these fixes, NuttX prints our very first Crash Dump on Ox64 yay! (Pic above)
Starting kernel...
123ABC
riscv_exception:
EXCEPTION: Load access fault
MCAUSE: 5
EPC: 50208086
MTVAL: 0c002104
riscv_exception: PANIC!!! Exception = 0000000000000005
_assert: Current Version: NuttX 12.0.3 93a92a7-dirty Nov 5 2023 11:27:46 risc-v
_assert: Assertion failed panic: at file: common/riscv_exception.c:85 task: Idle_Task process: Kernel 0x50200e28
up_dump_register: EPC: 0000000050208086
up_dump_register: A0: 000000000c002104 A1: ffffffffffffffff A2: 0000000000000001 A3: 0000000000000003
MTVAL (Machine Trap Value) says that NuttX has crashed while reading the Invalid Data Address 0x0C00
2104
. (Hence the “Load Access Fault”)
Why is Data Address 0x0C00
2104
causing unhappiness? First we learn about RISC-V Interrupts…
Platform-Level Interrupt Controller for Star64 JH7110
What’s this Platform-Level Interrupt Controller?
Inside our BL808 SoC, the Platform-Level Interrupt Controller (PLIC) is the hardware that receives External Interrupts and forwards them to our RISC-V CPU.
(Like for UART Interrupts, pic above)
Earlier we saw NuttX crashing with this RISC-V Exception…
EXCEPTION: Load access fault
MCAUSE: 5
EPC: 50208086
MTVAL: 0c002104
This says that NuttX crashed when it tried to access Invalid Data Address 0x0C00
2104
from Code Address 0x5020
8086
.
We look up Code Address 0x5020
8086
in our RISC-V Disassembly for NuttX Kernel…
nuttx/arch/risc-v/src/common/riscv_modifyreg32.c:52
regval = getreg32(addr);
50208086: 2701 sext.w a4,a4
Which points to this: riscv_modifyreg32.c
// Atomically modify the specified bits
// in a Memory-Mapped Register
void modifyreg32(uintptr_t addr, uint32_t clearbits, uint32_t setbits) {
irqstate_t flags = spin_lock_irqsave(NULL);
// Crashes here because `addr` is invalid...
uint32_t regval = getreg32(addr);
regval &= ~clearbits;
regval |= setbits;
putreg32(regval, addr);
spin_unlock_irqrestore(NULL, flags);
}
Hence NuttX tried to modify a Memory-Mapped Register that doesn’t exist, and crashed.
But what Memory-Mapped Register?
The offending Data Address 0x0C00
2104
actually comes from the Star64 PLIC! (Platform-Level Interrupt Controller)
// Star64 PLIC Base Address
// From https://github.com/apache/nuttx/blob/master/arch/risc-v/src/jh7110/hardware/jh7110_memorymap.h#L30
#define JH7110_PLIC_BASE 0x0c000000
// Star64 S-Mode Interrupt Enable
// From https://github.com/apache/nuttx/blob/master/arch/risc-v/src/jh7110/hardware/jh7110_plic.h#L34-L49
#define JH7110_PLIC_ENABLE2 (JH7110_PLIC_BASE + 0x002104)
PLIC for Ox64 is in a different place, let’s change it.
What’s the PLIC Base Address for Ox64?
For Ox64, PLIC Base Address is 0xE000
0000
, according to the Linux Device Tree: bl808-pine64-ox64.dts
interrupt-controller@e0000000 {
compatible = "thead,c900-plic";
reg = <0xe0000000 0x4000000>;
interrupts-extended = <0x06 0xffffffff 0x06 0x09>;
interrupt-controller;
#address-cells = <0x00>;
#interrupt-cells = <0x02>;
riscv,ndev = <0x40>;
phandle = <0x01>;
};
Based on the above, we change the PLIC Base Address for Ox64: jh7110_memorymap.h
#define JH7110_PLIC_BASE 0xe0000000ul
(PLIC Offsets are in XuanTie OpenC906 User Manual, Page 77)
NuttX now crashes at a different place, with IRQ 15 (pic below)…
123ABC
nx_start: Entry
up_irqinitialize: a, b, c
riscv_dispatch_irq: irq=15
irq_unexpected_isr: ERROR irq: 15
_assert: Current Version: NuttX 12.0.3 910bfca-dirty Nov 6 2023 15:23:11 risc-v
_assert: Assertion failed panic: at file: irq/irq_unexpectedisr.c:54 task: Idle_Task process: Kernel 0x50200e50
But there’s something exceptional about IRQ 15…
What is IRQ 15? Who’s causing it? (Pic above)
From the XuanTie OpenC906 User Manual (Page 21)…
“Exception Vector ID 15: A Store / Atomic Instruction page error exception”
This says that NuttX tried to write to an Invalid Data Address.
And it failed due to an “Unexpected Interrupt”.
Something special about IRQ 15?
IRQ 15 is actually a RISC-V Exception!
Rightfully, NuttX should print a helpful RISC-V Exception Crash Dump with the offending Data Address. (Like this)
But NuttX wasn’t terribly helpful for this RISC-V Exception. Very odd!
Where did it crash?
Based on our Debug Log, NuttX crashes just before setting the PLIC: jh7110_irq.c
// Init the Interrupts
void up_irqinitialize(void) {
...
// Disable S-Mode Interrupts
_info("b\n");
up_irq_save();
// Disable all Global Interrupts
_info("c\n");
// Crashes here!
putreg32(0x0, JH7110_PLIC_ENABLE1);
putreg32(0x0, JH7110_PLIC_ENABLE2);
...
// Attach the RISC-V Exception Handlers
_info("f\n");
riscv_exception_attach();
Something doesn’t look right…
Yeah in the code above, we attach the RISC-V Exception Handlers (riscv_exception_attach)…
After the code has crashed! (putreg32)
Hence we attach the Exception Handlers earlier: jh7110_irq.c
// Init the Interrupts
void up_irqinitialize(void) {
...
// Disable S-Mode Interrupts
_info("b\n");
up_irq_save();
// Moved Here: Attach the RISC-V Exception Handlers
_info("f\n");
riscv_exception_attach();
// Disable all Global Interrupts
_info("c\n");
// Crashes here!
putreg32(0x0, JH7110_PLIC_ENABLE1);
putreg32(0x0, JH7110_PLIC_ENABLE2);
Then riscv_exception_attach will handle all RISC-V Exceptions correctly, including IRQ 15: riscv_exception.c
// Attach the RISC-V Exception Handlers
void riscv_exception_attach(void) {
...
// IRQ 15: Store / AMO Page Fault
irq_attach(RISCV_IRQ_STOREPF, riscv_exception, NULL);
Does it work?
Yep we see the Store / AMO Page Fault Exception! (Pic below)
up_irqinitialize: c
riscv_dispatch_irq: irq=15
riscv_exception:
EXCEPTION: Store/AMO page fault
MCAUSE: f
EPC: 50207e6a
MTVAL: e0002100
When we look up the NuttX Kernel Disassembly, the Exception Code Address 0x5020
7E6A
(EPC) comes from our PLIC Code…
nuttx/arch/risc-v/src/chip/jh7110_irq.c:62
putreg32(0x0, JH7110_PLIC_ENABLE1);
50207e64: 700017b7 lui a5,0x70001
50207e68: 0786 slli a5,a5,0x1
50207e6a: 1007a023 sw zero,256(a5) # 70001100 <__ramdisk_end+0x1e601100>
The offending Data Address (MTVAL) is 0xE000
2100
.
Which is our Ox64 PLIC! We scrutinise PLIC again…
But is 0xE000 2100 accessible?
Ah we forgot to add the Platform-Level Interrupt Controller (PLIC) to the Memory Map. This is how we fix it: jh7110_mm_init.c
// Map the whole I/O Memory
// with Virtual Address = Physical Address
// (Includes PLIC)
// TODO: This is mapping too much memory!
#define MMU_IO_BASE (0x00000000ul)
#define MMU_IO_SIZE (0xf0000000ul)
(Memory Map doesn’t look right)
NuttX boots even further. And tries to register IRQ 57 for the Star64 UART Interrupt…
up_irqinitialize: c, d, e, g
uart_register: Registering /dev/console
uart_register: Registering /dev/ttyS0
irq_attach: irq=57
up_enable_irq: irq=57
riscv_exception:
EXCEPTION: Load access fault
MCAUSE: 5
EPC: 50208342
MTVAL: e0002104
But it crashes while accessing the PLIC at another Invalid Data Address: 0xE000
2104
. (Sigh)
Ack! Enough with the PLIC already…
Yeah we’ll fix PLIC later. The entire UART Driver will be revamped anyway, including the UART Interrupt.
For now, we disable the UART Interrupt: uart_16550.c
// Attach the UART Interrupt for Star64
int u16550_attach(struct uart_dev_s *dev) {
// Don't attach the interrupt
// Previously:
// ret = irq_attach(priv->irq, u16550_interrupt, dev);
// Don't enable the interrupt
// Previously:
// up_enable_irq(priv->irq);
NuttX hits another roadblock…
Initial RAM Disk for Star64 JH7110
We disabled the UART Interrupts. What happens now?
NuttX boots much further, but crashes in the NuttX Bringup…
up_irqinitialize: c, d, e, g
uart_register: Registering /dev/console
uart_register: Registering /dev/ttyS0
work_start_lowpri: Starting low-priority kernel worker thread(s)
_assert: Current Version: NuttX 12.0.3 b244f85-dirty Nov 6 2023 17:35:34 risc-v
_assert: Assertion failed ret >= 0: at file: init/nx_bringup.c:283 task: AppBringUp process: Kernel 0x5020107e
That’s because NuttX couldn’t mount the Initial RAM Disk: nx_bringup.c
// Mount the File System containing
// the NuttX Shell (NSH)
ret = nx_mount(CONFIG_INIT_MOUNT_SOURCE, CONFIG_INIT_MOUNT_TARGET,
CONFIG_INIT_MOUNT_FSTYPE, CONFIG_INIT_MOUNT_FLAGS,
CONFIG_INIT_MOUNT_DATA);
// Fails here
DEBUGASSERT(ret >= 0);
That contains the Executable Binaries for NuttX Shell (NSH) and the NuttX Apps.
Why is the Initial RAM Disk missing?
That’s because we haven’t loaded the Initial RAM Disk into RAM!
We’ll modify the NuttX Kernel Image (or U-Boot Script) on the microSD Card, so that U-Boot Bootloader will load our Initial RAM Disk before starting NuttX…
Are we done yet?
That’s all for today! NuttX has booted so much code on Ox64. Here’s the flow of the NuttX Code that boots on Ox64 (pic below)…
Clickable Version of NuttX Boot Flow
This week we made plenty of progress starting Apache NuttX RTOS on the tiny Ox64 BL808 RISC-V SBC…
We took NuttX for Star64 JH7110 RISC-V SBC
And booted it (semi-successfully) on Ox64 BL808 RISC-V SBC
(Through sheer tenacity or desperation or lots of luck)
Thanks to the Debug Logs we added in RISC-V Assembly
And our modified NuttX UART Driver that prints on Ox64
We fixed the Platform-Level Interrupt Controller
Discovered why RISC-V Exceptions weren’t dumped correctly
As we planned for the upcoming Initial RAM Disk
We’ll do much more for NuttX on Ox64 BL808, stay tuned for updates!
Many Thanks to my GitHub Sponsors (and the awesome NuttX Community) for supporting my work! This article wouldn’t have been possible without your support.
Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…
My soldering of Ox64 BL808 looks horrigible… But it boots NuttX!
What happens exactly when NuttX boots on Ox64?
In this article, NuttX has booted plenty of code on Ox64. Here’s the flow of the NuttX Code that boots on Ox64…
Clickable Version of NuttX Boot Flow
NuttX Boot Code: bl808_head calls…
NuttX Start Code: bl808_start which calls…
Erase the BSS Memory: bl808_clear_bss (Global and Static Variables) and…
Copy the RAM Disk: bl808_copy_ramdisk (from NuttX Image to RAM Disk Memory Region) and…
Add the Hart: riscv_percpu_add_hart and…
Disable the MMU and…
Set the RISC-V Trap Vector to trap_vec and…
Start Supervisor Mode: bl808_start_s (see below)
Start Supervisor Mode: bl808_start_s prints “ABC” and calls…
Early Serial Init: riscv_earlyserialinit (see below) and…
Memory Mgmt Init: bl808_mm_init (to init the Memory Mgmt Unit) and…
Start NuttX: nx_start (see below)
Early Serial Init: riscv_earlyserialinit calls…
UART Early Init: bl808_earlyserialinit
(To setup the UART)
Memory Mgmt Init: bl808_mm_init inits the Memory Mgmt Unit by calling…
MMU Map Region: mmu_ln_map_region (to map a Memory Region) and…
MMU Set Entry: mmu_ln_setentry (to set a Page Table Entry)
Start NuttX: nx_start does many things and calls…
IRQ Init: up_irqinitialize (see below) and…
Bringup NuttX: nx_bringup (see below)
IRQ Init: up_irqinitialize calls…
Attach RISC-V Exceptions: riscv_exception_attach (to attach the RISC-V Exception Handlers) and…
Init NuttX: up_initialize (see below)
Init NuttX: up_initialize calls…
Serial Init: riscv_serialinit which calls…
(To register “/dev/console”)
Bringup NuttX: nx_bringup calls…
Create Init Thread: nx_create_initthread (to create “AppBringUp” thread) which calls…
Start NuttX Task: nx_start_task (to start NuttX Task) which calls…
Start Application: nx_start_application which calls…
Board Late Init: board_late_initialize (to mount Initial RAM Disk) and…
Mount RAM Disk: nx_mount (to mount ROM File System from Initial RAM Disk)
(Which fails because our Initial RAM Disk is missing)
(Which prevents NuttX Shell from starting)
Therefore we expect NuttX to boot completely on Ox64 when we’ve implemented…
Initial RAM Disk for Ox64
Memory Map might need fixing too
How did we figure out the NuttX Boot Flow?
It’s awfully tricky to follow the Boot Flow by reading the NuttX Source Code. (So many C Macros!)
Instead we searched for Function Names in the NuttX Disassembly. Which has the C Macros completely expanded for us.
Read the article…
What’s this Memory Map?
// Map the whole I/O Memory
// with Virtual Address = Physical Address
// TODO: Interrupt Controller is missing!
#define MMU_IO_BASE (0x00000000ul)
#define MMU_IO_SIZE (0x50000000ul)
Inside the BL808 SoC is the Sv39 Memory Management Unit (MMU) with 128 / 256 / 512 TLB table entries. (Same for Star64 JH7110)
The MMU maps Virtual Memory Addresses to Physical Memory Addresses. And stops the NuttX Kernel from accessing Invalid Addresses.
At startup, NuttX configures the MMU with the Memory Map, the Range of Memory Addresses that the NuttX Kernel is allowed to access.
The code above says that NuttX is allowed to access any address from 0x0000
0000
to 0x5000
0000
. (Because of Memory-Mapped I/O)
(MMU appears in OpenC906 User Manual, Page 50)
But we forgot to add the PLIC to the Memory Map!
The Platform-Level Interrupt Controller (PLIC) is at 0xE000
0000
.
Let’s add the PLIC to the Memory Map: jh7110_mm_init.c
// Map the whole I/O Memory
// with Virtual Address = Physical Address
// (Includes PLIC)
// TODO: This is mapping too much memory!
#define MMU_IO_BASE (0x00000000ul)
#define MMU_IO_SIZE (0xf0000000ul)
This doesn’t look right…
Yeah when we substitute the above MMU_IO_BASE and MMU_IO_SIZE into the Memory Map: jh7110_mm_init.c
// Set up the Kernel MMU Memory Map
void jh7110_kernel_mappings(void) {
...
// Map I/O Region, use enough large page tables for the I/O region
// MMU_IO_BASE is 0x00000000
// MMU_IO_SIZE is 0xf0000000
mmu_ln_map_region(1, PGT_L1_VBASE, MMU_IO_BASE, MMU_IO_BASE, MMU_IO_SIZE, MMU_IO_FLAGS);
// Map the Kernel Code for L2/L3
// From https://github.com/lupyuen2/wip-nuttx/blob/ox64/boards/risc-v/jh7110/star64/scripts/ld.script#L20-L27
// KFLASH_START is 0x50200000
// KFLASH_SIZE is 2 MB
map_region(KFLASH_START, KFLASH_START, KFLASH_SIZE, MMU_KTEXT_FLAGS);
// Map the Kernel Data for L2/L3
// From https://github.com/lupyuen2/wip-nuttx/blob/ox64/boards/risc-v/jh7110/star64/scripts/ld.script#L20-L27
// KSRAM_START is 0x50400000
// KSRAM_SIZE is 2 MB
map_region(KSRAM_START, KSRAM_START, KSRAM_SIZE, MMU_KDATA_FLAGS);
// Connect the L1 and L2 page tables for the kernel text and data
mmu_ln_setentry(1, PGT_L1_VBASE, PGT_L2_PBASE, KFLASH_START, PTE_G);
// Map the Page Pool for NuttX Apps
// From https://github.com/lupyuen2/wip-nuttx/blob/ox64/boards/risc-v/jh7110/star64/scripts/ld.script#L20-L27
// PGPOOL_START is 0x50600000
// PGPOOL_SIZE is 4 MB + 16 MB (including RAM Disk)
mmu_ln_map_region(2, PGT_L2_VBASE, PGPOOL_START, PGPOOL_START, PGPOOL_SIZE, MMU_KDATA_FLAGS);
}
We see a problem with the Memory Map…
Memory Region | Start Address | Size |
---|---|---|
I/O Region | 0x0000 0000 | 0xF000 0000 |
Kernel Code | 0x5020 0000 | 2 MB |
Kernel Data | 0x5040 0000 | 2 MB |
Page Pool | 0x5060 0000 | 20 MB |
(Page Pool includes RAM Disk)
The I/O Region overlaps with the Kernel Code, Data and Page Pool!
This happens because the PLIC is located at 0xE000
0000
. Which is AFTER the RAM Region…
Memory Region | Start Address | Size |
---|---|---|
I/O Region | 0x0000 0000 | 0x5000 0000 |
RAM | 0x5000 0000 | 64 MB |
Apps | 0xC000 0000 | (See below) |
PLIC | 0xE000 0000 | ??? |
Also NuttX Apps will fail because they run in the (Virtual) User Address Space at 0xC000
0000
: nsh/defconfig
CONFIG_ARCH_TEXT_VBASE=0xC0000000
CONFIG_ARCH_TEXT_NPAGES=128
CONFIG_ARCH_DATA_VBASE=0xC0100000
CONFIG_ARCH_DATA_NPAGES=128
CONFIG_ARCH_HEAP_VBASE=0xC0200000
CONFIG_ARCH_HEAP_NPAGES=128
But our Kernel Memory Space already extends to 0xF000
0000
!
Thus we might introduce another Memory Region, just to map the PLIC.
(Or should we move the User Address Space to 0xF000
0000
? Which gives us max 256 MB for NuttX Apps?)
The OpenSBI Log might offer some hints on the Memory Map…
Firmware Base : 0x3ef80000
Firmware Size : 200 KB
Domain0 Region00 : 0xe4008000-0xe400bfff (I)
Domain0 Region01 : 0xe4000000-0xe4007fff (I)
Domain0 Region02 : 0x3ef80000-0x3efbffff ()
Domain0 Region03 : 0x00000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address: 0x50000000
Domain0 Next Arg1 : 0x51ff8000
(0x3EF8
0000
is probably protected because it contains the OpenSBI Firmware. What is “(I)
”?)
Here’s how we fixed the Memory Map for Ox64 NuttX…
Read the article…
How will we create the NuttX UART Driver for Ox64 BL808?
Today NuttX supports the 32-bit predecessor of BL808: Bouffalo Lab BL602.
When we compare these UARTs…
BL808 UART Controller
BL602 UART Controller
We discover that BL808 UART works the same way as BL602!
Thus we’ll simply copy the NuttX Driver for BL602 UART to Ox64.
UART Interrupts are mandatory: If UART Interrupts aren’t implemented, NuttX Shell (NSH) and NuttX Apps won’t print anything.
BL602 UART Driver has just been ported to Ox64! (Minus the UART Interrupts) Check our progress here…
What about other drivers: BL808 vs BL602?
The controllers below look highly similar on BL808 vs BL602. Which means we have plenty of NuttX Drivers to copy from BL602 to BL808!
Controller | BL808 RM | BL602 RM |
---|---|---|
I2C | Page 430 | Page 142 |
SPI | Page 387 | Page 115 |
ADC | Page 169 | Page 45 |
DAC | Page 180 | Page 66 |
DMA | Page 187 | Page 70 |
Infrared | Page 372 | Page 100 |
PWM | Page 447 | Page 157 |
Timer | Page 474 | Page 174 |
Our earlier experiments with BL602 NuttX proved that the drivers above work well. So we’re all set for BL808!
(BL602 NuttX is tested on Real Hardware every day)
What about the drivers missing from BL602 NuttX?
We’ll port the missing BL808 Drivers from Bouffalo Lab’s BouffaloSDK to NuttX.
(BouffaloSDK is Apache 2.0 Licensed)
Initial RAM Disk for Star64 JH7110
Read the article…
What’s this Initial RAM Disk?
The Initial RAM Disk contains the Executable Binaries for NuttX Shell (NSH) and NuttX Apps.
At startup, NuttX loads the Initial RAM Disk into RAM and mounts the File System, so that the NuttX Shell (and NuttX Apps) can be started later.
Why is the Initial RAM Disk missing from Ox64?
That’s because we haven’t loaded the Initial RAM Disk into RAM!
Two ways we can load the Initial RAM Disk…
Load the Initial RAM Disk from a Separate File: initrd (similar to Star64)
This means we need to modify the U-Boot Script: boot-pine64.scr
And make it load the initrd file into RAM.
(Which is good for separating the NuttX Kernel and NuttX Apps)
OR…
Append the Initial RAM Disk to the NuttX Kernel Image
So the U-Boot Bootloader will load (one-shot into RAM) the NuttX Kernel + Initial RAM Disk.
And we reuse the existing U-Boot Config on the microSD Card: extlinux/extlinux.conf
(Which might be more efficient for our Limited RAM)
TODO: Can we mount the File System directly from the NuttX Kernel Image in RAM? Without copying to the RAM Disk Memory Region?
We’ll probably adopt the Second Method, since we are low on RAM. Like this…
## Export the NuttX Kernel to `nuttx.bin`
riscv64-unknown-elf-objcopy \
-O binary \
nuttx \
nuttx.bin
## Prepare a Padding with 64 KB of zeroes
head -c 65536 /dev/zero >/tmp/nuttx.pad
## Append Padding and Initial RAM Disk to NuttX Kernel
cat nuttx.bin /tmp/nuttx.pad initrd \
>Image
## Overwrite the Linux Image on Ox64 microSD
cp Image "/Volumes/NO NAME/"
## U-Boot Bootloader will load NuttX Kernel and
## Initial RAM Disk into RAM
Here’s how we implemented the Initial RAM Disk…