Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/lupyuen/nuttx-ox64

Apache NuttX RTOS for Pine64 Ox64 64-bit RISC-V SBC (BouffaloLab BL808)
https://github.com/lupyuen/nuttx-ox64

bl808 nuttx ox64 riscv riscv64

Last synced: 5 days ago
JSON representation

Apache NuttX RTOS for Pine64 Ox64 64-bit RISC-V SBC (BouffaloLab BL808)

Awesome Lists containing this project

README

        

![Pine64 Ox64 64-bit RISC-V SBC (Bouffalo Lab BL808)](https://lupyuen.github.io/images/ox64-sbc.jpg)

# Apache NuttX RTOS for Pine64 Ox64 64-bit RISC-V SBC (Bouffalo Lab BL808)

[![Daily Build of NuttX for Ox64](https://github.com/lupyuen/nuttx-ox64/actions/workflows/ox64.yml/badge.svg)](https://github.com/lupyuen/nuttx-ox64/actions/workflows/ox64.yml)
[![Daily Test of NuttX for Ox64](https://github.com/lupyuen/nuttx-ox64/actions/workflows/ox64-test.yml/badge.svg)](https://github.com/lupyuen/nuttx-ox64/actions/workflows/ox64-test.yml)

Read the articles...

- ["Rust Apps on Ox64 BL808 RISC-V SBC and Apache NuttX RTOS"](https://lupyuen.github.io/articles/rust5)

- ["Too many Embedded Logs? PureScript might help (Ox64 BL808 SBC / Apache NuttX RTOS)"](https://lupyuen.github.io/articles/purescript)

- ["(Homage to MakeCode) Coding Ox64 BL808 SBC the Drag-n-Drop Way"](https://lupyuen.github.io/articles/quickjs2)

- ["QuickJS JavaScript Engine on a Real-Time Operating System (Apache NuttX RTOS)"](https://lupyuen.github.io/articles/quickjs)

- ["Zig runs ROM FS Filesystem in the Web Browser (thanks to Apache NuttX RTOS)"](https://lupyuen.github.io/articles/romfs)

- ["TCC RISC-V Compiler runs in the Web Browser (thanks to Zig Compiler)"](https://lupyuen.github.io/articles/tcc)

- ["Automated Testing with Ox64 BL808 Emulator (Apache NuttX RTOS)"](https://lupyuen.github.io/articles/tinyemu3)

- ["Emulate Ox64 BL808 in the Web Browser: Experiments with TinyEMU RISC-V Emulator and Apache NuttX RTOS"](https://lupyuen.github.io/articles/tinyemu2)

- ["Apache NuttX RTOS in a Web Browser? Adventures with TinyEMU and VirtIO"](https://lupyuen.github.io/articles/tinyemu)

- ["Nim on a Real-Time Operating System: Apache NuttX RTOS + Ox64 BL808 SBC"](https://lupyuen.github.io/articles/nim)

- ["$8 RISC-V SBC on a Real-Time Operating System: Ox64 + NuttX"](https://www.hackster.io/lupyuen/8-risc-v-sbc-on-a-real-time-operating-system-ox64-nuttx-474358)

- ["Fixed the UART Interrupt and Platform-Level Interrupt Controller (Ox64 BL808)"](https://lupyuen.github.io/articles/plic3)

- ["RISC-V Ox64 BL808 SBC: UART Interrupt and Platform-Level Interrupt Controller (PLIC)"](https://lupyuen.github.io/articles/plic2)

- ["RISC-V Ox64 BL808 SBC: NuttX Apps and Initial RAM Disk"](https://lupyuen.github.io/articles/app)

- ["RISC-V Ox64 BL808 SBC: Sv39 Memory Management Unit"](https://lupyuen.github.io/articles/mmu)

- ["RISC-V Ox64 BL808 SBC: Starting Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox2)

- ["RISC-V Ox64 BL808 SBC: Booting Linux and (maybe) Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox64)

_What's this BL808?_ [(Datasheet)](https://github.com/bouffalolab/bl_docs/blob/main/BL808_DS/en/BL808_DS_1.2_en.pdf) [(Reference Manual)](https://github.com/bouffalolab/bl_docs/blob/main/BL808_RM/en/BL808_RM_en_1.3.pdf)

BL808 is a complex creature with 3 (Asymmetric) RISC-V Cores (linked via IPC)...

1. D0 Core: [T-Head C906 64-bit RV64IMAFCV](https://www.t-head.cn/product/c906?lang=en) (480 MHz)

(Multimedia Core with MIPI CSI / DSI, Neural Proc Unit)

(Memory Mgmt Unit is Sv39, 128/256/512 TLB table entry. Same as Star64)

1. M0 Core: [T-Head E907 32-bit RV32IMAFCP](https://www.t-head.cn/product/e907?lang=en) (320 MHz)

(Wireless + Peripherals Core with WiFi, BLE, BT, Zigbee, Audio)

1. LP Core: [T-Head E902 32-bit RV32E[M]C](https://www.t-head.cn/product/e902?lang=en) (150 MHz)

(Low Power Core)

[(Upcoming BL606 looks similar, minus the Low Power Core)](https://en.bouffalolab.com/product/?type=detail&id=16)

![Bouffalo Lab BL808 is a complex creature with 3 (Asymmetric) RISC-V Cores](https://lupyuen.github.io/images/ox64-cores.jpg)

[Pine64 Ox64](https://wiki.pine64.org/wiki/Ox64) is the dev board for BL808C.

(BL808C supports MIPI CSI Cameras but not MIPI DSI Displays. Maybe someday we'll see BL808D for MIPI DSI Displays)

_Is Ox64 BL808 an SBC? Or an MCU Board?_

Technically Ox64 BL808 boots 64-bit RISC-V Linux (via MicroSD), so it feels like an SBC...

- ["Booting Linux on the Pine64 Ox64 SBC"](https://adventurist.me/posts/00317)

- [OpenBouffalo Wiki](https://openbouffalo.org/index.php/Main_Page)

- [Linux Image + OpenSBI + U-Boot Bootloader for BL808](https://github.com/openbouffalo/buildroot_bouffalo)

[(Newer version?)](https://github.com/bouffalolab/buildroot_bouffalo)

[(OpenSBI is the BIOS for RISC-V SBCs)](https://lupyuen.github.io/articles/sbi)

- USB-C Port for Camera Module (Dual-Lane MIPI CSI)

(USB-C is not for Flashing!)

- USB 2.0 support for USB OTG

(On-The-Go = USB Host + USB Device)

But Ox64 BL808 also feels like an MCU Board...

- Form Factor is similar to MCU Board

- Limited Memory: 64 MB of RAM, [128 Megabits](https://pine64.com/product/128mb-ox64-sbc-available-on-december-2-2022/) (16 MB) of Flash Memory

- M0 Wireless Core is 32-bit RISC-V MCU

- UART Pins need a USB Serial Adapter for Flashing and Console I/O

- Powered by Micro USB Port

(Micro USB is not for Flashing either!)

- Super Affordable: [$8 for a 64-bit RISC-V Board!](https://pine64.com/product/128mb-ox64-sbc-available-on-december-2-2022/)

_Ox64 BL808 sounds a little tiny for 64-bit Linux?_

Yeah 64-bit Linux runs with Limited RAM on the D0 Multimedia Core. But most Peripherals are hosted on the M0 Wireless Core: WiFi, BLE, BT, Zigbee, Audio, ...

So we flash M0 with a simple 32-bit RISC-V Firmware, to forward the Peripheral Interrupts from M0 to D0 Linux.

Here are the binaries loaded into D0 Multimedia Core and M0 Wireless Core, from [buildroot_bouffalo](https://github.com/openbouffalo/buildroot_bouffalo)...

* __d0_lowload_bl808_d0.bin__: This is a very basic bootloader that loads opensbi, the kernel and dts files into ram

* __m0_lowload_bl808_m0.bin__: This firmware runs on M0 and forwards interupts to the D0 for several peripherals

* __bl808-firmware.bin__: An image containing OpenSBI, Uboot and uboot dtb files.

* __sdcard-*.tar.xz__: A tarball containing the rootfs for the image to be flashed to the SD card

Perhaps Ox64 BL808 might run more efficiently with a tiny 64-bit RTOS.

_Why Apache NuttX RTOS?_

It might be interesting to run Apache NuttX RTOS on both the D0 Multimedia Core and the M0 Wireless Core. Then D0 and M0 can talk over OpenAMP (Asymmetric Multi-Processing).

Let's explore...

# NuttX Automated Daily Build for Ox64

NuttX for Ox64 is now built automatically every day via GitHub Actions.

The Daily Releases are available here...

- [nuttx-ox64/releases](https://github.com/lupyuen/nuttx-ox64/releases)

[nuttx.hash](https://github.com/lupyuen/nuttx-ox64/releases/download/nuttx-ox64-2023-12-19/nuttx.hash) contains the Commit Hash of the NuttX Kernel and NuttX Apps repos...

```text
NuttX Source: https://github.com/apache/nuttx/tree/31a6ffa15ca4e1604f80a02646a73c16570b8cbb
NuttX Apps: https://github.com/apache/nuttx-apps/tree/dcfb4d066247463daed6c59ed09668004f72e1c8
```

The GitHub Actions Workflow is here...

- [ox64.yml](https://github.com/lupyuen/nuttx-ox64/blob/main/.github/workflows/ox64.yml)

To run the NuttX Daily Build on Ox64...
1. Download the `Image` file from the Daily Build
1. Overwrite the `Image` file in the [Ox64 Linux microSD Card](https://www.hackster.io/lupyuen/8-risc-v-sbc-on-a-real-time-operating-system-ox64-nuttx-474358)
1. Boot Ox64 with the microSD Card

Maybe someday we'll do Daily Automated Testing with the Ox64 Emulator...

- ["Emulate Ox64 BL808 SBC with TinyEMU"](https://github.com/lupyuen/nuttx-tinyemu#emulate-ox64-bl808-sbc-with-tinyemu)

Or with a real Ox64 SBC...

1. Download the Daily Build to TFTP Server
1. Power on Ox64 with an [IKEA Smart Power Plug via Home Assistant](https://lupyuen.github.io/articles/tftp#whats-next)
1. Ox64 boots the Daily Build over TFTP
1. Capture the Automated Testing Log and write to the Release Notes

[(Similar to BL602)](https://lupyuen.github.io/articles/auto)

# Flashing UART vs Serial Console

Read the article...

- ["Ox64 BL808 RISC-V SBC: Booting Linux and (maybe) Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox64)

![Flashing UART vs Serial Console](https://lupyuen.github.io/images/ox64-pinout.jpg)

_We need to handle TWO UART Ports on Ox64?_

Yeah don't confuse the 2 UART Ports on Ox64! Let's give the UART Ports distinctive names [(like Migi & Dali)](https://en.wikipedia.org/wiki/Migi_%26_Dali)...

1. __Ox64 Flashing UART__: Used for Flashing Ox64

+ Flashing UART TX is __GPIO 14__ (Physical Pin 1)
+ Flashing UART RX is __GPIO 15__ (Physical Pin 2)
+ Remember to connect GND
+ Baud Rate for Normal Mode: 2,000,000 (2 Mbps)
+ Baud Rate for Flashing Mode: 230,400 (230.4 kbps)
+ BL808 UART0 is controlled by the M0 Wireless Core (OpenBouffalo Firmware)

1. __Ox64 Serial Console__: Used for Linux Serial Console (plus OpenSBI and U-Boot Bootloader)

+ Serial Console TX is __GPIO 16__ (Physical Pin 32)
+ Serial Console RX is __GPIO 17__ (Physical Pin 31)
+ Remember to connect GND
+ Baud Rate: 2,000,000 (2 Mbps)
+ BL808 UART3 is controlled by the D0 Multimedia Core (Linux + OpenSBI + U-Boot)
+ Output is totally blank if OpenBouffalo Firmware [wasn't flashed correctly](https://github.com/openbouffalo/buildroot_bouffalo/issues/60), or if OpenSBI / U-Boot / Linux couldn't boot

NEITHER UART Port is accessible over USB-C or Micro USB. So yeah it's totally counterintuitive.

(Maybe someone can create a Stackable HAT or Breadboard, that will expose the 2 UART Ports as USB Dongles? Or a UART Switcher?)

[(__For Pre-Production Ox64:__ Physical Pins are different, but GPIOs above are correct)](https://lupyuen.github.io/images/ox64-sd.jpg)

_Why 2 Baud Rates for Flashing UART?_

When we power up Ox64 in __Normal Mode__: (Boot Button NOT pressed)

- Flashing UART Port will show us the OpenBouffalo Firmware running on M0 Wireless Core

- This M0 Firmware will forward Peripheral Interrupts to D0 Multimedia Core

- M0 Firmware is hardcoded for 2 Mbps

- Not really fun to watch. But we use this for testing our 2 Mbps USB Serial Adapter.

When we power up Ox64 in __Flashing Mode__: (Boot Button pressed)

- Ox64 is ready for Firmware Flashing by the BL DevCube GUI Tool

- Firmware Flashing supports various Baud Rates: 230.4 kbps, 2 Mbps, ...

- But 2 Mbps will fail on macOS. That's why we Flash Firmware at 230.4 kbps.

[(Same problem when flashing BL602)](https://lupyuen.github.io/articles/flash#flash-the-firmware)

_Serial Console is always 2 Mbps?_

Yeah 2 Mbps is hardcoded in Ox64 Linux. Switching to other Baud Rates will show garbled text.

Thus our USB Serial Adapter must connect reliably to Ox64 at 2 Mbps.

Now we flash Ox64 and boot Linux...

# Flash OpenSBI and U-Boot Bootloader to Ox64 BL808

Read the article...

- ["Ox64 BL808 RISC-V SBC: Booting Linux and (maybe) Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox64)

Before booting Linux on Ox64, we flash OpenSBI + U-Boot Bootloader to D0 Multimedia Core, and the Peripheral Interrupt Firmware to M0 Wireless Core. From [buildroot_bouffalo](https://github.com/openbouffalo/buildroot_bouffalo):

* __d0_lowload_bl808_d0.bin__: This is a very basic bootloader that loads opensbi, the kernel and dts files into ram

* __m0_lowload_bl808_m0.bin__: This firmware runs on M0 and forwards interupts to the D0 for several peripherals

* __bl808-firmware.bin__: An image containing OpenSBI, Uboot and uboot dtb files.

Here are the steps, based on the [Official Flashing Instructions](https://github.com/openbouffalo/buildroot_bouffalo#flashing-instructions)...

1. We tested with [Pine64 Woodpecker CH340G USB Serial Adapter](https://pine64.com/product/serial-console-woodpecker-edition/) on macOS x64.

Warning: Some USB Serial Adapters [WON'T WORK!](https://wiki.pine64.org/wiki/Ox64#Compatible_UARTs_when_in_bootloader_mode)

Probably because we are connecting at 2 Mbps, which might be too fast for some USB Serial Adapters.

[(Like this CP2102, which shows garbled text at 2 Mbps)](https://www.lazada.sg/products/i2037772272-s11135131253.html)

![Flashing UART](https://lupyuen.github.io/images/ox64-pinout2.jpg)

1. To Test our USB Serial Adapter: Connect the USB Serial Adapter to __Ox64 Flashing UART__ (pic above)...
+ Flashing UART TX is __GPIO 14__ (Physical Pin 1)
+ Flashing UART RX is __GPIO 15__ (Physical Pin 2)
+ Remember to connect GND
+ Baud 2,000,000 (2 Mbps)

Start the USB Serial Terminal (Flashing UART).

Power up Ox64 via the Micro USB Port. Ox64 Green LED should light up.

This Clickety Micro USB Cable is very handy for rebooting Ox64...

![Clickety Micro USB Cable](https://lupyuen.github.io/images/ox64-usb.jpg)

1. In the USB Serial Terminal (Flashing UART), we should see the Ox64 Factory Test Firmware...

```text
Build:19:50:39,Nov 20 2022
Copyright (c) 2022 Bouffalolab team
dynamic memory init success,heap size = 93 Kbyte
sig1:ffff32ff
sig2:0000ffff
Pong!
Ping!
```

[(Source)](https://adventurist.me/posts/00317)

If the text appears garbled: Try a different USB Serial Adapter. (See above)

My prototype version shows this instead...

```text
Init CLI with event Driven
start aos loop...
CLI RAW Data, c906
/romfs/c906.bin not found!
```

[(Source)](https://gist.github.com/lupyuen/43676407bbced733e65566879e18732b)

1. Pre-Flash Check: Set BL808 board to programming mode
+ Remove the microSD Card
+ Press and Hold BOOT Button
+ Unplug and replug the Micro USB Port
+ Release BOOT button
+ Ox64 Green LED should turn on

In the USB Serial Terminal (Flashing UART), we should see this...

```text
.
```

Yep Ox64 is ready for flashing!

1. Now we prepare to flash:

Disconnect the USB Serial Terminal (to release the Flashing UART)

Set BL808 board to programming mode
+ Remove the microSD Card
+ Press and Hold BOOT Button
+ Unplug and replug the Micro USB Port
+ Release BOOT button
+ Ox64 Green LED should turn on

1. We download the Ox64 Binaries...

- [bl808-linux-pine64_ox64_full_defconfig.tar.gz](https://github.com/openbouffalo/buildroot_bouffalo/releases/download/v1.0.1/bl808-linux-pine64_ox64_full_defconfig.tar.gz)

From the latest Ox64 Linux Release...

- [openbouffalo/buildroot_bouffalo (Release v1.0.1)](https://github.com/openbouffalo/buildroot_bouffalo/releases/tag/v1.0.1)

Unzip the download and we should see this...

```bash
→ ls -l firmware
7340032 bl808-firmware.bin
31360 d0_lowload_bl808_d0.bin
65760 m0_lowload_bl808_m0.bin
43859444 sdcard-pine64_ox64_full_defconfig.img.xz
```

1. We'll run BouffaloLab DevCube for Flashing BL808.

Only Ubuntu x64, macOS and Windows are supported.

TODO: How to flash BL808 on Arm64 SBCs and Pinebook Pro? Sigh. See [bflb-iot-tool / bflb-mcu-tool](https://wiki.pine64.org/wiki/Ox64#Alternative:_Open-Source_Flashing)

1. Download Bouffalo Lab DevCube 1.8.3 from...

[openbouffalo.org/static-assets/bldevcube/BouffaloLabDevCube-v1.8.3.zip](https://openbouffalo.org/static-assets/bldevcube/BouffaloLabDevCube-v1.8.3.zip)

[(1.8.4 and later won't work)](https://github.com/openbouffalo/buildroot_bouffalo/issues/60)

May need to Grant Execute Permission...

```bash
cd BouffaloLabDevCube-v1.8.3
chmod +x BLDevCube-macos-x86_64
./BLDevCube-macos-x86_64
```

1. Run DevCube, select "BL808", and switch to "MCU" page

1. M0 Group: Group0

Image Addr: 0x58000000

PATH: Select "m0_lowload_bl808_m0.bin"

1. D0 Group: Group0

Image Addr: 0x58100000

PATH: Select "d0_lowload_bl808_d0.bin"

1. Set UART Rate to 230400.

Don't set to 2000000, it will fail on macOS!

[(Same problem when flashing BL602)](https://lupyuen.github.io/articles/flash#flash-the-firmware)

1. Click "Create & Download" and wait until it's done

[(See the log)](https://gist.github.com/lupyuen/125e15be5ed1e034bed33d16ed496d87)

1. Switch to "IOT" page

1. Enable 'Single Download'

Set Address to 0x800000

Select "bl808-firmware.bin"

1. Set UART Rate to 230400.

Don't set to 2000000, it will fail on macOS!

[(Same problem when flashing BL602)](https://lupyuen.github.io/articles/flash#flash-the-firmware)

1. Click "Create & Download" again and wait until it's done

[(See the log)](https://gist.github.com/lupyuen/e8c0aca0ebd0f1eae034b0996a5b3ec3)

1. Start the USB Serial Terminal (Flashing UART at 2 Mbps).

Unplug and replug the Micro USB Port.

(Don't press the Boot Button!)

1. On the USB Serial Terminal (Flashing UART) we should see...

```text
[I][] Powered by BouffaloLab
[I][] Build:11:52:22,Mar 6 2023
[I][] Copyright (c) 2023 OpenBouffalo team
[I][] Copyright (c) 2022 Bouffalolab team
[I][] =========== flash cfg ==============
[I][] jedec id 0xEF6018
[I][] mid 0xEF
[I][] iomode 0x04
[I][] clk delay 0x01
[I][] clk invert 0x01
[I][] read reg cmd0 0x05
[I][] read reg cmd1 0x35
[I][] write reg cmd0 0x01
[I][] write reg cmd1 0x31
[I][] qe write len 0x01
[I][] cread support 0x00
[I][] cread code 0xFF
[I][] burst wrap cmd 0x77
[I][] sector size: 0x04
[I][] =====================================
[I][] dynamic memory init success,heap size = 156 Kbyte
[I][MAIN] Starting Mailbox Handlers
[I][MBOX] Forwarding Interupt SDH (33) to D0 (0x58008bbc)
[I][MBOX] Forwarding Interupt GPIO (60) to D0 (0x58008d0e)
[I][MAIN] Running...
[I][MBOX] Mailbox IRQ Stats:
[I][MBOX] .Peripheral SDH (33): 0
[I][MBOX] .Peripheral GPIO (60): 0
[I][MBOX] Unhandled Interupts: 0 Unhandled Signals 0
```

[(Source)](https://gist.github.com/lupyuen/52ccdf076ae294db26e837e6ffc4bafb)

Yep we have flashed the OpenBouffalo Firmware successfully!

![Serial Console](https://lupyuen.github.io/images/ox64-pinout3.jpg)

1. Connect our USB Serial Adapter to __Ox64 Serial Console__: (pic above)
+ Serial Console TX is __GPIO 16__ (Physical Pin 32)
+ Serial Console RX is __GPIO 17__ (Physical Pin 31)
+ Remember to connect GND
+ Baud 2,000,000 (2 Mbps)

Start the USB Serial Terminal (Serial Console).

Unplug and replug the Micro USB Port.

(Don't press the Boot Button!)

1. On the USB Serial Terminal (Serial Console) we should see...

```text
U-Boot 2023.04-rc2 (Mar 06 2023 - 11:48:40 +0000)
Card did not respond to voltage select! : -110
BOOTP broadcast
Retry time exceeded; starting again
```

[(Source)](https://gist.github.com/lupyuen/0b1a98781e86ba11c5538eb1e3058718)

Which is OK because U-Boot Bootloader is waiting for a microSD Card.

1. If nothing appears...

Check that we are using [Bouffalo Lab DevCube 1.8.3](https://openbouffalo.org/static-assets/bldevcube/BouffaloLabDevCube-v1.8.3.zip)

[(1.8.4 and later won't work)](https://github.com/openbouffalo/buildroot_bouffalo/issues/60)

In BL Dev Cube, UART Rate (for MCU and IoT) should be 230400.

Don't set to 2000000, it will fail on macOS!

[(Same problem when flashing BL602)](https://lupyuen.github.io/articles/flash#flash-the-firmware)

Let's load Ox64 Linux into a microSD Card...

# Boot Linux on Ox64 BL808

![Ox64 Linux in a microSD Card](https://lupyuen.github.io/images/ox64-sd.jpg)

Read the article...

- ["Ox64 BL808 RISC-V SBC: Booting Linux and (maybe) Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox64)

Now that D0 Multimedia Core is flashed with OpenSBI and U-Boot Bootloader, we're ready to boot Linux on microSD!

Based on the [Official Flashing Instructions](https://github.com/openbouffalo/buildroot_bouffalo#flashing-instructions)...

1. Look for the microSD Image that we downloaded earlier...

```text
sdcard-pine64_ox64_full_defconfig.img.xz
```

Uncompress the file to get...

```text
sdcard-pine64_ox64_full_defconfig.img
```

1. Flash the uncompressed image to your microSD card.

You can use [Balena Etcher](https://github.com/balena-io/etcher), GNOME Disks or `dd`.

1. Insert the microSD Card into Ox64. (Pic above)

![Flashing UART](https://lupyuen.github.io/images/ox64-pinout2.jpg)

1. Connect our USB Serial Adapter to __Ox64 Flashing UART__: (pic above)
+ Flashing UART TX is __GPIO 14__ (Physical Pin 1)
+ Flashing UART RX is __GPIO 15__ (Physical Pin 2)
+ Remember to connect GND
+ Baud 2,000,000 (2 Mbps)

Start the USB Serial Terminal (Flashing UART).

Unplug and replug the Micro USB Port.

(Don't press the Boot Button!)

1. On the USB Serial Terminal (Flashing UART) we should see the same thing as earlier...

```text
[I][MAIN] Starting Mailbox Handlers
[I][MBOX] Forwarding Interupt SDH (33) to D0 (0x58008bbc)
[I][MBOX] Forwarding Interupt GPIO (60) to D0 (0x58008d0e)
[I][MAIN] Running...
[I][MBOX] Mailbox IRQ Stats:
[I][MBOX] .Peripheral SDH (33): 0
[I][MBOX] .Peripheral GPIO (60): 0
[I][MBOX] Unhandled Interupts: 0 Unhandled Signals 0
```

[(Source)](https://gist.github.com/lupyuen/52ccdf076ae294db26e837e6ffc4bafb)

![Serial Console](https://lupyuen.github.io/images/ox64-pinout3.jpg)

1. Connect our USB Serial Adapter to __Ox64 Serial Console__: (pic above)
+ Serial Console TX is __GPIO 16__ (Physical Pin 32)
+ Serial Console RX is __GPIO 17__ (Physical Pin 31)
+ Remember to connect GND
+ Baud 2,000,000 (2 Mbps)

Start the USB Serial Terminal (Serial Console).

Unplug and replug the Micro USB Port.

(Don't press the Boot Button!)

1. On the USB Serial Terminal (Serial Console) we should see...

```text
[I][] Powered by BouffaloLab
[I][] Build:11:52:04,Mar 6 2023
[I][] Copyright (c) 2023 OpenBouffalo team
[I][] Copyright (c) 2022 Bouffalolab team
[I][] dynamic memory init success,heap s[I][LowLoad] D0 start...
[I][LowLoad] low_load start...
[I][LowLoad] Header at 0x5d5ff000
[I][LowLoad] Section dtb(1) - Start 0x5d5ff100, Size 14314
[I][LowLoad] Copying DTB to 0x51ff8000...0x51ffb7ea
[I][LowLoad] Done!
[I][LowLoad] Section OpenSBI(2) - Start 0x5d60f100, Size 109864
[I][LowLoad] Copying OpenSBI to 0x3ef80000...0x3ef9ad28
[I][LowLoad] Done!
[I][LowLoad] Section Kernel(3) - Start 0x5d62f100, Size 315597
[I][LowLoad] Uncompressing Kernel to 0x50000000...
[I][LowLoad] Done!
[I][LowLoad] CRC: 00000000
[I][LowLoad] load time: 61306 us
[I][LowLoad] ing PMP
[I][LowLoad] Booting OpenSBI at 0x000000003ef80000 with DTB at 0x51ff8000
...
OpenSBI v1.2
Platform Name : Pine64 Ox64 (D0)
Platform Features medeleg
Platform HART Count : 1
Platform IPI Device : aclint-mswi
Platform Timer Device : aclint-mtimer @ 1000000Hz
Platform Console Device : bflb_uart
Platform HSM Device : ---
Platform PMU Device : ---
Platform Reboot Device : ---
Platform Shutdown Device : ---
Firmware Base : 0x3ef80000
Firmware Size : 200 KB
Runtime SBI Version : 1.0
...

U-Boot 2023.04-rc2 (Mar 06 2023 - 11:48:40 +0000)
DRAM: 64 MiB
Core: 36 devices, 17 uclasses, devicetree: board
MMC: mmc@20060000: 0
Loading Environment from FAT... Unable to read "uboot.env" from mmc0:2...
...
Starting kernel ...
Linux version 6.2.0 (runner@fv-az587-938) (riscv64-unknown-linux-gnu-gcc (Xuantie-900 linux-5.10.4 glibc gcc Toolchain V2.6.1 B-20220906) 10.2.0, GNU ld (GNU Binutils) 2.35) #1 Mon Mar 6 11:17:27 UTC 2023
...
Welcome to Buildroot
ox64 login:
```

[(See the Complete Log)](https://gist.github.com/lupyuen/3035a70d52d2d1d529e96f5292f54210)

[(Watch the Video on YouTube)](https://youtu.be/UJ_7DyHnfDA)

Yep Linux is running on Ox64 yay! (Pic below)

1. If nothing appears...

Check that we are using [Bouffalo Lab DevCube 1.8.3](https://openbouffalo.org/static-assets/bldevcube/BouffaloLabDevCube-v1.8.3.zip)

[(1.8.4 and later won't work)](https://github.com/openbouffalo/buildroot_bouffalo/issues/60)

In BL Dev Cube, UART Rate (for MCU and IoT) should be 230400.

Don't set to 2000000, it will fail on macOS!

[(Same problem when flashing BL602)](https://lupyuen.github.io/articles/flash#flash-the-firmware)

1. If we see...

```text
U-Boot 2023.04-rc2 (Mar 06 2023 - 11:48:40 +0000)
Card did not respond to voltage select! : -110
BOOTP broadcast
Retry time exceeded; starting again
```

[(Source)](https://gist.github.com/lupyuen/0b1a98781e86ba11c5538eb1e3058718)

Check that the microSD Card is inserted correctly. (Pic above)

1. TODO: TFTP Boot over Ethernet

![Boot Linux on Ox64 BL808](https://lupyuen.github.io/images/ox64-title.jpg)

Comment by [@gamelaster](https://x.com/gamelaster/status/1719073156281798755?s=20)...

> "This is not hardware specific, but flasher specific. With blisp, I was able to get faster flashing working, but this is Apple's quirk. Or maybe not? Because FreeBSD need same quirks and exact buffer sizes as Apple."

Comment by [@madushan1000](https://x.com/madushan1000/status/1719069431580524720?s=20)...

> "You can also use u-boot. https://github.com/openbouffalo/u-boot/releases/tag/bl808-2023-02-19
You can also get rid of mailbox, but you will have to build the kernel yourself https://github.com/openbouffalo/linux/tree/bl808/all"

# Forward Peripheral Interrupts

Read the article...

- ["Ox64 BL808 RISC-V SBC: Booting Linux and (maybe) Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox64)

TODO

```text
[I][MAIN] Starting Mailbox Handlers
[I][MBOX] Forwarding Interupt SDH (33) to D0 (0x58008bbc)
[I][MBOX] Forwarding Interupt GPIO (60) to D0 (0x58008d0e)
[I][MAIN] Running...
[I][MBOX] Mailbox IRQ Stats:
[I][MBOX] .Peripheral SDH (33): 0
[I][MBOX] .Peripheral GPIO (60): 0
[I][MBOX] Unhandled Interupts: 0 Unhandled Signals 0
```

[(Source)](https://gist.github.com/lupyuen/52ccdf076ae294db26e837e6ffc4bafb)

SDH: SD Card (SDIO) Host Controller (BL808 RM Page 561)

IRQ 60: GPIO_INT0 (IRQ_NUM_BASE+44) GPIO Interrupt (BL808 RM Page 44)

[GPIO_INT0_IRQn](https://github.com/bouffalolab/bl808_linux/blob/main/bl_mcu_sdk_bl808/drivers/bl808_driver/regs/bl808.h#L123)

[SDH is IRQ 33: SDH_IRQn](https://github.com/bouffalolab/bl808_linux/blob/main/bl_mcu_sdk_bl808/drivers/bl808_driver/regs/bl808.h#L96)

IRQ_NUM_BASE is 16 (BL808 RM Page 45)

[m0_lowload](https://github.com/openbouffalo/OBLFR/tree/master/apps/m0_lowload)

[d0_lowload](https://github.com/openbouffalo/OBLFR/tree/master/apps/d0_lowload)

[Forward GPIO Interrupt](https://github.com/openbouffalo/OBLFR/blob/master/components/mailbox/src/oblfr_mailbox.c#L127-L135)

[Forward SDH Interrupt](https://github.com/openbouffalo/OBLFR/blob/master/components/mailbox/src/oblfr_mailbox.c#L95-L103)

[Setup SDH Interrupt](https://github.com/openbouffalo/OBLFR/blob/master/components/mailbox/src/oblfr_mailbox.c#L238C1-L257)

Other Interrupts (unused)
- [UART2](https://github.com/openbouffalo/OBLFR/blob/master/components/mailbox/src/oblfr_mailbox.c#L103-L111)
- [USB](https://github.com/openbouffalo/OBLFR/blob/master/components/mailbox/src/oblfr_mailbox.c#L111-L119)
- [EMAC](https://github.com/openbouffalo/OBLFR/blob/master/components/mailbox/src/oblfr_mailbox.c#L119-L127)

# Inspect the Linux Image for Ox64 BL808

Read the article...

- ["Ox64 BL808 RISC-V SBC: Booting Linux and (maybe) Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox64)

_Will Apache NuttX RTOS boot on Ox64 BL808?_

Let's examine the Linux Kernel Image for Ox64, and we replicate the same format for NuttX. (Which is how we ported NuttX to 64-bit RISC-V Star64 JH7110 SBC)

We download the Ox64 Binaries...

- [bl808-linux-pine64_ox64_full_defconfig.tar.gz](https://github.com/openbouffalo/buildroot_bouffalo/releases/download/v1.0.1/bl808-linux-pine64_ox64_full_defconfig.tar.gz)

From the latest Ox64 Linux Release...

- [openbouffalo/buildroot_bouffalo (Release v1.0.1)](https://github.com/openbouffalo/buildroot_bouffalo/releases/tag/v1.0.1)

Unzip it and mount the SD Card Image...

```bash
→ ls -l sdcard-pine64_ox64_full_defconfig
- 13,154,816 Image
- 4,012 bl808-pine64-ox64.dtb
- 4,106 bl808-sipeed-m1s.dtb
- 350 boot-m1s.scr
- 352 boot-pine64.scr
- 352 boot.scr
d 96 extlinux
```

Dump the `Image` as hex...

```bash
→ hexdump sdcard-pine64_ox64_full_defconfig/Image
0000000 4d 5a 6f 10 20 08 01 00 00 00 20 00 00 00 00 00
0000010 00 80 cd 00 00 00 00 00 00 00 00 00 00 00 00 00
0000020 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000030 52 49 53 43 56 00 00 00 52 53 43 05 40 00 00 00
```

The Linux Kernel Image begins with this __RISC-V Linux Image Header__...

- [__"Boot Image Header in RISC-V Linux"__](https://www.kernel.org/doc/html/latest/riscv/boot-image-header.html)

Here are the decoded bytes...

1. __code0__: Executable code

(4 bytes, offset `0x00`)

```text
4d 5a 6f 10
```

1. __code1__: Executable code

(4 bytes, offset `0x04`)

```text
20 08 01 00
```

1. __text_offset__: Image load offset, little endian

(8 bytes, offset `0x08`)

```text
00 00 20 00 00 00 00 00
```

1. __image_size__: Effective Image size, little endian

(8 bytes, offset `0x10`)

```text
00 80 cd 00 00 00 00 00
```

1. __flags__: Kernel flags, little endian

(8 bytes, offset `0x18`)

```text
00 00 00 00 00 00 00 00
```

1. __version__: Version of this header (_MinL_ _MinM_ `.` _MajL_ _MajM_)

(4 bytes, offset `0x20`)

```text
02 00 00 00
```

1. __res1__: Reserved

(4 bytes, offset `0x24`)

```text
00 00 00 00
```

1. __res2__: Reserved

(8 bytes, offset `0x28`)

```text
00 00 00 00 00 00 00 00
```

1. __magic__: Magic number, little endian, "RISCV\x00\x00\x00"

(8 bytes, offset `0x30`)

```text
52 49 53 43 56 00 00 00
```

1. __magic2__: Magic number 2, little endian, "RSC\x05"

(4 bytes, offset `0x38`)

```text
52 53 43 05
```

1. __res3__: Reserved for PE COFF offset

(4 bytes, offset `0x3C`)

```text
40 00 00 00
```

Our NuttX Kernel shall __recreate this RISC-V Linux Image Header__. (Total `0x40` bytes)

(Or U-Boot Bootloader might refuse to boot NuttX)

Header Values are exactly the same as Star64. (Except the Image Size and Executable Code, since the Jump Address is different)

Thus we simply reuse the code from NuttX Star64!

# Linux Device Tree for Ox64 BL808

Read the article...

- ["Ox64 BL808 RISC-V SBC: Booting Linux and (maybe) Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox64)

TODO: Dump the Device Tree

```text
dtc \
-o bl808-pine64-ox64.dts \
-O dts \
-I dtb \
bl808-pine64-ox64.dtb
```

Here's the Decompiled Device Tree: [bl808-pine64-ox64.dts](bl808-pine64-ox64.dts)

TODO: Transmit to UART3 at 0x30002000. Reuse the BL602 UART Driver for NuttX.

```text
serial@30002000 {
compatible = "bflb,bl808-uart";
reg = <0x30002000 0x1000>;
interrupts = <0x14 0x04>;
clocks = <0x04>;
status = "okay";
phandle = <0x0a>;
};
```

[(Source)](https://github.com/lupyuen/nuttx-ox64/blob/main/bl808-pine64-ox64.dts#L89-L96)

TODO: Forward the Interrupts from M0 Wireless Core to D0 Multimedia Core via Mailbox / IPC (Where are the addresses documented?)

```text
mailbox@30005000 {
compatible = "bflb,bl808-ipc";
reg = <
0x30005000 0x20
0x30005020 0x20
0x2000a800 0x20
0x2000a820 0x20
>;
interrupts = <0x36 0x04>;
interrupt-controller;
#interrupt-cells = <0x03>;
#mbox-cells = <0x02>;
status = "okay";
phandle = <0x03>;
};
```

[(Source)](https://github.com/lupyuen/nuttx-ox64/blob/main/bl808-pine64-ox64.dts#L118-L127)

TODO: Print Debug Logs with OpenSBI

# Boot Apache NuttX RTOS on Ox64 BL808

Read the article...

- ["Ox64 BL808 RISC-V SBC: Starting Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox2)

_What happens if we boot Star64 NuttX on Ox64 BL808?_

Let's find out!

```bash
## Download and build NuttX for Star64
git clone --branch ox64 https://github.com/lupyuen2/wip-pinephone-nuttx nuttx
git clone --branch ox64 https://github.com/lupyuen2/wip-pinephone-nuttx-apps apps
cd nuttx
tools/configure.sh star64:nsh
make

## Export the Binary Image to nuttx.bin
riscv64-unknown-elf-objcopy \
-O binary \
nuttx \
nuttx.bin

## TODO: Prepare the microSD for Ox64 Linux
## https://lupyuen.github.io/articles/ox64#boot-linux-on-ox64

## Copy and overwrite the `Image` file on the microSD for Ox64 Linux
cp nuttx.bin Image
cp Image "/Volumes/NO NAME"
diskutil unmountDisk /dev/disk2
```

We boot NuttX on Ox64 via microSD... But Ox64 shows absolutely nothing!

```text
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 ...
```

[(Source)](https://gist.github.com/lupyuen/8134f17502db733ce87d6fa8b00eab55)

We're hoping that NuttX would crash and OpenSBI could print a meaningful Stack Trace. But nope! NuttX was probably stuck in a loop waiting for Star64 UART.

Let's print to the Ox64 Serial Console in the NuttX Boot Code (in RISC-V Assembly)...

# Print to Ox64 Serial Console in NuttX Boot Code

Read the article...

- ["Ox64 BL808 RISC-V SBC: Starting Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox2)

_How to print to the Ox64 Serial Console in the NuttX Boot Code? (RISC-V Assembly)_

When we compare the BL808 and BL602 Reference Manuals, we discover that BL808 UART works the same way as BL602.

This is how the BL602 UART Driver prints to the Serial Console: [bl602_serial.c](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl602/bl602_serial.c#L704-L725)

```c
#define BL602_UART_FIFO_WDATA_OFFSET 0x000088 /* uart_fifo_wdata */
#define BL602_UART_FIFO_WDATA(n) (BL602_UART_BASE(n) + BL602_UART_FIFO_WDATA_OFFSET)

static 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 output to FIFO
putreg32(ch, BL602_UART_FIFO_WDATA(uart_idx));
}
```

So for BL808, we simply write the character to...

- UART3 Base Address: 0x30002000 (from the Linux Device Tree earlier)

- Offset: 0x88

[Based on Star64 Debug Code](https://lupyuen.github.io/articles/nuttx2#print-to-qemu-console), we code this in RISC-V Assembly...

```text
/* 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)
```

We insert the above code into the NuttX Boot Code: [jh7110_head.S](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/arch/risc-v/src/jh7110/jh7110_head.S#L69-L87)

Now NuttX prints to the Serial Console yay! (Pic below)

```text
Starting kernel ...
123
```

[(Source)](https://gist.github.com/lupyuen/1f895c9d57cb4e7294522ce27fea70fb)

OpenSBI boots on Ox64 with Hart ID 0 (instead of 1), so we remove this code...

```text
/* 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 */
```

[(Source)](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/arch/risc-v/src/jh7110/jh7110_head.S#L89-L93)

![Booting Apache NuttX RTOS on Pine64 Ox64 64-bit RISC-V SBC (Bouffalo Lab BL808)](https://lupyuen.github.io/images/ox64-nuttx.png)

# Update the NuttX Boot Address for Ox64 BL808

Read the article...

- ["Ox64 BL808 RISC-V SBC: Starting Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox2)

_What is the Linux Boot Address for Ox64 BL808?_

From the [U-Boot Settings](https://gist.github.com/lupyuen/30df5a965fabf719cc52bf733e945db7)...

```bash
kernel_addr_r=0x50200000
```

Let's update the Boot Address in NuttX: [ld.script](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/boards/risc-v/jh7110/star64/scripts/ld.script#L20-L27)

```text
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 = 6M /* w/ cache */
}
```

TODO: Use up to 64 MB, the total RAM Size on Ox64

We make the same changes to the NuttX Config: [nsh/defconfig](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/boards/risc-v/jh7110/star64/configs/nsh/defconfig)

```text
CONFIG_RAM_START=0x50200000
CONFIG_RAM_SIZE=1048576
CONFIG_ARCH_PGPOOL_PBASE=0x50600000
CONFIG_ARCH_PGPOOL_VBASE=0x50600000
CONFIG_ARCH_PGPOOL_SIZE=4194304
```

And the Memory Mapping: [jh7110_mm_init.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ba093f2477f011ec7c5351eaba0a3002add02d6b/arch/risc-v/src/jh7110/jh7110_mm_init.c#L47-L50)

```c
/* Map the whole I/O memory with vaddr = paddr mappings */
#define MMU_IO_BASE (0x00000000)
#define MMU_IO_SIZE (0x50000000)
```

TODO: What's the RAM Disk Address? It's missing from [U-Boot Settings](https://gist.github.com/lupyuen/30df5a965fabf719cc52bf733e945db7)

```c
/* Ramdisk Load Address from U-Boot */
#define RAMDISK_ADDR_R (0x46100000)
```

[(Source)](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/arch/risc-v/src/jh7110/jh7110_mm_init.c#L43-L45)

NuttX shows the same output as earlier, no change...

```text
Starting kernel ...
123
```

[(Source)](https://gist.github.com/lupyuen/1f895c9d57cb4e7294522ce27fea70fb)

Let's fix the NuttX UART Driver...

# Fix the NuttX UART Driver for Ox64 BL808

Read the article...

- ["Ox64 BL808 RISC-V SBC: Starting Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox2)

_NuttX on Ox64 has been awfully quiet. How to fix the UART Driver so that NuttX can print things?_

Ox64 is still running on the JH7110 UART Driver (16550). Let's make a quick patch so that we will see something on the Ox64 Serial Console...

We hardcode the UART3 Base Address (from above) and FIFO Offset for now: [uart_16550.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/drivers/serial/uart_16550.c#L1698-L1716)

```c
// Write one character to the UART (polled)
static void u16550_putc(FAR struct u16550_s *priv, int ch) {

// Hardcode the UART3 Base Address and 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)

We skip the reading and writing of other UART Registers, because we'll patch them later: [uart_16550.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/drivers/serial/uart_16550.c#L604-L632)

```c
// Read UART Register
static inline uart_datawidth_t u16550_serialin(FAR struct u16550_s *priv, int offset) {
return 0; ////
// Commented out the rest
}

// Write UART Register
static inline 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're not accessing the Line Control Register: [uart_16550.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/drivers/serial/uart_16550.c#L633-L670)

```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.
static int u16550_wait(FAR struct u16550_s *priv) {
// Nopez! No waiting for now
return OK; ////
}
```

Now NuttX prints our very first Stack Dump on Ox64 yay!

```text
Starting kernel ...
123
ABC
riscv_exception: EXCEPTION: Load access fault. MCAUSE: 0000000000000005, EPC: 0000000050208086, MTVAL: 000000000c002104
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
up_dump_register: A4: ffffffffffffffff A5: 8000000200046000 A6: 0000000000000000 A7: fffffffffffffff8
up_dump_register: T0: 00000000502000a8 T1: 0000000000000007 T2: 656d616e2d64746d T3: 0000000050407b10
up_dump_register: T4: 0000000050407b08 T5: 0000000053f23fff T6: 0000000053f33870
up_dump_register: S0: 0000000000000000 S1: 0000000050400140 S2: 0000000000000001 S3: 8000000200046002
up_dump_register: S4: 0000000050400070 S5: 00000000000001b6 S6: 0000000000000000 S7: 0000000000000000
up_dump_register: S8: 0000000053f7a15c S9: 0000000053fcf2e0 S10: 0000000000000001 S11: 0000000000000003
up_dump_register: SP: 0000000050407a00 FP: 0000000000000000 TP: 0000000000000000 RA: 0000000050204064
```

[(Source)](https://gist.github.com/lupyuen/36b8c47abc2632063ca5cdebb958e3e8)

Let's look up the RISC-V Exception Code Address 0x50208086 in our RISC-V Disassembly...

```text
EXCEPTION: Load access fault
MCAUSE: 0000000000000005
EPC: 0000000050208086
MTVAL: 000000000c002104
```

And the offending Data Address 0xc002104. (Which looks very familiar!)

![NuttX prints our very first Stack Dump on Ox64 yay!](https://lupyuen.github.io/images/ox64-stack.png)

# Platform-Level Interrupt Controller for Ox64 BL808

Read the article...

- ["Ox64 BL808 RISC-V SBC: Starting Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox2)

_Why did NuttX crash with this RISC-V Exception?_

```text
EXCEPTION: Load access fault
MCAUSE: 0000000000000005
EPC: 0000000050208086
MTVAL: 000000000c002104
```

NuttX crashed when it tried to access invalid Data Address 0xc002104 from Code Address 0x50208086.

We look up Code Address 0x50208086 in our NuttX Disassembly...

```text
000000005020807a :
up_irq_save():
nuttx/include/arch/irq.h:689
5020807a: 4789 li a5,2
5020807c: 1007b7f3 csrrc a5,sstatus,a5
modifyreg32():
nuttx/arch/risc-v/src/common/riscv_modifyreg32.c:52
{
irqstate_t flags;
uint32_t regval;

flags = spin_lock_irqsave(NULL);
regval = getreg32(addr);
50208080: 4118 lw a4,0(a0)
nuttx/arch/risc-v/src/common/riscv_modifyreg32.c:53
regval &= ~clearbits;
50208082: fff5c593 not a1,a1
nuttx/arch/risc-v/src/common/riscv_modifyreg32.c:52
regval = getreg32(addr);
50208086: 2701 sext.w a4,a4
```

Which comes from here: [riscv_modifyreg32.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/arch/risc-v/src/common/riscv_modifyreg32.c#L38-L57)

```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;
uint32_t regval;

flags = spin_lock_irqsave(NULL);
// Crashes here because `addr` is invalid...
regval = getreg32(addr);
regval &= ~clearbits;
regval |= setbits;
putreg32(regval, addr);
spin_unlock_irqrestore(NULL, flags);
}
```

It's trying to modify a Memory-Mapped Register, and crashed.

_But what Memory-Mapped Register?_

The offending Data Address 0xc002104 actually comes from Star64 PLIC! (Platform-Level Interrupt Controller)

```c
// From https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/arch/risc-v/src/jh7110/hardware/jh7110_memorymap.h#L30
#define JH7110_PLIC_BASE 0x0c000000

// From https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/arch/risc-v/src/jh7110/hardware/jh7110_plic.h#L34-L49
/* Interrupt Priority */
#define JH7110_PLIC_PRIORITY (JH7110_PLIC_BASE + 0x000000)

/* Hart 1 S-Mode Interrupt Enable */
#define JH7110_PLIC_ENABLE1 (JH7110_PLIC_BASE + 0x002100)
#define JH7110_PLIC_ENABLE2 (JH7110_PLIC_BASE + 0x002104)

/* Hart 1 S-Mode Priority Threshold */
#define JH7110_PLIC_THRESHOLD (JH7110_PLIC_BASE + 0x202000)

/* Hart 1 S-Mode Claim / Complete */
#define JH7110_PLIC_CLAIM (JH7110_PLIC_BASE + 0x202004)
```

The PLIC Base Address is different for BL808, let's change it.

_What's the PLIC Base Address in Ox64 BL808?_

PLIC Base Address is 0xe0000000, according to the Linux Device Tree: [bl808-pine64-ox64.dts](https://github.com/lupyuen/nuttx-ox64/blob/main/bl808-pine64-ox64.dts#L129-L138)

```text
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>;
};
```

TODO: Why isn't this documented in [XuanTie OpenC906 User Manual](https://occ-intl-prod.oss-ap-southeast-1.aliyuncs.com/resource/XuanTie-OpenC906-UserManual.pdf)?

So we change the PLIC Base Address for Ox64: [jh7110_memorymap.h](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/arch/risc-v/src/jh7110/hardware/jh7110_memorymap.h#L30)

```c
#define JH7110_PLIC_BASE 0xe0000000
```

TODO: Enable Scheduler Debug

# Handle RISC-V Exceptions in NuttX

Read the article...

- ["Ox64 BL808 RISC-V SBC: Starting Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox2)

Now NuttX crashes at a different place, with IRQ 15...

```text
123ABC
nx_start: Entry
up_irqinitialize: a
up_irqinitialize: b
up_irqinitialize: 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
```

[(Source)](https://gist.github.com/lupyuen/11b8d4221a150f10afa3aa5ab5e50a4c)

_What's IRQ 15?_

From [XuanTie OpenC906 User Manual](https://occ-intl-prod.oss-ap-southeast-1.aliyuncs.com/resource/XuanTie-OpenC906-UserManual.pdf) (Page 21):

> "Exception Vector ID 15: A store/atomic instruction page error exception."

This RISC-V Exception says that we tried to write to an invalid Data Address. And failed.

_Where did it crash?_

Based on our log, NuttX crashes before setting the PLIC!

From [jh7110_irq.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/8f318c363c80e1d4f5788f3815009cb57b5ff298/arch/risc-v/src/jh7110/jh7110_irq.c#L42-L85)

```c
// Init the IRQs
void up_irqinitialize(void) {
_info("a\n");////

/* 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);

/* Colorize the interrupt stack for debug purposes */
...

/* Set irq threshold to 0 (permits all global interrupts) */
_info("e\n");////
putreg32(0, JH7110_PLIC_THRESHOLD);

/* Attach the common interrupt handler */
_info("f\n");////
riscv_exception_attach();
```

_But it's a RISC-V Exception! Shouldn't NuttX dump this as a proper exception?_

See the `riscv_exception_attach()` above? It happens AFTER the crash! This means NuttX hasn't properly initialised the Exception Handlers, when the crash happened.

Let's init the Exception Handlers earlier: [jh7110_irq.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/arch/risc-v/src/jh7110/jh7110_irq.c#L42-L85)

```c
// Init the IRQs
void up_irqinitialize(void) {
_info("a\n");////

/* Disable S-Mode interrupts */
_info("b\n");////
up_irq_save();

/* Attach the common interrupt handler */
_info("f\n");////
// Init the Exception Handlers here
riscv_exception_attach();

/* Disable all global interrupts */
_info("c\n");////
// Crashes here!
putreg32(0x0, JH7110_PLIC_ENABLE1);
putreg32(0x0, JH7110_PLIC_ENABLE2);
```

`riscv_exception_attach()` will handle all RISC-V Exceptions, including Store/AMO Page Fault (IRQ 15): [riscv_exception.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/arch/risc-v/src/common/riscv_exception.c#L89-L142)

```c
// Attach standard exception with suitable handler
void riscv_exception_attach(void) {
// Handle Store/AMO Page Fault (IRQ 15)
irq_attach(RISCV_IRQ_STOREPF, riscv_exception, NULL);
```

Now we see the Store/AMO Page Fault Exception!

```text
up_irqinitialize: c
riscv_dispatch_irq: irq=15
riscv_exception:
EXCEPTION: Store/AMO page fault
MCAUSE: 000000000000000f
EPC: 0000000050207e6a
MTVAL: 00000000e0002100
```

[(Source)](https://gist.github.com/lupyuen/85db0510712ba8c660e10f922d4564c9)

Code Address is 0x50207e6a, from our PLIC Code...

```text
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 is 0xe0002100. Which is our BL808 PLIC!

# Add PLIC to I/O Memory Map

Read the article...

- ["Ox64 BL808 RISC-V SBC: Starting Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox2)

_But is 0xe0002100 accessible?_

Ah we forgot to add it to the I/O Memory Map! Let's fix it: [jh7110_mm_init.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/b244f85065ecc749599842088f35f1b190466429/arch/risc-v/src/jh7110/jh7110_mm_init.c#L47-L50)

```c
/* Map the whole I/O memory with vaddr = paddr mappings */
#define MMU_IO_BASE (0x00000000)
#define MMU_IO_SIZE (0xf0000000)
```

(Doesn't look right, but we'll fix later)

Now NuttX boots further! And tries to register IRQ 57 for the Star64 UART Interrupt...

```text
up_irqinitialize: c
up_irqinitialize: d
up_irqinitialize: e
up_irqinitialize: g
irq_attach: irq=17, isr=0x50207eee
up_enable_irq: irq=17
uart_register: Registering /dev/console
uart_register: Registering /dev/ttyS0
irq_attach: irq=57, isr=0x502041fe
up_enable_irq: irq=57
riscv_dispatch_irq: irq=5
riscv_exception:
EXCEPTION: Load access fault
MCAUSE: 0000000000000005
EPC: 0000000050208342
MTVAL: 00000000e0002104
```

[(Source)](https://gist.github.com/lupyuen/ade5ff1433812fb675ff06f805f7339f)

But it crashes while accessing the PLIC at another address: 0xe0002104.

_Are we tired of PLIC yet?_

Yeah let's fix PLIC later. The entire UART Driver will be revamped anyway, including the UART Interrupt.

Let's disable the UART Interrupt for now: [uart_16550.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/drivers/serial/uart_16550.c#L902-L958)

```c
// Attach the UART Interrupt for Star64
static 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);
```

# Fail to Load Initial RAM Disk

Read the article...

- ["Ox64 BL808 RISC-V SBC: Starting Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox2)

Now NuttX boots even further yay! But crashes in the NuttX Bringup...

```text
up_irqinitialize: c
up_irqinitialize: d
up_irqinitialize: e
up_irqinitialize: g
irq_attach: irq=17, isr=0x50207e64
up_enable_irq: irq=17
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
```

[(Source)](https://gist.github.com/lupyuen/ab640bcb3ba3a19834bcaa29e43baddf)

Because it couldn't map the Initial RAM Disk: [nx_bringup.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/sched/init/nx_bringup.c#L276-L284)

```c
/* Mount the file system containing the init program. */
ret = nx_mount(CONFIG_INIT_MOUNT_SOURCE, CONFIG_INIT_MOUNT_TARGET,
CONFIG_INIT_MOUNT_FSTYPE, CONFIG_INIT_MOUNT_FLAGS,
CONFIG_INIT_MOUNT_DATA);
DEBUGASSERT(ret >= 0);
```

That's because we haven't loaded the Initial RAM Disk! Let's fix this later.

# NuttX Boot Flow for Ox64 BL808

(To see the NuttX Source Code: Right-click the Node and select "Open Link")

```mermaid
flowchart TD
START --> bl808_head["NuttX Boot Code: \n bl808_head \n (Prints 123)"]
click bl808_head href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_head.S#L41-L156" "arch/risc-v/src/bl808/bl808_head.S" _blank

bl808_head --> bl808_start["NuttX Start Code: \n bl808_start"]
click bl808_start href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_start.c#L254-L297" "arch/risc-v/src/bl808/bl808_start.c" _blank

bl808_start --> bl808_start_s["Start Supervisor Mode: \n bl808_start_s \n (Prints ABC)"]
click bl808_start_s href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_start.c#L200-L254" "arch/risc-v/src/bl808/bl808_start.c" _blank

bl808_start_s --> riscv_earlyserialinit["Early Serial Init: \n riscv_earlyserialinit"]
click riscv_earlyserialinit href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_start.c#L297-L314" "arch/risc-v/src/bl808/bl808_start.c" _blank

bl808_start_s --> bl808_mm_init["Memory Mgmt Init: \n bl808_mm_init \n (Map the Memory Mgmt Unit)"]
click bl808_mm_init href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_mm_init.c#L278-L298" "arch/risc-v/src/bl808/bl808_mm_init.c" _blank

bl808_start_s --> nx_start["Start NuttX: \n nx_start \n (Starts many things)"]
click nx_start href "https://github.com/apache/nuttx/blob/master/sched/init/nx_start.c#L298-L713" "sched/init/nx_start.c" _blank

riscv_earlyserialinit --> bl808_earlyserialinit["UART Early Init: \n bl808_earlyserialinit \n (Setup the UART)"]
click bl808_earlyserialinit href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_serial.c#L700-L720" "drivers/serial/uart_16550.c" _blank

nx_start --> up_irqinitialize["IRQ Init: \n up_irqinitialize"]
click up_irqinitialize href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_irq.c#L41-L103" "arch/risc-v/src/bl808/bl808_irq.c#" _blank

nx_start --> nx_bringup["Bringup NuttX: \n nx_bringup"]
click nx_bringup href "https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L379-L468" "sched/init/nx_bringup.c" _blank

up_irqinitialize --> riscv_exception_attach["Attach RISC-V Exceptions: \n riscv_exception_attach \n (Attach the RISC-V Exception Handlers)"]
click riscv_exception_attach href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_exception.c#L89-L142" "arch/risc-v/src/common/riscv_exception.c" _blank

up_irqinitialize --> up_initialize["Init NuttX: \n up_initialize"]
click up_initialize href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_initialize.c#L70-L132" "arch/risc-v/src/common/riscv_initialize.c" _blank

up_initialize --> riscv_serialinit["Serial Init: \n riscv_serialinit"]
click riscv_serialinit href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_start.c#L314-L327" "arch/risc-v/src/bl808/bl808_start.c" _blank

riscv_serialinit --> bl808_serialinit["UART Init: \n bl808_serialinit \n (Register /dev/console)"]
click bl808_serialinit href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_serial.c#L720-L762" "drivers/serial/uart_16550.c" _blank

nx_bringup --> nx_create_initthread["Create Init Thread: \n nx_create_initthread \n (Create AppBringUp thread)"]
click nx_create_initthread href "https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L336-L375" "sched/init/nx_bringup.c" _blank

nx_create_initthread --> nx_start_task["Start NuttX Task: \n nx_start_task"]
click nx_start_task href "https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L310-L336" "sched/init/nx_bringup.c" _blank

nx_start_task --> nx_start_application["Start Application: \n nx_start_application"]
click nx_start_application href "https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L213-L310" "sched/init/nx_bringup.c" _blank

nx_start_application --> board_late_initialize["Board Late Init: board_late_initialize \n (Mount Initial RAM Disk)"]
click board_late_initialize href "https://github.com/apache/nuttx/blob/master/boards/risc-v/bl808/ox64/src/bl808_appinit.c#L134-L167" "boards/risc-v/bl808/ox64/src/bl808_appinit.c" _blank

board_late_initialize --> nx_mount["Mount RAM Disk: nx_mount \n (Fails because Initial RAM Disk is missing)"]
click nx_mount href "https://github.com/apache/nuttx/blob/master/fs/mount/fs_mount.c#L260-L514" "fs/mount/fs_mount.c" _blank
```

Read the article...

- ["Ox64 BL808 RISC-V SBC: Starting Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox2)

_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__...

[__NuttX Boot Code: bl808_head__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_head.S#L41-L156) calls...

- [__NuttX Start Code: bl808_start__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_start.c#L254-L297) which calls...

- [__Start Supervisor Mode: bl808_start_s__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_start.c#L200-L254) which prints "ABC" and calls...

- [__Early Serial Init: riscv_earlyserialinit__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_start.c#L297-L314) (see below) and...

[__Memory Mgmt Init: bl808_mm_init__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_mm_init.c#L278-L298) (to init the Memory Mgmt Unit) and...

[__Start NuttX: nx_start__](https://github.com/apache/nuttx/blob/master/sched/init/nx_start.c#L298-L713) (see below)

[__Early Serial Init: riscv_earlyserialinit__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_start.c#L297-L314) calls...

- [__UART Early Init: bl808_earlyserialinit__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_serial.c#L700-L720)

(To setup the UART)

[__Memory Mgmt Init: bl808_mm_init__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_mm_init.c#L278-L298) inits the [__Memory Mgmt Unit__](https://lupyuen.github.io/articles/mmu) by calling...

- [__MMU Map Region: mmu_ln_map_region__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_mmu.c#L137-L153) (to map a Memory Region) and...

[__MMU Set Entry: mmu_ln_setentry__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_mmu.c#L61C1-L107) (to set a Page Table Entry)

[(More about the __Memory Mgmt Unit__)](https://lupyuen.github.io/articles/mmu)

[__Start NuttX: nx_start__](https://github.com/apache/nuttx/blob/master/sched/init/nx_start.c#L298-L713) does [__many things__](https://lupyuen.github.io/articles/unicorn2#after-primary-routine) and calls...

- [__IRQ Init: up_irqinitialize__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_irq.c#L41C1-L103) (see below) and...

[__Bringup NuttX: nx_bringup__](https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L379-L468) (see below)

[__IRQ Init: up_irqinitialize__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_irq.c#L41-L103) calls...

- [__Attach RISC-V Exceptions: riscv_exception_attach__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_exception.c#L89-L142) (to attach the RISC-V Exception Handlers) and...

[__Init NuttX: up_initialize__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_initialize.c#L70-L132) (see below)

[__Init NuttX: up_initialize__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_initialize.c#L70-L132) calls...

- [__Serial Init: riscv_serialinit__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_start.c#L314-L327) which calls...

- [__UART Init: bl808_serialinit__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/bl808/bl808_serial.c#L720-L762)

(To register "/dev/console")

[__Bringup NuttX: nx_bringup__](https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L379-L468) calls...

- [__Create Init Thread: nx_create_initthread__](https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L336-L375) (to create "AppBringUp" thread) which calls...

- [__Start NuttX Task: nx_start_task__](https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L310-L336) (to start NuttX Task) which calls...

- [__Start Application: nx_start_application__](https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L213-L310) which calls...

- [__Board Late Init: board_late_initialize__](https://github.com/apache/nuttx/blob/master/boards/risc-v/bl808/ox64/src/bl808_appinit.c#L134-L167) (to mount Initial RAM Disk) and...

[__Mount RAM Disk: nx_mount__](https://github.com/apache/nuttx/blob/master/fs/mount/fs_mount.c#L260-L514) (to mount ROM File System from Initial RAM Disk)

(Which fails because our Initial RAM Disk is missing)

(Which prevents NuttX Shell from starting)

[(How __NuttX Shell__ should be started)](https://github.com/lupyuen/nuttx-ox64#start-nuttx-apps-on-ox64-bl808)

[(How __NuttX Apps__ are started)](https://lupyuen.github.io/articles/app)

Therefore we expect NuttX to __boot completely on Ox64__ when we've implemented...

- [__Initial RAM Disk__](https://lupyuen.github.io/articles/ox2#appendix-initial-ram-disk) for Ox64

- [__UART Driver and UART Interrupts__](https://lupyuen.github.io/articles/ox2#appendix-uart-driver-for-ox64)

- [__Memory Map__](https://lupyuen.github.io/articles/ox2#appendix-memory-map-for-ox64) might need fixing too

# NuttX UART Driver for Ox64 BL808

Read the article...

- ["RISC-V Ox64 BL808 SBC: UART Interrupt and Platform-Level Interrupt Controller (PLIC)"](https://lupyuen.github.io/articles/plic2)

BL808 UART is mostly identical to BL602 UART, so we ported the NuttX BL602 UART Driver to BL808.

Here's the UART Driver ported to BL808: [bl602_serial.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/bl602_serial.c)

We hardcoded the UART3 Base Address: [bl602_uart.h](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/hardware/bl602_uart.h#L30-L41)

```c
#define BL602_UART0_BASE 0x30002000
#define BL602_UART_BASE(n) (BL602_UART0_BASE)
// Previously: #define BL602_UART_BASE(n) (BL602_UART0_BASE + (n * (BL602_UART1_BASE - BL602_UART0_BASE)))
```

We fixed the NuttX Start Code to call our new UART Driver: [jh7110_start.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/jh7110_start.c#L175-L184)

```c
void riscv_earlyserialinit(void) {
bl602_earlyserialinit();
}

void riscv_serialinit(void) {
bl602_serialinit();
}
```

We disabled UART Interrupts for now: [bl602_attach and bl602_detach](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/bl602_serial.c#L377-L431)

And the UART Driver works! [(See the log)](https://gist.github.com/lupyuen/74a44a3e432e159c62cc2df6a726cb89)

TODO: /dev/ttyS0 is missing

TODO: Enable UART Interrupts

# Initial RAM Disk for Ox64 BL808

Read the article...

- ["RISC-V Ox64 BL808 SBC: NuttX Apps and Initial RAM Disk"](https://lupyuen.github.io/articles/app)

Two ways we can load the Initial RAM Disk...

1. 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__](https://github.com/openbouffalo/buildroot_bouffalo/blob/main/board/pine64/ox64/boot-pine64.cmd)

And make it [__load the initrd__](https://lupyuen.github.io/articles/semihost#appendix-boot-nuttx-over-tftp-with-initial-ram-disk) file into RAM.

(Which is good for separating the NuttX Kernel and NuttX Apps)

OR...

1. 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__](https://github.com/openbouffalo/buildroot_bouffalo/blob/main/board/pine64/ox64/rootfs-overlay/boot/extlinux/extlinux.conf)

(Which might be more efficient for our Limited RAM)

[(See the __U-Boot Boot Flow__)](https://github.com/openbouffalo/buildroot_bouffalo/wiki/U-Boot-Bootflow)

__TODO:__ Can we mount the File System directly from the __NuttX Kernel Image in RAM__? Without copying to the [__RAM Disk Memory Region__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64/boards/risc-v/jh7110/star64/scripts/ld.script#L26)?

We'll do the Second Method, since we are low on RAM. Like this...

```bash
## Export the Binary Image to `nuttx.bin`
riscv64-unknown-elf-objcopy \
-O binary \
nuttx \
nuttx.bin

## Insert 64 KB of zeroes after Binary Image for Kernel Stack
head -c 65536 /dev/zero >/tmp/nuttx.zero

## Append Initial RAM Disk to Binary Image
cat nuttx.bin /tmp/nuttx.zero initrd \
>Image

## Overwrite the Linux Image on Ox64 microSD
cp Image "/Volumes/NO NAME/"
```

![Initial RAM Disk for Ox64](https://lupyuen.github.io/images/app-initrd.jpg)

This is how we copy the initrd in RAM to the Memory Region for the RAM Disk: [jh7110_start.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/jh7110_start.c#L190-L245)

```c
static void jh7110_copy_ramdisk(void) {
// Based on ROM FS Format: https://docs.kernel.org/filesystems/romfs.html
// After _edata, search for "-rom1fs-". This is the RAM Disk Address.
// Stop searching after 64 KB.
extern uint8_t _edata[];
extern uint8_t _sbss[];
extern uint8_t _ebss[];
const char *header = "-rom1fs-";
uint8_t *ramdisk_addr = NULL;
for (uint8_t *addr = _edata; addr < (uint8_t *)JH7110_IDLESTACK_TOP + (65 * 1024); addr++) {
if (memcmp(addr, header, strlen(header)) == 0) {
ramdisk_addr = addr;
break;
}
}
// Check for Missing RAM Disk
if (ramdisk_addr == NULL) { _info("Missing RAM Disk"); }
DEBUGASSERT(ramdisk_addr != NULL);

// RAM Disk must be after Idle Stack
if (ramdisk_addr <= (uint8_t *)JH7110_IDLESTACK_TOP) { _info("RAM Disk must be after Idle Stack"); }
DEBUGASSERT(ramdisk_addr > (uint8_t *)JH7110_IDLESTACK_TOP);

// Read the Filesystem Size from the next 4 bytes, in Big Endian
// Add 0x1F0 to Filesystem Size
const uint32_t size =
(ramdisk_addr[8] << 24) +
(ramdisk_addr[9] << 16) +
(ramdisk_addr[10] << 8) +
ramdisk_addr[11] +
0x1F0;
_info("size=%d\n", size);

// Filesystem Size must be less than RAM Disk Memory Region
DEBUGASSERT(size <= (size_t)__ramdisk_size);

// Before Copy: Verify the RAM Disk Image to be copied
verify_image(ramdisk_addr);

// Copy the Filesystem Size to RAM Disk Start
// Warning: __ramdisk_start overlaps with ramdisk_addr + size
// memmove is aliased to memcpy, so we implement memmove ourselves
local_memmove((void *)__ramdisk_start, ramdisk_addr, size);

// Before Copy: Verify the copied RAM Disk Image
verify_image(__ramdisk_start);
}
```

We copy the initrd at the very top of our NuttX Start Code, before erasing the BSS (in case it corrupts our RAM Disk, but actually it shouldn't): [jh7110_start.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/jh7110_start.c#L144-L156)

```c
// NuttX Start Code
void jh7110_start(int mhartid) {
DEBUGASSERT(mhartid == 0); /* Only Hart 0 supported for now */
if (0 == mhartid) {
/* Copy the RAM Disk */
jh7110_copy_ramdisk();

/* Clear the BSS */
jh7110_clear_bss();
```

NuttX mounts the RAM Disk from the Memory Region later during startup: [jh7110_appinit.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/boards/risc-v/jh7110/star64/src/jh7110_appinit.c#L51-L87)

```c
// After NuttX has booted...
void board_late_initialize(void) {
// Mount the RAM Disk
mount_ramdisk();
}

// Mount the RAM Disk
int mount_ramdisk(void) {
desc.minor = RAMDISK_DEVICE_MINOR;
desc.nsectors = NSECTORS((ssize_t)__ramdisk_size);
desc.sectsize = SECTORSIZE;
desc.image = __ramdisk_start;
ret = boardctl(BOARDIOC_ROMDISK, (uintptr_t)&desc);
```

And NuttX mounts our RAM Disk successfully!

```text
jh7110_copy_ramdisk: _edata=0x50400258, _sbss=0x50400290, _ebss=0x50407000, JH7110_IDLESTACK_TOP=0x50407c00
jh7110_copy_ramdisk: ramdisk_addr=0x50408288
jh7110_copy_ramdisk: size=8192016
jh7110_copy_ramdisk: Before Copy: ramdisk_addr=0x50408288
jh7110_copy_ramdisk: After Copy: __ramdisk_start=0x50a00000
...
elf_initialize: Registering ELF
uart_register: Registering /dev/console
work_start_lowpri: Starting low-priority kernel worker thread(s)
nx_start_application: Starting init task: /system/bin/init
load_absmodule: Loading /system/bin/init
elf_loadbinary: Loading file: /system/bin/init
elf_init: filename: /system/bin/init loadinfo: 0x5040c618
elf_read: Read 64 bytes from offset 0
```

[(Source)](https://gist.github.com/lupyuen/74a44a3e432e159c62cc2df6a726cb89)

_Why did we insert 64 KB of zeroes after the NuttX Binary Image, before the initrd Initial RAM Disk?_

```bash
## Insert 64 KB of zeroes after Binary Image for Kernel Stack
head -c 65536 /dev/zero >/tmp/nuttx.zero

## Append Initial RAM Disk to Binary Image
cat nuttx.bin /tmp/nuttx.zero initrd \
>Image
```

When we refer to the [NuttX Log](https://gist.github.com/lupyuen/74a44a3e432e159c62cc2df6a726cb89) and the [NuttX Linker Script](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/boards/risc-v/jh7110/star64/scripts/ld.script)...

```text
// End of Data Section
_edata=0x50400258

// Start of BSS Section
_sbss=0x50400290

// End of BSS Section
_ebss=0x50407000

// Top of Idle Stack
JH7110_IDLESTACK_TOP=0x50407c00

// We located the initd after the Top of Idle Stack
ramdisk_addr=0x50408288, size=8192016

// And we copied initrd to the Memory Region for the RAM Disk
__ramdisk_start=0x50a00000
```

Which says...

1. The NuttX Binary Image `nuttx.bin` terminates at `_edata`. (End of Data Section)

1. If we append `initrd` directly to the end of `nuttx.bin`, it will collide with the [BSS Section](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/jh7110_start.c#L74-L92) and the [Idle Stack](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/jh7110_head.S#L94-L101). And `initrd` will get overwritten by NuttX.

1. Best place to append `initrd` is after the Top of Idle Stack. Which is located 32 KB after `_edata`. (End of Data Section)

1. That's why we inserted a padding of 64 KB between `nuttx.bin` and `initrd`. So it won't collide with BSS and Idle Stack.

1. Our code locates `initrd` (searching by Magic Number "-rom1fs-"). And copies `initrd` to `__ramdisk_start`. (Memory Region for the RAM Disk)

1. NuttX mounts the RAM Disk from `__ramdisk_start`. (Memory Region for the RAM Disk)

_But 64 KB sounds so arbitrary. What if the parameters change?_

That's why we have a Runtime Check: [jh7110_start.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/jh7110_start.c#L190-L245)

```c
// RAM Disk must be after Idle Stack
if (ramdisk_addr <= (uint8_t *)JH7110_IDLESTACK_TOP) { _info("RAM Disk must be after Idle Stack"); }
DEBUGASSERT(ramdisk_addr > (uint8_t *)JH7110_IDLESTACK_TOP);
```

_Why did we call local_memmove to copy `initrd` to `__ramdisk_start`? Why not memcpy?_

That's because `initrd` overlaps with `__ramdisk_start`!

```
ramdisk_addr = 0x50408288, size = 8192016
ramdisk_addr + size = 0x50bd8298
Which is AFTER __ramdisk_start (0x50a00000)
```

`memcpy` won't work with Overlapping Memory Regions. So we wrote our own: [jh7110_start.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/jh7110_start.c#L246-L487)

```c
// From libs/libc/string/lib_memmove.c
static FAR void *local_memmove(FAR void *dest, FAR const void *src, size_t count) {
FAR char *d;
FAR char *s;
DEBUGASSERT(dest > src);
d = (FAR char *) dest + count;
s = (FAR char *) src + count;

while (count--) {
d -= 1;
s -= 1;
// TODO: Very strange. This needs to be volatile or C Compiler will replace this by memcpy.
volatile char c = *s;
*d = c;
}
return dest;
}
```

_We're sure that it works?_

That's why we called `verify_image` to do a simple integrity check on `initrd`, before and after copying. And that's how we discovered that `memcpy` doesn't work. From [jh7110_start.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/jh7110_start.c#L246-L487)

```c
// Verify that image is correct
static void verify_image(uint8_t *addr) {
// Verify that the Byte Positions below (offset by 1) contain 0x0A
for (int i = 0; i < sizeof(search_addr) / sizeof(search_addr[0]); i++) {
const uint8_t *p = addr + search_addr[i] - 1;
if (*p != 0x0A) { _info("No Match: %p\n", p); }
}
}

// Byte Positions (offset by 1) of 0x0A in initrd. Extracted from:
// grep --binary-files=text -b -o A initrd
const uint32_t search_addr[] =
{
76654,
78005,
79250,
...
7988897,
7992714,
};
```

But NuttX fails to start our NuttX Shell (NSH) ELF Executable from "/system/bin/init"...

```text
elf_read: Read 3392 bytes from offset 3385080
elf_addrenv_select: ERROR: up_addrenv_text_enable_write failed: -22
elf_load: ERROR: elf_addrenv_select() failed: -22
...
elf_loadbinary: Failed to load ELF program binary: -22
exec_internal: ERROR: Failed to load program '/system/bin/init': -22
_assert: Current Version: NuttX 12.0.3 8017bd9-dirty Nov 10 2023 22:50:07 risc-v
_assert: Assertion failed ret > 0: at file: init/nx_bringup.c:302 task: AppBringUp process: Kernel 0x502014ea
```

[(Source)](https://gist.github.com/lupyuen/74a44a3e432e159c62cc2df6a726cb89)

Maybe because NuttX is trying to map the User Address Space 0xC000 0000: [nsh/defconfig](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/boards/risc-v/jh7110/star64/configs/nsh/defconfig#L17-L26)

```text
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? (Because of the PLIC at 0xE000 0000)

From [jh7110_mm_init.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/jh7110_mm_init.c#L43-L46):

```c
/* Map the whole I/O memory with vaddr = paddr mappings */
#define MMU_IO_BASE (0x00000000)
#define MMU_IO_SIZE (0xf0000000)
```

_Let's disable PLIC, and exclude PLIC from Memory Map. Will the NuttX Shell start?_

Yep it does! [(See the log)](https://gist.github.com/lupyuen/9fc9b2de9938b48666cc5e5fa3f8278e)

Now we fix the Memory Map...

# NuttX Memory Map for Ox64 BL808

Read the article...

- ["RISC-V Ox64 BL808 SBC: Sv39 Memory Management Unit"](https://lupyuen.github.io/articles/mmu)

To fix the NuttX Memory Map for Ox64, let's trace the MMU Page Table Entries. From the [MMU Log](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

## Map the I/O Region (Level 1)

From the [MMU Log](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

```text
mmu_ln_map_region:
ptlevel=1, lnvaddr=0x50407000, paddr=0, vaddr=0, size=0x40000000, mmuflags=0x26

mmu_ln_setentry:
ptlevel=1, lnvaddr=0x50407000, paddr=0, vaddr=0, mmuflags=0x26
mmu_ln_setentry:
index=0, paddr=0, mmuflags=0xe7, pte_addr=0x50407000, pte_val=0xe7
```

`mmuflags=0x26` means Read + Write + Global

![Level 1 Page Table for Kernel](https://lupyuen.github.io/images/mmu-l1kernel2.jpg)

## Map the PLIC (Level 2)

From the [MMU Log](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

```text
mmu_ln_map_region:
ptlevel=2, lnvaddr=0x50403000, paddr=0xe0000000, vaddr=0xe0000000, size=0x10000000, mmuflags=0x26

mmu_ln_setentry: ptlevel=2, lnvaddr=0x50403000, paddr=0xe0000000, vaddr=0xe0000000, mmuflags=0x26
mmu_ln_setentry: index=0x100, paddr=0xe0000000, mmuflags=0xe7, pte_addr=0x50403800, pte_val=0x380000e7
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50403000, paddr=0xe0200000, vaddr=0xe0200000, mmuflags=0x26
mmu_ln_setentry: index=0x101, paddr=0xe0200000, mmuflags=0xe7, pte_addr=0x50403808, pte_val=0x380800e7
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50403000, paddr=0xe0400000, vaddr=0xe0400000, mmuflags=0x26
mmu_ln_setentry: index=0x102, paddr=0xe0400000, mmuflags=0xe7, pte_addr=0x50403810, pte_val=0x381000e7
...
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50403000, paddr=0xefe00000, vaddr=0xefe00000, mmuflags=0x26
mmu_ln_setentry: index=0x17f, paddr=0xefe00000, mmuflags=0xe7, pte_addr=0x50403bf8, pte_val=0x3bf800e7
```

`mmuflags=0x26` means Read + Write + Global

![Level 2 Page Table for Interrupt Controller](https://lupyuen.github.io/images/mmu-l2int.jpg)

## Connect the Level 1 and Level 2 Page Tables for PLIC

From the [MMU Log](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

```text
mmu_ln_setentry:
ptlevel=1, lnvaddr=0x50407000, paddr=0x50403000, vaddr=0xe0000000, mmuflags=0x20

mmu_ln_setentry:
index=0x3, paddr=0x50403000, mmuflags=0x21, pte_addr=0x50407018, pte_val=0x14100c21
```

`mmuflags=0x20` means PTE_G: Global Mapping.

![Level 1 Page Table for Kernel](https://lupyuen.github.io/images/mmu-l1kernel.jpg)

## Map the Kernel Text (Levels 2 & 3)

From the [MMU Log](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

```text
// Level 2
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50406000, paddr=0x50404000, vaddr=0x50200000, mmuflags=0x0
mmu_ln_setentry: index=0x81, paddr=0x50404000, mmuflags=0x1, pte_addr=0x50406408, pte_val=0x14101001

// Level 3
mmu_ln_setentry: ptlevel=3, lnvaddr=0x50404000, paddr=0x50200000, vaddr=0x50200000, mmuflags=0x2a
mmu_ln_setentry: index=0, paddr=0x50200000, mmuflags=0xeb, pte_addr=0x50404000, pte_val=0x140800eb

mmu_ln_setentry: ptlevel=3, lnvaddr=0x50404000, paddr=0x5020100, vaddr=0x50201000, mmuflags=0x2a
mmu_ln_setentry: index=0x1, paddr=0x50201000, mmuflags=0xeb, pte_addr=0x50404008, pte_val=0x140804eb

mmu_ln_setentry: ptlevel=3, lnvaddr=0x50404000, paddr=0x50202000, vaddr=0x50202000, mmuflags=0x2a
mmu_ln_setentry: index=0x2, paddr=0x50202000, mmuflags=0xeb, pte_addr=0x50404010, pte_val=0x140808eb
...
mmu_ln_setentry: ptlevel=3, lnvaddr=0x50404000, paddr=0x503ff000, vaddr=0x503ff000, mmuflags=0x2a
mmu_ln_setentry: index=0x1ff, paddr=0x503ff000, mmuflags=0xeb, pte_addr=0x50404ff8, pte_val=0x140ffceb
```

`mmuflags=0x0` means PTE is a pointer to the next level of the page table

`mmuflags=0x2a` means Read + Execute + Global

![Level 3 Page Table for Kernel](https://lupyuen.github.io/images/mmu-l3kernel.jpg)

## Map the Kernel Data (Levels 2 & 3)

From the [MMU Log](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

```text
// Level 2
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50406000, paddr=0x50405000, vaddr=0x50400000, mmuflags=0x0
mmu_ln_setentry: index=0x82, paddr=0x50405000, mmuflags=0x1, pte_addr=0x50406410, pte_val=0x14101401

// Level 3
mmu_ln_setentry: ptlevel=3, lnvaddr=0x50405000, paddr=0x50400000, vaddr=0x5040000, mmuflags=0x26
mmu_ln_setentry: index=0, paddr=0x50400000, mmuflags=0xe7, pte_addr=0x50405000, pte_val=0x141000e7
mmu_ln_setentry: ptlevel=3, lnvaddr=0x50405000, paddr=0x50401000, vaddr=0x50401000, mmuflags=0x26
mmu_ln_setentry: index=0x1, paddr=0x50401000, mmuflags=0xe7, pte_addr=0x50405008, pte_val=0x141004e7
mmu_ln_setentry: ptlevel=3, lnvaddr=0x50405000, paddr=0x50402000, vaddr=0x50402000, mmuflags=0x26
mmu_ln_setentry: index=0x2, paddr=0x50402000, mmuflags=0xe7, pte_addr=0x50405010, pte_val=0x141008e7
...
mmu_ln_setentry: ptlevel=3, lnvaddr=0x50405000, paddr=0x505ff000, vaddr=0x505ff000, mmuflags=0x26
mmu_ln_setentry: index=0x1ff, paddr=0x505ff000, mmuflags=0xe7, pte_addr=0x50405ff8, pte_val=0x1417fce7
```

`mmuflags=0x0` means PTE is a pointer to the next level of the page table

`mmuflags=0x26` means Read + Write + Global

## Connect the Level 1 and Level 2 Page Tables

From the [MMU Log](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

```text
mmu_ln_setentry:
ptlevel=1, lnvaddr=0x50407000, paddr=0x50406000, vaddr=0x50200000, mmuflags=0x20

mmu_ln_setentry:
index=0x1, paddr=0x50406000, mmuflags=0x21, pte_addr=0x50407008, pte_val=0x14101821
```

`mmuflags=0x20` means PTE_G: Global Mapping.

And PTE is a pointer to the next level of the page table.

Which means that Virtual Address 0x5020 0000 points to the L2 Page Table 0x5040 6000

## Map the Page Pool (Level 2)

From the [MMU Log](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

```text
mmu_ln_map_region: ptlevel=2, lnvaddr=0x50406000, paddr=0x50600000, vaddr=0x50600000, size=0x1400000, mmuflags=0x26

mmu_ln_setentry: ptlevel=2, lnvaddr=0x50406000, paddr=0x50600000, vaddr=0x50600000, mmuflags=0x26
mmu_ln_setentry: index=0x83, paddr=0x50600000, mmuflags=0xe7, pte_addr=0x50406418, pte_val=0x141800e7

mmu_ln_setentry: ptlevel=2, lnvaddr=0x50406000, paddr=0x50800000, vaddr=0x50800000, mmuflags=0x26
mmu_ln_setentry: index=0x84, paddr=0x50800000, mmuflags=0xe7, pte_addr=0x50406420, pte_val=0x142000e7

mmu_ln_setentry: ptlevel=2, lnvaddr=0x50406000, paddr=0x50a00000, vaddr=0x50a00000, mmuflags=0x26
mmu_ln_setentry: index=0x85, paddr=0x50a00000, mmuflags=0xe7, pte_addr=0x50406428, pte_val=0x142800e7
...
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50406000, paddr=0x51800000, vaddr=0x51800000, mmuflags=0x26
mmu_ln_setentry: index=0x8c, paddr=0x51800000, mmuflags=0xe7, pte_addr=0x50406460, pte_val=0x146000e7
```

`mmuflags=0x26` means Read + Write + Global

![Level 2 Page Table for Kernel](https://lupyuen.github.io/images/mmu-l2kernel.jpg)

## Map the User Code, Data and Heap (Levels 1, 2, 3)

From the [MMU Log](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

```text
nx_start_application: Starting init task: /system/bin/init
// Level 1 (Code, Data, Heap)
// `mmuflags=0x0` means it's a link to a PTE
// `vaddr=0x80100000` will be aligned to 0x80000000
mmu_ln_setentry: ptlevel=1, lnvaddr=0x50600000, paddr=0x50601000, vaddr=0x80100000, mmuflags=0x0
mmu_ln_setentry: index=0x2, paddr=0x50601000, mmuflags=0x1, pte_addr=0x50600010, pte_val=0x14180401

// Level 2 (Code, Data)
// `mmuflags=0x0` means it's a link to a PTE
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50601000, paddr=0x50602000, vaddr=0x80100000, mmuflags=0x0
mmu_ln_setentry: index=0, paddr=0x50602000, mmuflags=0x1, pte_addr=0x50601000, pte_val=0x14180801

// Level 3 (Data)
// `mmuflags=0x16` means Read + Write + User
mmu_ln_setentry: ptlevel=3, lnvaddr=0x50602000, paddr=0x50603000, vaddr=0x80100000, mmuflags=0x16
mmu_ln_setentry: index=0x100, paddr=0x50603000, mmuflags=0xd7, pte_addr=0x50602800, pte_val=0x14180cd7

// Level 3 (Code)
// `mmuflags=0x1a` means Read + Execute + User
mmu_ln_setentry: ptlevel=3, lnvaddr=0x50602000, paddr=0x50604000, vaddr=0x80000000, mmuflags=0x1a
mmu_ln_setentry: index=0, paddr=0x50604000, mmuflags=0xdb, pte_addr=0x50602000, pte_val=0x141810db

mmu_ln_setentry: ptlevel=3, lnvaddr=0x50602000, paddr=0x50605000, vaddr=0x80001000, mmuflags=0x1a
mmu_ln_setentry: index=0x1, paddr=0x50605000, mmuflags=0xdb, pte_addr=0x50602008, pte_val=0x141814db
...
mmu_ln_setentry: ptlevel=3, lnvaddr=0x50602000, paddr=0x50619000, vaddr=0x80015000, mmuflags=0x1a
mmu_ln_setentry: index=0x15, paddr=0x50619000, mmuflags=0xdb, pte_addr=0x506020a8, pte_val=0x141864db

// Level 3 (Data)
// `mmuflags=0x16` means Read + Write + User
mmu_ln_setentry: ptlevel=3, lnvaddr=0x50602000, paddr=0x5061a000, vaddr=0x80101000, mmuflags=0x16
mmu_ln_setentry: index=0x101, paddr=0x5061a000, mmuflags=0xd7, pte_addr=0x50602808, pte_val=0x141868d7

// Level 2 (Heap)
// `mmuflags=0x0` means it's a link to a PTE
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50601000, paddr=0x5061b000, vaddr=0x80200000, mmuflags=0x0
mmu_ln_setentry: index=0x1, paddr=0x5061b000, mmuflags=0x1, pte_addr=0x50601008, pte_val=0x14186c01

// Level 3 (Heap)
// `mmuflags=0x16` means Read + Write + User
mmu_ln_setentry: ptlevel=3, lnvaddr=0x5061b000, paddr=0x5061c000, vaddr=0x80200000, mmuflags=0x16
mmu_ln_setentry: index=0, paddr=0x5061c000, mmuflags=0xd7, pte_addr=0x5061b000, pte_val=0x141870d7

mmu_ln_setentry: ptlevel=3, lnvaddr=0x5061b000, paddr=0x5061d000, vaddr=0x80201000, mmuflags=0x16
mmu_ln_setentry: index=0x1, paddr=0x5061d000, mmuflags=0xd7, pte_addr=0x5061b008, pte_val=0x141874d7
...
mmu_ln_setentry: ptlevel=3, lnvaddr=0x5061b000, paddr=0x5069c000, vaddr=0x80280000, mmuflags=0x16
mmu_ln_setentry: index=0x80, paddr=0x5069c000, mmuflags=0xd7, pte_addr=0x5061b400, pte_val=0x141a70d7
```

![Level 1 Page Table for User](https://lupyuen.github.io/images/mmu-l1user.jpg)

![Level 2 Page Table for User](https://lupyuen.github.io/images/mmu-l2user.jpg)

![Level 3 Page Table for User](https://lupyuen.github.io/images/mmu-l3user.jpg)

## Page Tables

_What are the 3 Levels of Page Tables?_

From the [MMU Log](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

lnvaddr=0x50407000 is m_l1_pgtable (Level 1 Page Table for Kernel)

lnvaddr=0x50406000 is m_l2_pgtable (Level 2 Page Table for Kernel)

lnvaddr=0x50404000 is m_l3_pgtable (Level 3 Page Table for Kernel)

lnvaddr=0x50403000 is m_int_l3_pgtable (Level 3 Page Table for Kernel PLIC)

lnvaddr=0x5061b000 is User L1 Page Table for Code, Data, Heap

lnvaddr=0x50601000 is User L2 Page Table for Code, Data

lnvaddr=0x50602000 is User L3 Page Table for Code, Data

lnvaddr=0x50601000 is User L2 Page Table for Heap

lnvaddr=0x5061b000 is User L3 Page Table for Heap

# Fix the NuttX Memory Map for PLIC

Read the article...

- ["RISC-V Ox64 BL808 SBC: Sv39 Memory Management Unit"](https://lupyuen.github.io/articles/mmu)

_Can we add L1 for 0xE000 0000 to access PLIC?_

[Nope it fails](https://gist.github.com/lupyuen/73906723edf5f1611c7829779b18668a). Because 0xE000 0000 is not aligned to 0x4000 0000 for L1.

But when we add L1 for 0xC000 0000, it works!

```c
// Map PLIC
mmu_ln_map_region(1, PGT_L1_VBASE, 0xC0000000, 0xC0000000,
0x40000000, MMU_IO_FLAGS);
```

But NuttX Apps need to access 0xC000 0000. So NuttX Apps will fail...

```text
nx_start_application: Starting init task: /system/bin/init
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50601000, paddr=0x50602000, vaddr=0xc0100000, mmuflags=0x0
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50601000, paddr=0x5061b000, vaddr=0xc0200000, mmuflags=0x0
elf_addrenv_select: ERROR: up_addrenv_text_enable_write failed: -22
elf_load: ERROR: elf_addrenv_select() failed: -22
elf_loadbinary: Failed to load ELF program binary: -22
exec_internal: ERROR: Failed to load program '/system/bin/init': -22
_assert: Current Version: NuttX 12.0.3 47ec850-dirty Nov 11 2023 20:24:22 risc-v
_assert: Assertion failed ret > 0: at file: init/nx_bringup.c:302 task: AppBringUp process: Kernel 0x502014b0
```

[(Source)](https://gist.github.com/lupyuen/124aa56c263e51d9306e0d70321f2864)

_What if we map PLIC as L2 at 0xE000 0000? By reusing the existing L2 Page Tables?_

```c
// Map PLIC as L2, by reusing the existing L2 Page Tables
map_region(0xE0000000, 0xE0000000, 0x10000000, MMU_IO_FLAGS);
```

This fails with IRQ 5. The existing L2 Page Tables can't accommodate PLIC at 0xE000 0000.

_What if we add L1 for 0xC000 0000? And move apps to 0x8000 0000?_

From [jh7110_mm_init.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/jh7110_mm_init.c#L238-L246):

```c
// Map PLIC as L1
// This will waste a whole chunk of L1 Addresses (Size 0x4000 0000) just for PLIC:
mmu_ln_map_region(1, PGT_L1_VBASE, 0xC0000000, 0xC0000000,
0x40000000, MMU_IO_FLAGS);
```

NSH starts OK! Though we wasted a whole chunk of L1 Addresses (Size 0x4000 0000) just for PLIC...

```text
jh7110_kernel_mappings: map PLIC as L1
mmu_ln_map_region: ptlevel=1, lnvaddr=0x50406000, paddr=0xc0000000, vaddr=0xc0000000, size=0x40000000, mmuflags=0x26
mmu_ln_setentry: ptlevel=1, lnvaddr=0x50406000, paddr=0xc0000000, vaddr=0xc0000000, mmuflags=0x26
...
uart_register: Registering /dev/console
work_start_lowpri: Starting low-priority kernel worker thread(s)
nx_start_application: Starting init task: /system/bin/init
mmu_ln_setentry: ptlevel=1, lnvaddr=0x50600000, paddr=0x50601000, vaddr=0x80100000, mmuflags=0x0
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50601000, paddr=0x50602000, vaddr=0x80100000, mmuflags=0x0
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50601000, paddr=0x5061b000, vaddr=0x80200000, mmuflags=0x0
elf_symname: Symbol has no name
elf_symvalue: SHN_UNDEF: Failed to get symbol name: -3
elf_relocateadd: Section 2 reloc 2: Undefined symbol[0] has no name: -3
up_exit: TCB=0x50409900 exiting
riscv_dispatch_irq: irq=8

NuttShell (NSH) NuttX-12.0.3
riscv_dispatch_irq: irq=8
nsh> riscv_dispatch_irq: irq=8
riscv_dispatch_irq: irq=8
nx_start: CPU0: Beginning Idle Loop
```

[(Source)](https://gist.github.com/lupyuen/0f4bd7efc4d2d2839eba5ad62349af35)

_What if we map PLIC as a new L2 Page Table: Interrupt L2?_

From [jh7110_mm_init.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64a/arch/risc-v/src/jh7110/jh7110_mm_init.c#L250-L257):

```c
// Interrupt L2 Table Table
#define PGT_INT_L2_PBASE (uintptr_t)&m_int_l2_pgtable
#define PGT_INT_L2_VBASE PGT_INT_L2_PBASE
#define PGT_INT_L2_SIZE (512) /* Enough to map 1 GiB */
static size_t m_int_l2_pgtable[PGT_INT_L2_SIZE] locate_data(".pgtables");

// Map PLIC as Interrupt L2 Table Table
mmu_ln_map_region(2, PGT_INT_L2_PBASE, 0xE0000000, 0xE0000000, 0x10000000,
MMU_IO_FLAGS);

// Connect the L1 and Interrupt L2 Page Tables for PLIC
mmu_ln_setentry(1, PGT_L1_VBASE, PGT_INT_L2_PBASE, 0xE0000000, PTE_G);
```

NSH starts OK yay!

```text
jh7110_kernel_mappings: map PLIC as Interrupt L2
mmu_ln_map_region: ptlevel=2, lnvaddr=0x50403000, paddr=0xe0000000, vaddr=0xe0000000, size=0x10000000, mmuflags=0x26
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50403000, paddr=0xe0000000, vaddr=0xe0000000, mmuflags=0x26
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50403000, paddr=0xe0200000, vaddr=0xe0200000, mmuflags=0x26
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50403000, paddr=0xe0400000, vaddr=0xe0400000, mmuflags=0x26
...

mmu_ln_setentry: ptlevel=2, lnvaddr=0x50403000, paddr=0xefa00000, vaddr=0xefa00000, mmuflags=0x26
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50403000, paddr=0xefc00000, vaddr=0xefc00000, mmuflags=0x26
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50403000, paddr=0xefe00000, vaddr=0xefe00000, mmuflags=0x26
jh7110_kernel_mappings: connect the L1 and Interrupt L2 page tables for PLIC
mmu_ln_setentry: ptlevel=1, lnvaddr=0x50407000, paddr=0x50403000, vaddr=0xe0000000, mmuflags=0x20
...
uart_register: Registering /dev/console
work_start_lowpri: Starting low-priority kernel worker thread(s)
nx_start_application: Starting init task: /system/bin/init
mmu_ln_setentry: ptlevel=1, lnvaddr=0x50600000, paddr=0x50601000, vaddr=0x80100000, mmuflags=0x0
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50601000, paddr=0x50602000, vaddr=0x80100000, mmuflags=0x0
mmu_ln_setentry: ptlevel=2, lnvaddr=0x50601000, paddr=0x5061b000, vaddr=0x80200000, mmuflags=0x0
elf_symname: Symbol has no name
elf_symvalue: SHN_UNDEF: Failed to get symbol name: -3
elf_relocateadd: Section 2 reloc 2: Undefined symbol[0] has no name: -3
up_exit: TCB=0x5040a900 exiting
riscv_dispatch_irq: irq=8

NuttShell (NSH) NuttX-12.0.3
riscv_dispatch_irq: irq=8
nsh> riscv_dispatch_irq: irq=8
riscv_dispatch_irq: irq=8
nx_start: CPU0: Beginning Idle Loop
```

[(Source)](https://gist.github.com/lupyuen/74c8cb0e519984f6392384f6cca3daff)

TODO: Who maps the User Memory for `lnvaddr=0x50600000`?

![Ox64 boots to NuttX Shell](https://lupyuen.github.io/images/mmu-boot1.png)

[_Ox64 boots to NuttX Shell_](https://gist.github.com/lupyuen/aa9b3e575ba4e0c233ab02c328221525)

__Compute Level 1 PTE:__

Based the [Updated MMU Log with PTE](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

```text
connect the L1 and Interrupt L2 page tables for PLIC
mmu_ln_setentry:
ptlevel=1, lnvaddr=0x50407000, paddr=0x50403000, vaddr=0xe0000000, mmuflags=0x20
mmu_ln_setentry:
index=0x3,
paddr=0x50403000,
mmuflags=0x21,
pte_addr=0x50407018,
pte_val=0x14100c21
```

To compute the Address of Level 1 PTE:

- pte_addr = lnvaddr + (index * 8) = 0x50407018

(8 bytes per PTE)

- index = vpn >> 18 = 3

(Extract Bits 18 to 26 to get Level 1 Index)

- vpn = vaddr >> 12 = 0xe0000

(4096 bytes per Memory Page)

To compute the Value of Level 1 PTE:

- pte_val = (ppn << 10) | mmuflags = 0x14100c21

(Shift 10 bits to accommodate MMU Flags)

- ppn = paddr >> 12 = 0x50403

(1<<12 bits per Memory Page)

mmuflags=0x21 means 0b100001:

- NOT Dirty

- NOT Accessed

- Global Mapping

- NOT User-Accessible

- NOT eXecutable

- NOT Writeable

- NOT Readable

- Valid

__Compute Level 2 PTE:__

Based the [Updated MMU Log with PTE](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

```text
map PLIC as Interrupt L2
mmu_ln_map_region:
ptlevel=2, lnvaddr=0x50403000, paddr=0xe0000000, vaddr=0xe0000000, size=0x10000000, mmuflags=0x26
mmu_ln_setentry:
ptlevel=2, lnvaddr=0x50403000, paddr=0xe0000000, vaddr=0xe0000000, mmuflags=0x26
mmu_ln_setentry:
index=0x100,
paddr=0xe0000000,
mmuflags=0xe7,
pte_addr=0x50403800,
pte_val=0x380000e7
```

To compute the Address of Level 2 PTE:

- pte_addr = lnvaddr + (index * 8) = 0x50403800

(8 bytes per PTE)

- index = (vpn >> 9) & 0b111111111 = 0x100

(Extract Bits 9 to 17 to get Level 2 Index)

- vpn = vaddr >> 12 = 0xe0000

(4096 bytes per Memory Page)

To compute the Value of Level 2 PTE:

- pte_val = (ppn << 10) | mmuflags = 0x380000e7

(Shift 10 bits to accommodate MMU Flags)

- ppn = paddr >> 12 = 0xe0000

(4096 bytes per Memory Page)

mmuflags=0xe7 means 0b11100111:

- Dirty

- Accessed

- Global Mapping

- NOT User-Accessible

- NOT eXecutable

- Writeable

- Readable

- Valid

__Compute Level 3 PTE:__

Based the [Updated MMU Log with PTE](https://gist.github.com/lupyuen/22712d6a2c3a7eb2da1f3cd5c2f4f6cf)...

```text
map kernel text
mmu_ln_setentry:
ptlevel=2, lnvaddr=0x50406000, paddr=0x50404000, vaddr=0x50200000, mmuflags=0x0
mmu_ln_setentry:
index=0x81, paddr=0x50404000, mmuflags=0x1, pte_addr=0x50406408, pte_val=0x14101001
...
mmu_ln_setentry: ptlevel=3, lnvaddr=0x50404000, paddr=0x5020100, vaddr=0x50201000, mmuflags=0x2a
mmu_ln_setentry:
index=0x1,
paddr=0x50201000,
mmuflags=0xeb,
pte_addr=0x50404008,
pte_val=0x140804eb
```

To compute the Address of Level 3 PTE:

- pte_addr = lnvaddr + (index * 8) = 0x50404008

(8 bytes per PTE)

- index = vpn & 0b111111111 = 0x1

(Extract Bits 0 to 8 to get Level 3 Index)

- vpn = vaddr >> 12 = 0x50201

(4096 bytes per Memory Page)

To compute the Value of Level 3 PTE:

- pte_val = (ppn << 10) | mmuflags = 0x140804eb

(Shift 10 bits to accommodate MMU Flags)

- ppn = paddr >> 12 = 0x50201

(4096 bytes per Memory Page)

mmuflags=0xeb means 0b11101011:

- Dirty

- Accessed

- Global Mapping

- NOT User-Accessible

- eXecutable

- NOT Writeable

- Readable

- Valid

TODO: [G Bit](https://five-embeddev.com/riscv-isa-manual/latest/supervisor.html#sec:translation)

> "The G bit designates a global mapping. Global mappings are those that exist in all address spaces. For non-leaf PTEs, the global setting implies that all mappings in the subsequent levels of the page table are global. Note that failing to mark a global mapping as global merely reduces performance, whereas marking a non-global mapping as global is a software bug that, after switching to an address space with a different non-global mapping for that address range, can unpredictably result in either mapping being used."

TODO: [SATP Log](https://gist.github.com/lupyuen/aa9b3e575ba4e0c233ab02c328221525)

![NuttX swaps the SATP Register](https://lupyuen.github.io/images/mmu-boot2.jpg)

[_NuttX swaps the SATP Register_](https://gist.github.com/lupyuen/aa9b3e575ba4e0c233ab02c328221525)

# Start NuttX Apps on Ox64 BL808

Read the article...

- ["RISC-V Ox64 BL808 SBC: NuttX Apps and Initial RAM Disk"](https://lupyuen.github.io/articles/app)

NuttX Kernel starts a NuttX App (in ELF Format) by calling...

- [__ELF Loader: g_elfbinfmt__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/binfmt/elf.c#L84-L94), which calls...

- [__elf_loadbinary__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/binfmt/elf.c#L225-L355), which calls...

- [__elf_load__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/binfmt/libelf/libelf_load.c#L297-L445), which calls...

- [__elf_addrenv_alloc__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/binfmt/libelf/libelf_addrenv.c#L56-L178), which calls...

- [__up_addrenv_create__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/common/riscv_addrenv.c#L339-L490), which calls...

(Also calls [__mmu_satp_reg__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/common/riscv_mmu.h#L152-L176) to set SATP Register)

- [__create_region__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/common/riscv_addrenv.c#L213-L310), which calls...

- [__mmu_ln_setentry__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/common/riscv_mmu.c#L62-L109) to populate the Page Table Entries

_Who calls [ELF Loader g_elfbinfmt](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/binfmt/elf.c#L84-L94) to start the NuttX App?_

Earlier we stepped through the __Boot Sequence__ for NuttX...

- [__"NuttX Boot Flow"__](https://lupyuen.github.io/articles/ox2#appendix-nuttx-boot-flow)

Right after that, [__nx_bringup__](https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L373-L458) calls...

- [__nx_create_initthread__](https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L330-L367), which calls...

- [__nx_start_application__](https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L212C1-L302), which calls...

- [__exec_spawn__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_exec.c#L183-L223), which calls...

- [__exec_internal__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_exec.c#L42-L179), which calls...

- [__load_module__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_loadmodule.c#L136-L225) and...

[__exec_module__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_execmodule.c#L190-L450)

[__load_module__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_loadmodule.c#L136-L225) calls...

- [__load_absmodule__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_loadmodule.c#L83-L132), which calls...

- [__binfmt_s.load__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/master/include/nuttx/binfmt/binfmt.h#L122-L148), which calls...

- [__ELF Loader: g_elfbinfmt__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/binfmt/elf.c#L84-L94) to load the ELF File (explained above)

# Simple NuttX App for Ox64 BL808

Read the article...

- ["RISC-V Ox64 BL808 SBC: NuttX Apps and Initial RAM Disk"](https://lupyuen.github.io/articles/app)

_What's inside the simplest app for NuttX?_

From [hello_main.c](https://github.com/lupyuen2/wip-pinephone-nuttx-apps/blob/ox64b/examples/hello/hello_main.c#L36-L40)

```c
int main(int argc, FAR char *argv[]) {
printf("Hello, World!!\n");
return 0;
}
```

Here's the RISC-V Disassembly: [hello.S](https://github.com/lupyuen2/wip-pinephone-nuttx/releases/download/ox64a-1/hello.S)

```text
000000000000003e :
main():
apps/examples/hello/hello_main.c:37
/****************************************************************************
* hello_main
****************************************************************************/

int main(int argc, FAR char *argv[])
{
3e: 1141 addi sp,sp,-16
apps/examples/hello/hello_main.c:38
printf("Hello, World!!\n");
40: 00000517 auipc a0,0x0 40: R_RISCV_PCREL_HI20 .LC0
40: R_RISCV_RELAX *ABS*
44: 00050513 mv a0,a0 44: R_RISCV_PCREL_LO12_I .L0
44: R_RISCV_RELAX *ABS*

0000000000000048 <.LVL1>:
apps/examples/hello/hello_main.c:37
{
48: e406 sd ra,8(sp)
apps/examples/hello/hello_main.c:38
printf("Hello, World!!\n");
4a: 00000097 auipc ra,0x0 4a: R_RISCV_CALL puts
4a: R_RISCV_RELAX *ABS*
4e: 000080e7 jalr ra # 4a <.LVL1+0x2>

0000000000000052 <.LVL2>:
apps/examples/hello/hello_main.c:40
return 0;
}
52: 60a2 ld ra,8(sp)
54: 4501 li a0,0
56: 0141 addi sp,sp,16
58: 8082 ret
```

We see that [main](https://github.com/lupyuen2/wip-pinephone-nuttx-apps/blob/ox64b/examples/hello/hello_main.c#L36-L40) calls...

- [puts](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/libs/libc/stdio/lib_puts.c#L34-L96), which calls...

- [lib_fwrite_unlocked](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/libs/libc/stdio/lib_libfwrite.c#L45-L200), which calls...

- [stream->fs_iofunc.write](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/libs/libc/stdio/lib_libfwrite.c#L145) OR...

[write](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/libs/libc/stdio/lib_libfwrite.c#L149) (See below)

TODO: Which one?

TODO: _start prepares sig_trampoline and calls main

_This code doesn't look right..._

```text
apps/examples/hello/hello_main.c:38
printf("Hello, World!!\n");
4a: 00000097 auipc ra,0x0
4e: 000080e7 jalr ra
```

That's because this is __Relocatable Code__. The auipc offset will be fixed up by the NuttX ELF Loader when it loads this code into User Memory.

The Relocation Info shows that 0x0 will be replaced by the address of `puts`...

```text
4a: 00000097 auipc ra,0x0
4a: R_RISCV_CALL puts
4e: 000080e7 jalr ra
```

_Why `puts` instead of `printf`?_

The GCC Compiler has cleverly optimised away `printf` to become `puts`.

If we do this...

```c
printf("Hello, World %s!!\n", "Luppy");
```

Then `printf` will appear in our disassembly.

# NuttX App calls NuttX Kernel

![NuttX App calls NuttX Kernel](https://lupyuen.github.io/images/app-syscall.jpg)

Read the article...

- ["RISC-V Ox64 BL808 SBC: NuttX Apps and Initial RAM Disk"](https://lupyuen.github.io/articles/app)

_How does a NuttX App make a System Call to NuttX Kernel?_

Our NuttX App calls `write`, which is a Proxy Version...

```c
// From nuttx/syscall/proxies/PROXY_write.c
/* Auto-generated write proxy file -- do not edit */
#include
#include
#include

ssize_t write(int parm1, FAR const void * parm2, size_t parm3) {
return (ssize_t)sys_call3((unsigned int)SYS_write, (uintptr_t)parm1, (uintptr_t)parm2, (uintptr_t)parm3);
}
```
Proxy for `write` calls [sys_call3](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/include/syscall.h), which makes an `ecall` to NuttX Kernel...

```c
// Make an `ecall` to NuttX Kernel
static inline uintptr_t sys_call3(unsigned int nbr, uintptr_t parm1,
uintptr_t parm2, uintptr_t parm3) {
register long r0 asm("a0") = (long)(nbr);
register long r1 asm("a1") = (long)(parm1);
register long r2 asm("a2") = (long)(parm2);
register long r3 asm("a3") = (long)(parm3);
asm volatile
(
"ecall"
:: "r"(r0), "r"(r1), "r"(r2), "r"(r3)
: "memory"
);
asm volatile("nop" : "=r"(r0));
return r0;
}
```

Here's how it works...

![NuttX App calls NuttX Kernel](https://lupyuen.github.io/images/app-run.png)

We can see the list of proxies in the RISC-V Disassembly of the NuttX Apps...

```bash
## Hello App
→ grep PROXY hello.S
PROXY__assert.c
PROXY__exit.c
PROXY_clock_gettime.c
PROXY_gettid.c
PROXY_lseek.c
PROXY_nxsem_wait.c
PROXY_sem_clockwait.c
PROXY_sem_destroy.c
PROXY_sem_post.c
PROXY_sem_trywait.c
PROXY_task_setcancelstate.c
PROXY_write.c

## NuttX Shell NSH
→ grep PROXY init.S
PROXY__assert.c
PROXY__exit.c
PROXY_clock_gettime.c
PROXY_gettid.c
PROXY_nxsem_wait.c
PROXY_sched_getparam.c
PROXY_sched_setparam.c
PROXY_sem_clockwait.c
PROXY_sem_destroy.c
PROXY_sem_post.c
PROXY_sem_trywait.c
PROXY_task_setcancelstate.c
PROXY_write.c
PROXY_boardctl.c
PROXY_clock_nanosleep.c
PROXY_close.c
PROXY_ftruncate.c
PROXY_get_environ_ptr.c
PROXY_getenv.c
PROXY_gethostname.c
PROXY_ioctl.c
PROXY_kill.c
PROXY_lseek.c
PROXY_lstat.c
PROXY_mkdir.c
PROXY_mount.c
PROXY_nx_pthread_create.c
PROXY_nx_pthread_exit.c
PROXY_nx_vsyslog.c
PROXY_open.c
PROXY_pgalloc.c
PROXY_posix_spawn.c
PROXY_pthread_detach.c
PROXY_read.c
PROXY_rename.c
PROXY_rmdir.c
PROXY_sched_getscheduler.c
PROXY_sched_lock.c
PROXY_sched_unlock.c
PROXY_setenv.c
PROXY_stat.c
PROXY_sysinfo.c
PROXY_umount2.c
PROXY_unlink.c
PROXY_unsetenv.c
PROXY_waitpid.c
```

# Kernel Handles App Call

Read the article...

- ["RISC-V Ox64 BL808 SBC: NuttX Apps and Initial RAM Disk"](https://lupyuen.github.io/articles/app)

_Our App makes an ecall to jump to NuttX Kernel..._

_What happens on the other side?_

Remember the Proxy Function from earlier? Now we do the exact opposite in our __Stub Function__ (that runs in the Kernel)...

```c
// From nuttx/syscall/stubs/STUB_write.c
/* Auto-generated write stub file -- do not edit */
#include
#include
#include

uintptr_t STUB_write(int nbr, uintptr_t parm1, uintptr_t parm2, uintptr_t parm3) {
return (uintptr_t)write((int)parm1, (FAR const void *)parm2, (size_t)parm3);
}
```

To handle IRQ 8 (RISCV_IRQ_ECALLU), NuttX does...

- [Attach RISCV_IRQ_ECALLU](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/common/riscv_exception.c#L114-L119), which calls...

- [riscv_swint](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/common/riscv_swint.c#L105-L537), which calls...

- [dispatch_syscall](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/common/riscv_swint.c#L54-L100), which calls Kernel Function Stub and...

- [sys_call2](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/common/supervisor/riscv_syscall.S#L49-L177) with A0=SYS_syscall_return (3), which calls...

- [riscv_perform_syscall](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/common/supervisor/riscv_perform_syscall.c#L36-L78), which calls...

- [riscv_swint](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/common/riscv_swint.c#L105-L537) with IRQ 0, to return from Syscall

_How did we figure out that 63 is the System Call Number for "write"?_

OK this part gets tricky. Below is the Enum that defines all __System Call Numbers__: [syscall.h](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/include/sys/syscall.h#L55-L66) and [syscall_lookup.h](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/include/sys/syscall_lookup.h#L202)

```c
// System Call Enum sequentially assigns
// all System Call Numbers (8 to 147-ish)
enum {
...
SYSCALL_LOOKUP(close, 1)
SYSCALL_LOOKUP(ioctl, 3)
SYSCALL_LOOKUP(read, 3)
SYSCALL_LOOKUP(write, 3)
...
};
```

But it's an Enum, __numbered sequentially__ from 8 to 147-ish. We won't actually see 63 in the NuttX Source Code.

When we look at the RISC-V Disassembly hello.S:

```text
ssize_t write(int parm1, FAR const void * parm2, size_t parm3)
{
dcc: 872a mv a4,a0

0000000000000dce <.LVL1>:
dce: 87ae mv a5,a1

0000000000000dd0 <.LVL2>:
dd0: 86b2 mv a3,a2

0000000000000dd2 <.LBB4>:
sys_call3():
/Users/Luppy/ox64/nuttx/include/arch/syscall.h:252
register long r0 asm("a0") = (long)(nbr);
dd2: 03f00513 li a0,63
```

We see that SYS_write = 63. Also from hello.S:

```text
<2><66e7>: Abbrev Number: 6 (DW_TAG_enumerator)
<66e8> DW_AT_name : (indirect string, offset: 0x4b98): SYS_write
<66ec> DW_AT_const_value : 63
```

_What happens when we run this?_

In `make menuconfig`, enable CONFIG_DEBUG_SYSCALL_INFO: Build Setup > Debug Options > Syscall Debug Features > Syscall Warning / Error / Info

The [NuttX Log](https://gist.github.com/lupyuen/ce82b29c664b1d5898b6a59743310c17) shows the System Calls from NuttX App to NuttX Kernel...

```text
riscv_dispatch_irq: irq=8
riscv_swint: Entry: regs: 0x5040bcb0 cmd: 63
up_dump_register: EPC: 00000000800019b2
up_dump_register: A0: 000000000000003f A1: 0000000000000001 A2: 000000008000ad00 A3: 000000000000001e
up_dump_register: A4: 0000000000000001 A5: 000000008000ad00 A6: 0000000000000000 A7: fffffffffffffff8
up_dump_register: T0: 0000000050212a20 T1: 0000000000000007 T2: 0000000000000000 T3: 0000000080200908
up_dump_register: T4: 0000000080200900 T5: 0000000000000000 T6: 0000000000000000
up_dump_register: S0: 00000000802005c0 S1: 0000000080202010 S2: 0000000080202010 S3: 0000000000000000
up_dump_register: S4: 0000000000000001 S5: 0000000000000000 S6: 0000000000000000 S7: 0000000000000000
up_dump_register: S8: 0000000000000000 S9: 0000000000000000 S10: 0000000000000000 S11: 0000000000000000
up_dump_register: SP: 0000000080202b70 FP: 00000000802005c0 TP: 0000000000000000 RA: 0000000080001a6a
riscv_swint: SWInt Return: 37
STUB_write: nbr=440, parm1=1, parm2=8000ad00, parm3=1e

NuttShell (NSH) NuttX-12.0.3
riscv_swint: Entry: regs: 0x5040baa0 cmd: 3
up_dump_register: EPC: 0000000080001a6a
up_dump_register: A0: 0000000000000003 A1: 000000005040bbec A2: 000000000000001e A3: 0000000000000000
up_dump_register: A4: 0000000000007fff A5: 0000000000000001 A6: 0000000000000009 A7: fffffffffffffff8
up_dump_register: T0: 000000000000002e T1: 000000000000006a T2: 00000000000001ff T3: 000000000000006c
up_dump_register: T4: 0000000000000068 T5: 0000000000000009 T6: 000000000000002a
up_dump_register: S0: 00000000802005c0 S1: 0000000080202010 S2: 0000000080202010 S3: 0000000000000000
up_dump_register: S4: 0000000000000001 S5: 0000000000000000 S6: 0000000000000000 S7: 0000000000000000
up_dump_register: S8: 0000000000000000 S9: 0000000000000000 S10: 0000000000000000 S11: 0000000000000000
up_dump_register: SP: 000000005040bcb0 FP: 00000000802005c0 TP: 0000000000000000 RA: 0000000080001a6a
riscv_swint: SWInt Return: 1e
```

_What are the Register Values?_

Before System Call:

- A0 = 0x3f (SYS_write)

- A1 = 1 (stdout)

- A2 = 0x8000ad00 (g_nshgreeting)

- A3 = 0x1e (length)

- nbr = 440 (Offset for the stub lookup table, [g_stublookup](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/syscall/syscall_stublookup.c#L80-L93))

After System Call:

- A0 = 3 [(SYS_syscall_return)](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/include/syscall.h#L80-L87)

- Returns 0x1E = 30 chars, including [linefeeds before and after](https://github.com/lupyuen2/wip-pinephone-nuttx-apps/blob/ox64b/nshlib/nsh_parse.c#L292-L302)

# Kernel Starts a NuttX App

(To see the NuttX Source Code: Right-click the Node and select "Open Link")

```mermaid
flowchart TD
START --> nx_bringup["NuttX Bringup: \n nx_bringup"]
click nx_bringup href "https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L373-L458" "sched/init/nx_bringup.c" _blank

nx_bringup --> nx_create_initthread["Create Init Thread: \n nx_create_initthread \n (Create the Init Thread)"]
click nx_create_initthread href "https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L330-L367" "sched/init/nx_bringup.c" _blank

nx_create_initthread --> nx_start_application["Start App: \n nx_start_application \n (Start NuttX Shell)"]
click nx_start_application href "https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L212C1-L302" "sched/init/nx_bringup.c" _blank

nx_start_application --> exec_spawn["Exec Spawn: \n exec_spawn \n (Start the app)"]
click exec_spawn href "https://github.com/apache/nuttx/blob/master/binfmt/binfmt_exec.c#L183-L223" "binfmt/binfmt_exec.c" _blank

exec_spawn --> exec_internal["Exec Internal: \n exec_internal \n (Start the app)"]
click exec_internal href "https://github.com/apache/nuttx/blob/master/binfmt/binfmt_exec.c#L42-L179" "binfmt/binfmt_exec.c" _blank

exec_internal --> load_module["Load Module: \n load_module \n (Load the app)"]
click load_module href "https://github.com/apache/nuttx/blob/master/binfmt/binfmt_loadmodule.c#L136-L225" "binfmt/binfmt_loadmodule.c" _blank

exec_internal --> exec_module["Execute Module: \n exec_module \n (Execute the app)"]
click exec_module href "https://github.com/apache/nuttx/blob/master/binfmt/binfmt_execmodule.c#L190-L450" "binfmt/binfmt_execmodule.c" _blank

load_module --> load_absmodule["Load Absolute Module: \n load_absmodule \n (Load an absolute path)"]
click load_absmodule href "https://github.com/apache/nuttx/blob/master/binfmt/binfmt_loadmodule.c#L83-L132" "binfmt/binfmt_loadmodule.c" _blank

load_absmodule --> binfmt_s.load["Load Binary Format: \n binfmt_s.load \n (Load a binary module)"]
click binfmt_s.load href "https://github.com/apache/nuttx/blob/master/include/nuttx/binfmt/binfmt.h#L122-L148" "include/nuttx/binfmt/binfmt.h" _blank

binfmt_s.load --> g_elfbinfmt["ELF Loader: \n g_elfbinfmt \n (Load the ELF File)"]
click g_elfbinfmt href "https://github.com/apache/nuttx/blob/master/binfmt/elf.c#L84-L94" "binfmt/elf.c" _blank

g_elfbinfmt --> elf_loadbinary["Load ELF Binary: \n elf_loadbinary \n (Load the ELF Binary)"]
click elf_loadbinary href "https://github.com/apache/nuttx/blob/master/binfmt/elf.c#L225-L355" "binfmt/elf.c" _blank

elf_loadbinary --> elf_load["Load ELF: \n elf_load \n (Load the ELF Binary)"]
click elf_load href "https://github.com/apache/nuttx/blob/master/binfmt/libelf/libelf_load.c#L297-L445" "binfmt/libelf/libelf_load.c" _blank

elf_load --> elf_addrenv_alloc["Allocate Address Env: \n elf_addrenv_alloc \n (Allocate the Address Env)"]
click elf_addrenv_alloc href "https://github.com/apache/nuttx/blob/master/binfmt/libelf/libelf_addrenv.c#L56-L178" "binfmt/libelf/libelf_addrenv.c" _blank

elf_addrenv_alloc --> up_addrenv_create["Create Address Env: \n up_addrenv_create \n (Create the Address Env)"]
click up_addrenv_create href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_addrenv.c#L339-L490" "arch/risc-v/src/common/riscv_addrenv.c" _blank

elf_addrenv_alloc --> mmu_satp_reg["Set SATP Register: \n mmu_satp_reg \n (Set SATP Register)"]
click mmu_satp_reg href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_mmu.h#L152-L176" "arch/risc-v/src/common/riscv_mmu.h" _blank

up_addrenv_create --> create_region["Create MMU Region: \n create_region \n (Create the MMU Region)"]
click create_region href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_addrenv.c#L213-L310" "arch/risc-v/src/common/riscv_addrenv.c" _blank

create_region --> mmu_ln_setentry["Set MMU Page Table Entry: \n mmu_ln_setentry \n (Populate the Page Table Entries)"]
click mmu_ln_setentry href "https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_mmu.c#L62-L109" "arch/risc-v/src/common/riscv_mmu.c" _blank
```

Read the article...

- ["RISC-V Ox64 BL808 SBC: NuttX Apps and Initial RAM Disk"](https://lupyuen.github.io/articles/app)

_Phew so NuttX Apps can call NuttX Kernel..._

_But how does NuttX Kernel start a NuttX App?_

Earlier we stepped through the __Boot Sequence__ for NuttX...

- [__"NuttX Boot Flow"__](https://lupyuen.github.io/articles/ox2#appendix-nuttx-boot-flow)

Right after that, [__NuttX Bringup (nx_bringup)__](https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L373-L458) calls...

- [__Create Init Thread: nx_create_initthread__](https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L330-L367) (to create the Init Thread), which calls...

- [__Start App: nx_start_application__](https://github.com/apache/nuttx/blob/master/sched/init/nx_bringup.c#L212C1-L302) (to start NuttX Shell), which calls...

- [__Exec Spawn: exec_spawn__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_exec.c#L183-L223) (to start the app), which calls...

- [__Exec Internal: exec_internal__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_exec.c#L42-L179) (to start the app), which calls...

- [__Load Module: load_module__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_loadmodule.c#L136-L225) (to load the app, see below) and...

[__Execute Module: exec_module__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_execmodule.c#L190-L450) (to execute the app)

To load a NuttX App module: [__load_module__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_loadmodule.c#L136-L225) calls...

- [__Load Absolute Module: load_absmodule__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_loadmodule.c#L83-L132) (to load an absolute path), which calls...

- [__Load Binary Format: binfmt_s.load__](https://github.com/apache/nuttx/blob/master/include/nuttx/binfmt/binfmt.h#L122-L148) (to load a binary module), which calls...

- [__ELF Loader: g_elfbinfmt__](https://github.com/apache/nuttx/blob/master/binfmt/elf.c#L84-L94) (to load the ELF File, see below)

To load the ELF File: [__ELF Loader g_elfbinfmt__](https://github.com/apache/nuttx/blob/master/binfmt/elf.c#L84-L94) calls...

- [__Load ELF Binary: elf_loadbinary__](https://github.com/apache/nuttx/blob/master/binfmt/elf.c#L225-L355) (to load the ELF Binary), which calls...

- [__Load ELF: elf_load__](https://github.com/apache/nuttx/blob/master/binfmt/libelf/libelf_load.c#L297-L445) (to load the ELF Binary), which calls...

- [__Allocate Address Env: elf_addrenv_alloc__](https://github.com/apache/nuttx/blob/master/binfmt/libelf/libelf_addrenv.c#L56-L178) (to allocate the Address Env), which calls...

- [__Create Address Env: up_addrenv_create__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_addrenv.c#L339-L490) (to create the Address Env), which calls...

(Also calls [__mmu_satp_reg__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_mmu.h#L152-L176) to set SATP Register)

- [__Create MMU Region: create_region__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_addrenv.c#L213-L310) (to create the MMU Region), which calls...

- [__Set MMU Page Table Entry: mmu_ln_setentry__](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/common/riscv_mmu.c#L62-L109) (to populate the Page Table Entries)

There's plenty happening inside [__Execute Module: exec_module__](https://github.com/apache/nuttx/blob/master/binfmt/binfmt_execmodule.c#L190-L450). But we won't explore today.

# UART Interrupt for Ox64 BL808

![Platform-Level Interrupt Controller for Pine64 Ox64 64-bit RISC-V SBC (Bouffalo Lab BL808)](https://lupyuen.github.io/images/plic2-registers.jpg)

Read the article...

- ["RISC-V Ox64 BL808 SBC: UART Interrupt and Platform-Level Interrupt Controller (PLIC)"](https://lupyuen.github.io/articles/plic2)

Let's fix the UART Interrupts for NuttX on Ox64 BL808!

We fix the PLIC Offsets according to [C906 User Manual (Page 77)](https://occ-intl-prod.oss-ap-southeast-1.aliyuncs.com/resource/XuanTie-OpenC906-UserManual.pdf)

_What's the UART3 IRQ?_

From the Linux Device Tree...

```text
serial@30002000 {
compatible = "bflb,bl808-uart";
reg = <0x30002000 0x1000>;
interrupts = <0x14 0x04>;
clocks = <0x04>;
status = "okay";
phandle = <0x0a>;
};
```

Thus...

- RISC-V IRQ = 0x14 = 20

- UART3 Int = (IRQ_NUM_BASE + 4)

- IRQ_NUM_BASE = 16

- NuttX IRQ = 45 (Offset by RISCV_IRQ_EXT)

- RISCV_IRQ_EXT = 25

We show the UART Interrupt Status...

```text
bl602_attach: BL602_UART_INT_STS=0x84
bl602_attach: BL602_UART_INT_MASK=0xfff
bl602_attach: BL602_UART_INT_CLEAR=0x0
bl602_attach: BL602_UART_INT_EN=0xfff
```

[(Source)](https://gist.github.com/lupyuen/c3f187af9f5c81594ddf8f854de2ed0a)

"urx_fer_int = 1" means "UART RX FIFO error interrupt, auto-cleared when FIFO overflow/underflow error flag is cleared"

We clear the RX FIFO Underflow, but still no UART Interrupts...

```text
bl602_attach: BL602_UART_FIFO_CONFIG_0=0x80
bl602_attach: BL602_UART_FIFO_CONFIG_0=0x8
```

We dump the PLIC and UART Registers in U-Boot...

```bash
## UART Registers
=> md 0x30002000 0x36
30002000: 00001705 00000701 00130013 00000000 ................
30002010: 009f0070 0000006f 0000000f 00000000 p...o..........
30002020: 00000012 00000fff 00000000 00000fff ................
30002030: 00000001 00000000 00000000 00000000 ................
30002040: 00000000 00000000 00000003 00000000 ................
30002050: 0026ffff 00000002 00000000 00000000 ..&.............
30002060: 00000000 00000000 00000000 00000000 ................
30002070: 00000000 00000000 00000000 00000000 ................
30002080: 00000000 07070000 0000000a 00000078 ............x...
30002090: 00000000 00000000 00000000 00000000 ................
300020a0: 00000000 00000000 00000000 00000000 ................
300020b0: 00000000 00000000 00000000 00000000 ................
300020c0: 00000000 00000000 00000000 00000000 ................
300020d0: 00000000 00000000 ........

## PLIC Interrupt Priority
=> md 0xe0000004 0x50
e0000004: 00000000 00000000 00000000 00000000 ................
e0000014: 00000000 00000000 00000000 00000000 ................
e0000024: 00000000 00000000 00000000 00000000 ................
e0000034: 00000000 00000000 00000000 00000000 ................
e0000044: 00000000 00000000 00000000 00000000 ................
e0000054: 00000000 00000000 00000000 00000000 ................
e0000064: 00000000 00000000 00000000 00000000 ................
e0000074: 00000000 00000000 00000000 00000000 ................
e0000084: 00000000 00000000 00000000 00000000 ................
e0000094: 00000000 00000000 00000000 00000000 ................
e00000a4: 00000000 00000000 00000000 00000000 ................
e00000b4: 00000000 00000000 00000000 00000000 ................
e00000c4: 00000000 00000000 00000000 00000000 ................
e00000d4: 00000000 00000000 00000000 00000000 ................
e00000e4: 00000000 00000000 00000000 00000000 ................
e00000f4: 00000000 00000000 00000000 00000000 ................
e0000104: 00000000 00000000 00000000 00000000 ................
e0000114: 00000000 00000000 00000000 00000000 ................
e0000124: 00000000 00000000 00000000 00000000 ................
e0000134: 00000000 00000000 00000000 00000000 ................

## PLIC Hart 0 S-Mode Interrupt Enable
=> md 0xe0002080 2
e0002080: 00000000 00000000 ........

## PLIC Hart 0 S-Mode Priority Threshold
=> md 0xe0201000 2
e0201000: 00000007 00000000 ........

## PLIC Hart 0 S-Mode Claim / Complete
=> md 0xe0201004 1
e0201004: 00000000 ....

## Interrupt Pending
=> md 0xe0001000 2
e0001000: 00000000 00000000 ........

## PLIC Hart 0 M-Mode Interrupt Enable
=> md 0xe0002000 2
e0002000: 00000000 00000000 ........

## PLIC Hart 0 M-Mode Priority Threshold
=> md 0xe0200000 2
e0200000: 00000007 00000000 ........

## PLIC Hart 0 M-Mode Claim / Complete
=> md 0xe0200004 1
e0200004: 00000000 ....

## Doesn't work: PLIC permission control register
## md 0xe01ffffc 1
```

_Claim / Complete M-Mode seems OK in U-Boot. Can we Claim / Complete M-Mode in NuttX S-Mode?_

```text
=> md 0xe0200004 1
e0200004: 00000000 ....
=> mw 0xe0200004 0x00 1
```

Yes we can Claim / Complete M-Mode in NuttX S-Mode. But S-Mode Claim is still 0...

```text
PLIC Hart 0 M-Mode Claim / Complete (0xe0200004):
0000 07 00 00 00 ....
bl602_attach: Claim / Complete M-Mode: claim=7
PLIC Hart 0 M-Mode Claim / Complete (0xe0200004):
0000 00 00 00 00 ....
...
riscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
```

TODO: Why UART Interrupt not enabled? U-Boot and OpenSBI don't use UART Interrupts?

TODO: What is Priority Threshold 7?

But after enabling UART IRQ, PLIC Interrupt Priority is 0 and Invalid!

```text
PLIC Interrupt Priority (0xe0000004):
0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
```

We set PLIC Interrupt Priority to 1 ourselves...

```text
bl602_attach: Set PLIC Interrupt Priority to 1
PLIC Interrupt Priority (0xe0000004):
0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 ................
0050 01 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00 ................
0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...............
00e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0130 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 ................
```

Then [IRQ 25 is OK yay!](https://gist.github.com/lupyuen/af6112c80db6907e5e5dec3519af53ff)

```text
riscv_dispatch_irq: irq=8
riscv_dispatch_irq: irq=8

NuttShell (NSH) NuttX-12.0.3
riscv_dispatch_irq: irq=25
riscv_dispatch_irq: irq=25
riscv_dispatch_irq: irq=25
```

But UART Interrupt can't be handled because [PLIC Claim is 0](https://gist.github.com/lupyuen/e1e6bf670ee4eefa0f968f1901407419)...

```text
riscv_dispatch_irq: Do irq=8
NuttShell (NSH) NuttX-12.0.3
riscv_dispatch_irq: irq=25, claim=0
riscv_dispatch_irq: *0xe0201004=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 10 00 00 00 10 00 ........
```

_Why is PLIC Claim = 0?_

This means that there are no External Interrupts triggered. So NuttX does nothing, again and again, till it becomes too busy to respond.

But Interrupt Pending is actually set for 2 External Interrupts!

_What are the 2 Interrupts Pending?_

```text
PLIC Interrupt Pending (0xe0001000):
0000 00 00 10 00 00 00 10 00 ........
```

- (0xe0001000) PLIC_IP0: Interrupt Pending for interrupts 1 to 31

0x100000 = (1 << 20) = IRQ 20 (UART3)

- (0xe0001004) PLIC_IP1: Interrupt Pending for interrupts 32 to 63

0x100000 = (1 << 20) = IRQ 52 (EMAC2)

TODO: Why EMAC2? We didn't enable the interrupt

We [handle Interrupt Pending](https://gist.github.com/lupyuen/84959d9ba79498a13a759b5b86c6fa29) ourselves...

```text
riscv_dispatch_irq: irq=25, claim=0
riscv_dispatch_irq: *0xe0201004=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 10 00 00 00 10 00 ........
riscv_dispatch_irq: Do irq=45
After Claim (0xe0001000):
0000 00 00 10 00 00 00 10 00 ........

riscv_dispatch_irq: irq=25, claim=0
riscv_dispatch_irq: *0xe0201004=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 10 00 00 00 10 00 ........
riscv_dispatch_irq: Do irq=45
After Claim (0xe0001000):
0000 00 00 10 00 00 00 10 00 ........
```

"Do IRQ" now works! But Interrupt Pending is not cleared, after we Claimed the interrupt.

_Doesn't NuttX already implement C906 PLIC?_

Yep but for Machine Mode only...

- [c906_irq.c](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/c906/c906_irq.c)

- [c906_irq_dispatch.c](https://github.com/apache/nuttx/blob/master/arch/risc-v/src/c906/c906_irq_dispatch.c)

_What if we copy this code into Ox64 PLIC?_

```c
// From arch/risc-v/src/c906/c906_irq.c
/* Clear pendings in PLIC */
uintptr_t val = getreg32(JH7110_PLIC_CLAIM);
putreg32(val, JH7110_PLIC_CLAIM);
```

Still the same, Claim = 0...

```text
riscv_dispatch_irq: irq=25, claim=0
riscv_dispatch_irq: *0xe0201004=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 10 00 00 00 10 00 ........
```

_Something special about T-Head C906 PLIC?_

According to the Linux Device Tree, Ox64 uses this PLIC Driver: "thead,c900-plic"

```text
interrupt-controller@e0000000 {
compatible = "thead,c900-plic";
```

Then from this [Linux Patch](https://lore.kernel.org/lkml/CAJF2gTS8Z+6Ewy0D5+0X_h2Jz4BqsJp7wEC5F0iNaDsSpiE2aw@mail.gmail.com/)

> "The T-HEAD C9xx SoC implements a modified/custom T-HEAD PLIC
specification which will mask current IRQ upon read to CLAIM register
and will unmask the IRQ upon write to CLAIM register. The
thead,c900-plic compatible string represents the custom T-HEAD PLIC
specification."

"thead,c900-plic" is implemented in Linux here: [irq-sifive-plic.c](https://github.com/torvalds/linux/blob/master/drivers/irqchip/irq-sifive-plic.c#L574-L582)

Which sets [PLIC_QUIRK_EDGE_INTERRUPT](https://github.com/torvalds/linux/blob/master/drivers/irqchip/irq-sifive-plic.c#L64), which is used by...

- [plic_irq_set_type](https://github.com/torvalds/linux/blob/master/drivers/irqchip/irq-sifive-plic.c#L212-L235)

- [plic_irq_domain_translate](https://github.com/torvalds/linux/blob/master/drivers/irqchip/irq-sifive-plic.c#L312-L325)

Which calls [irq_domain_translate_twocell](https://github.com/torvalds/linux/blob/master/kernel/irq/irqdomain.c#L1060-L1081)

(Instead of the normal [irq_domain_translate_onecell](https://github.com/torvalds/linux/blob/master/kernel/irq/irqdomain.c#L1043-L1060))

_What does it do?_

From another [Linux Patch](https://lore.kernel.org/all/[email protected]/)

> The Renesas RZ/Five SoC has a RISC-V AX45MP AndesCore with NCEPLIC100. The
NCEPLIC100 supports both edge-triggered and level-triggered interrupts. In
case of edge-triggered interrupts NCEPLIC100 ignores the next interrupt
edge until the previous completion message has been received and
NCEPLIC100 doesn't support pending interrupt counter, hence losing the
interrupts if not acknowledged in time.

> So the workaround for edge-triggered interrupts to be handled correctly
and without losing is that it needs to be acknowledged first and then
handler must be run so that we don't miss on the next edge-triggered
interrupt.

TODO: Fix this for Ox64 NuttX

After Clearing the Pending Interrupts, [NuttX responds to Key Presses yay!](https://gist.github.com/lupyuen/4e8ca1f0c0c2bd3b22a8b63f098abdd5)

```text
NuttShell (NSH) NuttX-12.0.3
riscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 00 00 00 00 00 00 ........
nsh> riscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 00 00 00 00 00 00 ........
riscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 00 00 00 00 00 00 ........
nx_start: CPU0: Beginning Idle Loop
riscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 00 00 00 00 00 00 ........
ˇriscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 00 00 00 00 00 00 ........
```

But Claim is still 0 though.

_Does it work for UART Input?_

Since we've correctly identified the IRQ Number, [__riscv_dispatch_irq__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/jh7110/jh7110_irq_dispatch.c#L48-L105) will (eventually) call [__bl602_receive__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/jh7110/bl602_serial.c#L859-L904) to read the UART Input (pic below)...

```text
bl602_receive: rxdata=-1
bl602_receive: rxdata=0x0
```

But the [__UART Input is empty__](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/jh7110/bl602_serial.c#L892-L901)! We need to troubleshoot our UART Driver some more.

[(See the __Complete Log__)](https://gist.github.com/lupyuen/cf32c834f4f5b8f66715ee4c606b7580#file-ox64-nuttx-int-clear-pending2-log-L294-L325)

[(Watch the __Demo on YouTube__)](https://youtu.be/VSTpsSJ_7L0)

![NuttX boots OK on Ox64 BL808! But UART Input is null](https://lupyuen.github.io/images/plic2-run.png)

# Strangeness in Ox64 BL808 PLIC

Read the article...

- ["RISC-V Ox64 BL808 SBC: UART Interrupt and Platform-Level Interrupt Controller (PLIC)"](https://lupyuen.github.io/articles/plic2)

_PLIC in Ox64 BL808 is acting really strange..._

_Why is Interrupt Priority set for 4 Interrupts, when we only set 1 (for UART)?_

```text
bl602_attach: Set PLIC Interrupt Priority to 1
PLIC Interrupt Priority (0xe0000004):
...
0040 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 ................
0050 01 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00 ................
```

_Maybe it's a problem with the RISC-V Code generated by GCC?_

Let's do a simple test: [bl602_serial.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64b/arch/risc-v/src/jh7110/bl602_serial.c#L447-L473)

```c
// Test the setting of PLIC Interrupt Priority
void test_interrupt_priority(void) {
static uint32_t before1 = 0xFF;
static uint32_t before2 = 0xFF;
static uint32_t after1 = 0xFF;
static uint32_t after2 = 0xFF;

// Read the values before setting Interrupt Priority
before1 = *(volatile uint32_t *) 0xe0000050UL;
before2 = *(volatile uint32_t *) 0xe0000054UL;

// Set the Interrupt Priority
*(volatile uint32_t *) 0xe0000050UL = 1;

// Read the values after setting Interrupt Priority
after1 = *(volatile uint32_t *) 0xe0000050UL;
after2 = *(volatile uint32_t *) 0xe0000054UL;
_info("before1=%u, before2=%u, after1=%u, after2=%u\n", before1, before2, after1, after2);
}
```

The Interrupt Priority [wasn't be set correctly](https://gist.github.com/lupyuen/4e8ca1f0c0c2bd3b22a8b63f098abdd5). Why did 0xe0000054 change from 0 to 1?

```text
bl602_attach: Test Interrupt Priority
test_interrupt_priority: before1=0, before2=0, after1=1, after2=1
```

Here's the Disassembly, which looks OK...

```text
0000000050200daa :
test_interrupt_priority():
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:451
uint32_t before1 = *(volatile uint32_t *) 0xe0000050;
50200daa: 461d li a2,7
50200dac: 0676 slli a2,a2,0x1d

/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:454
*(volatile uint32_t *) 0xe0000050 = 1;
50200dae: 4785 li a5,1

/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:451
uint32_t before1 = *(volatile uint32_t *) 0xe0000050;
50200db0: 4a34 lw a3,80(a2)

/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:452
uint32_t before2 = *(volatile uint32_t *) 0xe0000054;
50200db2: 4a78 lw a4,84(a2)

/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:454
*(volatile uint32_t *) 0xe0000050 = 1;
50200db4: ca3c sw a5,80(a2)

/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:457
uint32_t after1 = *(volatile uint32_t *) 0xe0000050;
50200db6: 4a3c lw a5,80(a2)

/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:458
uint32_t after2 = *(volatile uint32_t *) 0xe0000054;
50200db8: 05462803 lw a6,84(a2)
```

_Maybe we need to flush the CPU Cache?_

Nope `sfence` doesn't help...

```text
0000000050200daa :
test_interrupt_priority():
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:451
uint32_t before1 = *(volatile uint32_t *) 0xe0000050;
50200daa: 461d li a2,7
50200dac: 0676 slli a2,a2,0x1d
50200dae: 4a34 lw a3,80(a2)
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:452
uint32_t before2 = *(volatile uint32_t *) 0xe0000054;
50200db0: 4a78 lw a4,84(a2)
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:455
*(volatile uint32_t *) 0xe0000050 = 1;
50200db2: 4785 li a5,1
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:451
uint32_t before1 = *(volatile uint32_t *) 0xe0000050;
50200db4: 2681 sext.w a3,a3
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:452
uint32_t before2 = *(volatile uint32_t *) 0xe0000054;
50200db6: 2701 sext.w a4,a4
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:455
*(volatile uint32_t *) 0xe0000050 = 1;
50200db8: ca3c sw a5,80(a2)
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:458
__asm__ __volatile__
50200dba: 12000073 sfence.vma
50200dbe: 0330000f fence rw,rw
50200dc2: 0000100f fence.i
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:467
uint32_t after1 = *(volatile uint32_t *) 0xe0000050;
50200dc6: 4a3c lw a5,80(a2)
50200dc8: 2781 sext.w a5,a5
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:469
__asm__ __volatile__
50200dca: 12000073 sfence.vma
50200dce: 0330000f fence rw,rw
50200dd2: 0000100f fence.i
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:477
uint32_t after2 = *(volatile uint32_t *) 0xe0000054;
50200dd6: 05462803 lw a6,84(a2)
50200dda: 2801 sext.w a6,a6
/Users/Luppy/ox64/nuttx/arch/risc-v/src/chip/bl602_serial.c:479
__asm__ __volatile__
50200ddc: 12000073 sfence.vma
50200de0: 0330000f fence rw,rw
50200de4: 0000100f fence.i
```

Let's do the same with U-Boot Bootloader. It looks OK, doesn't have the same problem...

```bash
## Read the values before setting Interrupt Priority
=> md 0xe0000050 1
e0000050: 00000000 ....
=> md 0xe0000054 1
e0000054: 00000000 ....

## Set the Interrupt Priority
=> mw 0xe0000050 0x01 1

## Read the values after setting Interrupt Priority
=> md 0xe0000050 1
e0000050: 00000001 ....
=> md 0xe0000054 1
e0000054: 00000000 ....
```

And U-Boot doesn't use MMU.

_Could it be caused by MMU?_

Let's try setting Interrupt Priority before MMU Init. It works OK!

```text
123jh7110_copy_ramdisk: _edata=0x50400258, _sbss=0x504002a0, _ebss=0x50408000, JH7110_IDLESTACK_TOP=0x50408c00
jh7110_copy_ramdisk: ramdisk_addr=0x50410291
jh7110_copy_ramdisk: size=8192000
ABCjh7110_mm_init: Test Interrupt Priority
test_interrupt_priority: before1=0, before2=0, after1=1, after2=0
jh7110_kernel_mappings: map I/O regions
```

When we set Interrupt Priority after MMU Init, it looks incorrect...

```text
123jh7110_copy_ramdisk: _edata=0x50400258, _sbss=0x504002a0, _ebss=0x50408000, JH7110_IDLESTACK_TOP=0x50408c00
jh7110_copy_ramdisk: ramdisk_addr=0x50410291
jh7110_copy_ramdisk: size=8192000
ABCjh7110_kernel_mappings: map I/O regions
jh7110_kernel_mappings: map PLIC as Interrupt L2
jh7110_kernel_mappings: connect the L1 and Interrupt L2 page tables for PLIC
jh7110_kernel_mappings: map kernel text
jh7110_kernel_mappings: map kernel data
jh7110_kernel_mappings: connect the L1 and L2 page tables
jh7110_kernel_mappings: map the page pool
mmu_satp_reg: pgbase=0x50407000, asid=0x0, reg=0x8000000000050407
mmu_write_satp: reg=0x8000000000050407
jh7110_mm_init: Test Interrupt Priority
test_interrupt_priority: before1=0, before2=0, after1=0, after2=0
nx_start: Entry
```

So it's an MMU problem!

_Why is MMU messing up our updates to Ox64 BL808 PLIC?_

We might have missed something specific to C906 MMU. Here are the Extended Page Attributes, from [C906 User Manual (Page 53)](https://occ-intl-prod.oss-ap-southeast-1.aliyuncs.com/resource/XuanTie-OpenC906-UserManual.pdf)

- __SO – Strong order__ (Bit 63)

Indicates the access order required by memory.

0: no strong order (Normal-memory)

1: strong order (Device)

The default value is no strong order.

- __C – Cacheable__ (Bit 62)

0: Non-cacheable

1: Cacheable

The default value is Non-cacheable.

- __B – Buffer__ (Bit 61)

0: Non-bufferable

1: Bufferable

The default value is Non-bufferable

Also...

> "C906 extended page attributes exist only when the MAEE bit in the MXSTATUS register is 1."

__So Beware:__ T-Head MMU Flags (Strong Order / Shareable) are available only if OpenSBI has set the __MAEE Bit in the MXSTATUS Register to 1__. Otherwise the MMU will crash when we set the flags!

__For Ox64 (T-Head C906):__ MAEE Bit in MXSTATUS Register is set to 1. So T-Head MMU Flags are allowed. (But [d0_lowload Boot Code](https://github.com/openbouffalo/OBLFR/blob/master/apps/d0_lowload/src/rv32i_xtheade_lz4.S) doesn't set MXSTATUS?)

[__For CanMV-k230 (T-Head C908):__](https://github.com/apache/nuttx/pull/11379) MAEE Bit in MXSTATUS Register is (probably) set to 0. So T-Head MMU Flags are NOT ALLOWED. MMU will crash if we set the T-Head MMU Flags! [(Reported by yf13)](https://github.com/yf13)

TODO: Does T-Head C908 support T-Head MMU Flags or RISC-V Svpbmt Extension? We can't find the C908 Manual, and this [Linux Patch](https://lore.kernel.org/lkml/[email protected]/T/) says...

> In an ideal world those would be handled as extensions as well - T-Head fixed
their vectors with the C908 so they might do standards-compliant Svpbmt and
Zicbom in the future.

This is how we set the T-Head MMU Flags (Strong Order / Shareable)...

- ["Fixed the UART Interrupt and Platform-Level Interrupt Controller (Ox64 BL808)"](https://lupyuen.github.io/articles/plic3)

_What is `ERRATA_THEAD_*` in Linux Kernel?_

- [riscv/errata/thead/errata.c](https://github.com/torvalds/linux/blob/master/arch/riscv/errata/thead/errata.c#L88C1-L122)

- [riscv/include/asm/errata_list.h](https://github.com/torvalds/linux/blob/master/arch/riscv/include/asm/errata_list.h#L69-L164)

- [riscv/include/asm/pgtable-64.h](https://github.com/torvalds/linux/blob/master/arch/riscv/include/asm/pgtable-64.h#L126-L142)

- [drivers/perf/riscv_pmu_sbi.c](https://github.com/torvalds/linux/blob/master/drivers/perf/riscv_pmu_sbi.c#L803-L845)

Check out the answer here...

- ["Fixed the UART Interrupt and Platform-Level Interrupt Controller (Ox64 BL808)"](https://lupyuen.github.io/articles/plic3)

_What if we disable and re-enable MMU, while setting PLIC Interrupt Priority?_

Yep seems to work...

```text
jh7110_mm_init: Disable MMU
mmu_write_satp: reg=0
jh7110_mm_init: Test Interrupt Priority
test_interrupt_priority: before1=0, before2=0, after1=1, after2=0
jh7110_mm_init: Enable MMU
```

# Compare Ox64 BL808 UART Registers

Read the article...

- ["Fixed the UART Interrupt and Platform-Level Interrupt Controller (Ox64 BL808)"](https://lupyuen.github.io/articles/plic3)

To fix the null UART Input, let's compare the [UART Registers from NuttX](https://gist.github.com/lupyuen/5d16f536133c0c3b5a30a50950a1ee75) vs [U-Boot Bootloader](https://gist.github.com/lupyuen/e0d13fb888a490fbf3dfcb01bbdd86fc)

UART Registers from [NuttX UART Driver](https://gist.github.com/lupyuen/5d16f536133c0c3b5a30a50950a1ee75)...

```bash
// UART Registers from NuttX
bl602_receive: rxdata=-1
bl602_receive: rxdata=0x0
UART Registers (0x30002000):
0000 05 17 00 00 | 01 07 00 00 | 13 00 13 00 | 00 00 00 00 ................
0010 70 00 9f 00 | 6f 00 00 00 | 0f 00 00 00 | 00 00 00 00 p...o...........
0020 [94 00 00 00]|[f5 0f 00 00]| 00 00 00 00 | ff 0f 00 00 ................
0030 01 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 ................
0040 00 00 00 00 | 00 00 00 00 | 03 00 00 00 | 00 00 00 00 ................
0050 [ff ff 1c 00]| 02 00 00 00 | 00 00 00 00 | 00 00 00 00 ................
0060 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 ...............
0070 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 ................
0080 [80 00 00 00]|[18 00 07 07]| 0a 00 00 00 |[00 00 00 00] ................
0090 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 ................
00a0 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 ................
00b0 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 ................
00c0 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 ................
00d0 00 00 00 00 | 00 00 00 00 | | ........
```

UART Registers from [U-Boot Bootloader](https://gist.github.com/lupyuen/e0d13fb888a490fbf3dfcb01bbdd86fc)...

```bash
## UART Registers from U-Boot
=> md 0x30002000 0x36
30002000: 00001705 00000701 00130013 00000000 ................
30002010: 009f0070 0000006f 0000000f 00000000 p...o..........
30002020:[00000012][00000fff]00000000 00000fff ................
30002030: 00000001 00000000 00000000 00000000 ................
30002040: 00000000 00000000 00000003 00000000 ................
30002050:[0026ffff] 00000002 00000000 00000000 ..&.............
30002060: 00000000 00000000 00000000 00000000 ................
30002070: 00000000 00000000 00000000 00000000 ................
30002080:[00000000][07070000]0000000a[00000078] ............x...
30002090: 00000000 00000000 00000000 00000000 ................
300020a0: 00000000 00000000 00000000 00000000 ................
300020b0: 00000000 00000000 00000000 00000000 ................
300020c0: 00000000 00000000 00000000 00000000 ................
300020d0: 00000000 00000000 ........
```

Here are the differences (marked above)...

```text
Offset 20: uart_int_sts (Interrupt Status)

00000094 = 0b10010100
Bit 7 urx_fer_int: UART RX FIFO error interrupt, auto-cleared when FIFO overflow/underflow error flag is cleared
Bit 4 urx_rto_int: UART RX Time-out interrupt
Bit 2 utx_frdy_int: UART TX FIFO ready (tx_fifo_cnt > tx_fifo_th) interrupt, auto-cleared when data is pushed

00000012 = 0b00010010
Bit 4 urx_rto_int: UART RX Time-out interrupt
Bit 1 urx_end_int: UART RX transfer end interrupt (set according to cr_urx_-len)

Offset 24: uart_int_mask (Interrupt Mask)
00000ff5
00000fff
TODO: Set to 0xfff

Offset 50: urx_bcr_int_cfg (Receive Byte Count)
001cffff
0026ffff
Number of bytes received. OK to ignore this.

Offset 80: uart_fifo_config_0 (FIFO Config 0)
00000080
00000000
Bit 7 rx_fifo_underflow: Underflow flag of RX FIFO
Can be cleared by rx_fifo_clr.
TODO: Set Bit 3 rx_fifo_clr: Clear signal of RX FIFO

Offset 84: uart_fifo_config_1 (FIFO Config 1)
07070018
07070000
rx_fifo_cnt = 1 (RX FIFO available count)
tx_fifo_cnt = 8 (TX FIFO available count)
Let's ignore this.

Offset 8c: uart_fifo_rdata (Receive Data)
00000000
00000078
RX FIFO. OK to ignore this.
```

Nope still the same.

# Fix the UART Interrupt for Ox64 BL808

Read the article...

- ["Fixed the UART Interrupt and Platform-Level Interrupt Controller (Ox64 BL808)"](https://lupyuen.github.io/articles/plic3)

Let's fix the UART Interrupt for Ox64 BL808!

UART Input is strangely null, so we tried printing the UART Input just before reading it: [bl602_serial.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64c/arch/risc-v/src/jh7110/bl602_serial.c#L1026-L1044)

```c
/****************************************************************************
* Name: bl602_rxavailable
*
* Description:
* Return true if the receive register is not empty
*
****************************************************************************/

static bool bl602_rxavailable(struct uart_dev_s *dev)
{
struct bl602_uart_s *priv = (struct bl602_uart_s *)dev->priv;
uint8_t uart_idx = priv->config.idx;

/* Return true is data is available in the receive data buffer */

uintptr_t rx = getreg32(0x3000208c); _info("rx=%p\n", rx); ////
return (getreg32(BL602_UART_FIFO_CONFIG_1(uart_idx)) & \
UART_FIFO_CONFIG_1_RX_CNT_MASK) != 0;
}
```

Yes UART Input is correct!

```text
nx_start: CPU0: Beginning Idle Loop
bl602_rxavailable: rx=0x31
riscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
PLIC Interrut Pending (0xe0001000):
0000 00 00 00 00 00 00 00 00 ........
bl602_rxavailable: rx=0x32
riscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 00 00 00 00 00 00 ........
```

But somehow UART Input is erased when we read BL602_UART_FIFO_CONFIG_1 (Offset 0x84)...

```c
uintptr_t fifo = getreg32(0x30002084);
uintptr_t rx = getreg32(0x3000208c);
_info("fifo=%p, rx=%p\n", fifo, rx);
```

Which shows...

```text
nx_start: CPU0: Beginning Idle Loop
bl602_rxavailable: fifo=0x7070120, rx=0
riscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 00 00 00 00 00 00 ........
```

_Why is C906 messing up the memory access?_

From Linux Kernel we see the MMU Flags (Strong Order + Shareable) for I/O Memory: [pgtable-64.h](https://github.com/torvalds/linux/blob/master/arch/riscv/include/asm/pgtable-64.h#L126-L142)

Which is used by this T-Head Errata: [errata_list.h](https://github.com/torvalds/linux/blob/master/arch/riscv/include/asm/errata_list.h#L70-L92)

We do the same to Enable Strong Order + Shareable in NuttX Page Table Entries: [riscv_mmu.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/ox64c/arch/risc-v/src/common/riscv_mmu.c#L100-L127)

```c
// Save the Page Table Entry
lntable[index] = (paddr | mmuflags);

//// Begin Test
// From https://github.com/torvalds/linux/blob/master/arch/riscv/include/asm/pgtable-64.h#L126-L142
/*
* [63:59] T-Head Memory Type definitions:
* bit[63] SO - Strong Order
* bit[60] SH - Shareable
* 10010 - IO Strongly-ordered, Non-cacheable, Non-bufferable, Shareable, Non-trustable
*/
#define _PAGE_IO_THEAD ((1UL << 63) | (1UL << 60))
if ((mmuflags & PTE_R) && // Leaf Page Table Entry
(vaddr < 0x40000000UL || vaddr >= 0xe0000000UL)) // I/O or PLIC Memory
{
// Enable Strong Order and Shareable
lntable[index] = lntable[index] | _PAGE_IO_THEAD;
_info("vaddr=%p, lntable[index]=%p\n", vaddr, lntable[index]);
}
//// End Test
```

Yep [UART Input works OK](https://gist.github.com/lupyuen/6f3e24278c4700f73da72b9efd703167) yay!

```text
nx_start: CPU0: Beginning Idle Loop
bl602_receive: rxdata=0x31
riscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 00 00 00 00 00 00 ........
1riscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 00 00 00 00 00 00 ........
bl602_receive: rxdata=0x32
riscv_dispatch_irq: Clear Pending Interrupts, irq=45, claim=0
PLIC Interrupt Pending (0xe0001000):
0000 00 00 00 00 00 00 00 00 ........
2
```

Finally [UART Input and PLIC are both OK](https://gist.github.com/lupyuen/3761d9e73ca2c5b97b2f33dc1fc63946) yay!

```text
Starting kernel ...
123jh7110_copy_ramdisk: _edata=0x50400258, _sbss=0x50400290, _ebss=0x50110_IDLESTACK_TOP=0x50407c00
jh7110_copy_ramdisk: ramdisk_addr=0x50410281
jh7110_copy_ramdisk: size=8192000
ABCjh7110_kernel_mappings: map I/O regions
mmu_ln_setentry: vaddr=0, lntable[index]=0x90000000000000e7
jh7110_kernel_mappings: map PLIC as Interrupt L2
mmu_ln_setentry: vaddr=0xe0000000, lntable[index]=0x90000000380000e7
mmu_ln_setentry: vaddr=0xe0200000, lntable[index]=0x90000000380800e7
mmu_ln_setentry: vaddr=0xe0400000, lntable[index]=0x90000000381000e7
mmu_ln_setentry: vaddr=0xe0600000, lntable[index]=0x90000000381800e7
...
mmu_ln_setentry: vaddr=0xefc00000, lntable[index]=0x900000003bf000e7
mmu_ln_setentry: vaddr=0xefe00000, lntable[index]=0x900000003bf800e7
jh7110_kernel_mappings: connect the L1 and Interrupt L2 page tables for PLIC
jh7110_kernel_mappings: map kernel text
jh7110_kernel_mappings: map kernel data
jh7110_kernel_mappings: connect the L1 and L2 page tables
jh7110_kernel_mapmap the page pool
up_irqinitialize:

NuttShell (NSH) NuttX-12.0.3
nsh> uname -a
NuttX 12.0.3 fd05b07 Nov 24 2023 07:42:54 risc-v star64
nsh>
nsh> ls /dev
/dev:
console
null
ram0
zero
nsh>
nsh> hello
Hello, World!!
```

[(Watch the __Demo on YouTube__)](https://youtu.be/l7Y36nTkr8c)

C906 MMU is actually explained in [__C906 Integration Manual (Chinese)__](https://github.com/T-head-Semi/openc906/blob/main/doc/%E7%8E%84%E9%93%81C906%E9%9B%86%E6%88%90%E6%89%8B%E5%86%8C.pdf), Page 9.

And here's the [__RTL Code for C906 MMU__](https://github.com/T-head-Semi/openc906/tree/main/C906_RTL_FACTORY/gen_rtl/mmu/rtl).

![UART Input and PLIC are both OK!](https://lupyuen.github.io/images/plic3-title.png)

# Fix the RISC-V Timer with OpenSBI

Read the article...

- ["Nim on a Real-Time Operating System: Apache NuttX RTOS + Ox64 BL808 SBC"](https://lupyuen.github.io/articles/nim)

_The `sleep` command hangs in NuttX Shell. How to fix it?_

That's because we haven't implemented the RISC-V Timer for Ox64! We should call OpenSBI to handle the Timer, [here's the fix](https://github.com/lupyuen2/wip-pinephone-nuttx/commit/57ea5f000636f739ac3cb8ea1e60936798f6c3a9#diff-535879ffd6d9fc8e7d84b37a88bdeb1609c4a90e3777150939a96bed18696aee).

(Ignore riscv_mtimer.c, we were verifying that mtime and mtimecmp are unused in Kernel Mode)

We only need to change [arch/risc-v/src/bl808/bl808_timerisr.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/nim/arch/risc-v/src/bl808/bl808_timerisr.c#L44-L116)

```c
// Timer Frequency
#define MTIMER_FREQ 1000000

// This function is called during start-up to initialize the timer interrupt.
void up_timer_initialize(void) {
struct oneshot_lowerhalf_s *lower = riscv_mtimer_initialize(
0, 0, RISCV_IRQ_STIMER, MTIMER_FREQ);
DEBUGASSERT(lower);
up_alarm_set_lowerhalf(lower);
}
```

How it works: At startup, `up_timer_initialize` (above) calls...

- [riscv_mtimer_initialize](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/nim/arch/risc-v/src/common/riscv_mtimer.c#L318-L332) which calls...

- [riscv_mtimer_set_mtimecmp](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/nim/arch/risc-v/src/common/riscv_mtimer.c#L136-L141) which calls...

- [riscv_sbi_set_timer](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/nim/arch/risc-v/src/common/supervisor/riscv_sbi.c#L94-L107) which calls...

- [sbi_ecall](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/nim/arch/risc-v/src/common/supervisor/riscv_sbi.c#L53-L76) which makes an ecall to OpenSBI

- Which accesses the System Timer

Originally we set MTIMER_FREQ to 10000000: [bl808_timerisr.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/nim/arch/risc-v/src/bl808/bl808_timerisr.c#L44-L48)

```c
#define MTIMER_FREQ 10000000
```

But this causes the command `sleep 1` to pause for 10 seconds. So we divide the frequency by 10: [bl808_timerisr.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/nim/arch/risc-v/src/bl808/bl808_timerisr.c#L44-L48)

```c
#define MTIMER_FREQ 1000000
```

Now the `sleep` command works correctly in NuttX Shell!

[Here's the log (ignore the errors)](https://gist.github.com/lupyuen/8aa66e7f88d1e31a5f198958c15e4393)

# LED Driver for Ox64 BL808

Read the article...

- ["Nim on a Real-Time Operating System: Apache NuttX RTOS + Ox64 BL808 SBC"](https://lupyuen.github.io/articles/nim)

We wish to blink an LED with Nim on Ox64...

- ["Blink an LED with Nim"](https://github.com/lupyuen/nuttx-nim#blink-an-led-with-nim)

But first we need a barebones NuttX LED Driver for Ox64.

_How to create the NuttX LED Driver?_

We assume LED is connected to GPIO 29, Pin 21. [(See the Pinout)](https://wiki.pine64.org/wiki/File:Ox64_pinout.png)

(With a 47 Ohm Resistor, yellow-purple-black-gold)

_How do we flip a BL808 GPIO High and Low?_

From BL808 Reference Manual Page 56, "Normal GPIO Output Mode"...

- Set reg_gpio_xx_oe (Bit 6) to 1 to enable the GPIO output mode

= (1 << 6)

- Set reg_gpio_xx_func_sel (Bits 8 to 12) to 11 to enter the SWGPIO mode

= (11 << 8)

- Set reg_gpio_xx_mode (Bits 30 to 31) to 0 to enable the normal output function of I/O

= (0 << 30)

- Set reg_gpio_xx_pu (Bit 4) and reg_gpio_xx_pd (Bit 5) to 0 to disable the internal pull-up and pull-down functions

= (0 << 4)

- Set the level of I/O pin through reg_gpio_xx_o (Bit 24)

= Either (0 << 24) Or (1 << 24)

(GPIO Bit Definitions are below)

Which means...

- Set GPIO Output to 0

= (1 << 6) | (11 << 8) | (0 << 30) | (0 << 4) | (0 << 24)

= 0xb40

- Set GPIO Output to 1

= (1 << 6) | (11 << 8) | (0 << 30) | (0 << 4) | (1 << 24)

= 0x1000b40

_How to test this?_

GPIO 29 Base Address `gpio_cfg29` is 0x20000938.

For testing, we run U-Boot Bootloader Commands to set GPIO 29 to High and Low...

```bash
## Dump gpio_cfg29 at 0x20000938
$ md 0x20000938 1
20000938: 00400803 ..@.

## Set GPIO Output to 0: (1 << 6) | (11 << 8) | (0 << 30) | (0 << 4) | (0 << 24)
## = 0xb40
$ mw 0x20000938 0xb40 1
$ md 0x20000938 1
20000938: 00000b40 @...

## Set GPIO Output to 1: (1 << 6) | (11 << 8) | (0 << 30) | (0 << 4) | (1 << 24)
## = 0x1000b40
$ mw 0x20000938 0x1000b40 1
$ md 020000938 1
20000938: 01000b40 @...
```

And U-Boot switches the LED On and Off correctly yay!

_How to flip the GPIO in our NuttX LED Driver?_

This is how we flip the GPIO in our NuttX LED Driver: [bl808_userleds.c](https://github.com/lupyuen2/wip-pinephone-nuttx/blob/nim/boards/risc-v/bl808/ox64/src/bl808_userleds.c#L176-L209)

```c
// Switch the LEDs On and Off according to the LED Set
// (Bit 0 = LED 0)
void board_userled_all(uint32_t ledset)
{
_info("ledset=0x%x\n", ledset);////
int i;

// For LED 0 to 2...
for (i = 0; i < BOARD_LEDS; i++)
{
// Get the desired state of the LED
bool val = ((ledset & g_led_setmap[i]) != 0);
_info("led=%d, val=%d\n", i, val);////

// If this is LED 0...
if (i == 0)
{
// Switch it On or Off?
if (val)
{
// Switch LED 0 (GPIO 29) to On:
// Set gpio_cfg29 to (1 << 6) | (11 << 8) | (0 << 30) | (0 << 4) | (1 << 24)
// mw 0x20000938 0x1000b40 1
*(volatile uint32_t *) 0x20000938 = 0x1000b40;
}
else
{
// Switch LED 0 (GPIO 29) to Off:
// Set gpio_cfg29 to (1 << 6) | (11 << 8) | (0 << 30) | (0 << 4) | (0 << 24)
// mw 0x20000938 0xb40 1
*(volatile uint32_t *) 0x20000938 = 0xb40;
}
}
////TODO: a64_pio_write(g_led_map[i], (ledset & g_led_setmap[i]) != 0);
}
}
```

And our LED Driver works OK with Nim: It blinks our LED on Ox64 BL808 SBC!

- [Watch the Demo on YouTube](https://youtube.com/shorts/KCkiXFxBgxQ)

- [See the Log](https://gist.github.com/lupyuen/553c2da4ad5d119468d223e162573e96)

- ["Blink an LED with Nim"](https://github.com/lupyuen/nuttx-nim#blink-an-led-with-nim)

Later we'll replace the (awful) code above by the BL808 GPIO Driver. Which we'll copy from NuttX for BL602.

_How did we get the GPIO Bit Definitions?_

From BL808 Reference Manual Page 119...

```text
4.8.30 gpio_cfg29
Base Address:0x20000938

Bits Name Type Reset Description

31:30 reg_gpio_29_mode r/w 0 When GPIO Function Selected to SWGPIO
00 (Output Value Mode): GPIO Output by reg_gpio_x_o
Value
01 (Set/Celar Mode ) :GPIO Output set by reg_gpio_x_set
and clear by reg_gpio_x_clr
10 : SWGPIO Source comes from GPIO DMA (GPIO DMA
Mode), GPIO Output value by gpio_dma_o
11: SWGPIO Source comes from GPIO DMA (GPIO DMA
Mode), GPIO Outout value by gpio_dma_set/gpio_dma_clr

29 RSVD

28 reg_gpio_29_i r 0

27 RSVD

26 reg_gpio_29_clr w1p 0 When SWGPIO @ Set/Clear Mode
Set this bit will clear GPIO output value to 0,when set/clr at
the same time, only set take effect

25 reg_gpio_29_set w1p 0 When SWGPIO @ Set/Clear Mode
Set this bit will set GPIO output value to 1,when set/clr at
the same time, only set take effect

24 reg_gpio_29_o r/w 0 When SWGPIO @ Output Value Mode
00 : GPIO Value changes according to this value
01 : GPIO Value Set by this register and clr by clr_reg

23 RSVD

22 reg_gpio_29_int_mask r/w 1 mask interrupt (1)

21 gpio_29_int_stat r 0 interrupt status

20 reg_gpio_29_int_clr r/w 0 clear interrupt

19:16 reg_gpio_29_int_mode_set r/w 0 0000 : sync falling edge trigger
0001 : sync rising edge trigger
0010 : sync low level trigger
0011 : sync high level trigger
01xx : sync rising & falling edge trigger
1000 : async falling edge trigger
1001 : async rising edge trigger
1010 : async low level trigger
1011 : async high level trigger

15:13 RSVD

12:8 reg_gpio_29_func_sel r/w 5’hB GPIO Function Select (Default : SW-GPIO)

7 RSVD

6 reg_gpio_29_oe r/w 0 Register Controlled GPIO Output Enable (Used when GPIO
Function select to Register Control GPIO)

5 reg_gpio_29_pd r/w 0 GPIO Pull Down Control

4 reg_gpio_29_pu r/w 0 GPIO Pull Up Control

3:2 reg_gpio_29_drv r/w 0 GPIO Driving Control

1 reg_gpio_29_smt r/w 1 GPIO SMT Control

0 reg_gpio_29_ie r/w 0 GPIO Input Enable
```

# Emulate Ox64 BL808 SBC with TinyEMU

Read the articles...

- ["Automated Testing with Ox64 BL808 Emulator (Apache NuttX RTOS)"](https://lupyuen.github.io/articles/tinyemu3)

- ["Emulate Ox64 BL808 in the Web Browser: Experiments with TinyEMU RISC-V Emulator and Apache NuttX RTOS"](https://lupyuen.github.io/articles/tinyemu2)

Objective: Take the NuttX Kernel built for Ox64 BL808 SBC. And boot it on TinyEMU RISC-V Emulator in the Web Browser!

Check out the details here...

- ["Emulate Ox64 BL808 SBC with TinyEMU"](https://github.com/lupyuen/nuttx-tinyemu#emulate-ox64-bl808-sbc-with-tinyemu)

![Ox64 BL808 Emulator with TinyEMU RISC-V Emulator and Apache NuttX RTOS](https://lupyuen.github.io/images/tinyemu2-title.png)

[_(Live Demo of Ox64 BL808 Emulator)_](https://lupyuen.github.io/nuttx-tinyemu/ox64)

# Build and Test NuttX Apps in the Web Browser

Ox64 Emulator is now integrated with TCC RISC-V Compiler in WebAssembly. So we can Build and Test NuttX Apps in the Web Browser!

Read the article...

- ["Zig runs ROM FS Filesystem in the Web Browser (thanks to Apache NuttX RTOS)"](https://lupyuen.github.io/articles/romfs)

# QuickJS JavaScript Engine on Ox64 BL808 SBC

QuickJS JavaScript Engine now runs on Ox64 BL808 SBC, and in the Ox64 Web Emulator!

Read the article...

- ["QuickJS JavaScript Engine on a Real-Time Operating System (Apache NuttX RTOS)"](https://lupyuen.github.io/articles/quickjs)

# Documentation for Ox64 BL808

![Pine64 Ox64 64-bit RISC-V SBC (Sorry for my substandard soldering)](https://lupyuen.github.io/images/ox64-solder.jpg)

[_Pine64 Ox64 64-bit RISC-V SBC (Sorry for my substandard soldering)_](https://wiki.pine64.org/wiki/Ox64)

- ["Ox64 BL808 RISC-V SBC: Booting Linux and (maybe) Apache NuttX RTOS"](https://lupyuen.github.io/articles/ox64)

- ["Booting Linux on the Pine64 Ox64 SBC"](https://adventurist.me/posts/00317)

- [Pine64 Ox64 Wiki](https://wiki.pine64.org/wiki/Ox64)

- [Pine64 Ox64 Schematic](https://files.pine64.org/doc/ox64/PINE64_Ox64-Schematic-202221018.pdf)

- [OpenBouffalo Wiki](https://openbouffalo.org/index.php/Main_Page)

- [Linux Image + OpenSBI + U-Boot for BL808](https://github.com/openbouffalo/buildroot_bouffalo)

[(Newer version?)](https://github.com/bouffalolab/buildroot_bouffalo)

- [BL808 Datasheet](https://github.com/bouffalolab/bl_docs/blob/main/BL808_DS/en/BL808_DS_1.2_en.pdf)

- [BL808 Reference Manual](https://github.com/bouffalolab/bl_docs/blob/main/BL808_RM/en/BL808_RM_en_1.3.pdf)

- [XuanTie OpenC906 User Manual](https://occ-intl-prod.oss-ap-southeast-1.aliyuncs.com/resource/XuanTie-OpenC906-UserManual.pdf)

- [BL808 D0 Core: T-Head C906 480MHz 64-bit RISC-V CPU](https://www.t-head.cn/product/c906?lang=en)

(Multimedia Core: MIPI CSI / DSI, Neural Proc Unit)

Memory Mgmt Unit is Sv39, 128/256/512 TLB table entry. (Same as Star64?)

- [BL808 M0 Core: T-Head E907 320MHz 32-bit RISC-V CPU](https://www.t-head.cn/product/e907?lang=en)

(Wireless + Peripherals Core: WiFi, BLE, BT, Zigbee, Audio)

- [BL808 LP Core: T-Head E902 150MHz 32-bit RISC-V CPU](https://www.t-head.cn/product/e902?lang=en)

(Low Power Core)