Skip to content

Latest commit

 

History

History
1556 lines (1159 loc) · 52 KB

README.md

File metadata and controls

1556 lines (1159 loc) · 52 KB

64-bit RISC-V Sophgo SG2000 (T-Head C906 / Milk-V Duo S)

(Watch the Demo on YouTube)

Apache NuttX RTOS on 64-bit RISC-V Sophgo SG2000 (T-Head C906 / Milk-V Duo S)

Daily Build of NuttX for SG2000 Daily Test of NuttX for SG2000

Read the articles...

UPDATE: Sophgo SG2000 is officially supported by NuttX Mainline! (Including Milk-V Duo S and Pine64 Oz64)

Here's how everything got started...


64-bit RISC-V Sophgo SG2000 SoC ... Will it boot Apache NuttX RTOS? 🤔 (T-Head C906 / Milk-V Duo S)

Let's find out!

Is this a sponsored review?

I was given a Milk-V Duo S, and I bought another. So it cancels out, I guess?

Why are we doing all this?

  1. We hear that there will be plenty of interesting new SBCs based on Sophgo SG2000 and SG2002. Perfect for NuttX!

  2. NuttX has been ported from QEMU RISC-V to Star64 JH7110 to Ox64 BL808 and now Sophgo SG2000. Let's find the most efficient way to port NuttX to new RISC-V Devices!

Sophgo SG2000 RISC-V SoC

NuttX Automated Daily Build for SG2000

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

The Daily Releases are available here...

nuttx.hash contains the Commit Hash of the NuttX Kernel and NuttX Apps repos...

NuttX Source: https://github.com/apache/nuttx/tree/28ae3b38499c6c7bac0d432658da263b9eab2981
NuttX Apps: https://github.com/apache/nuttx-apps/tree/00f98947786892eaabf60b2e61fad553aee1c36c

The GitHub Actions Workflow is here...

We are now running Daily Automated Testing of NuttX on a Real Milk-V Duo S SBC...

  1. Download the Daily Build to TFTP Server

  2. Power on our SBC with an IKEA Smart Power Plug via Home Assistant

  3. Our SBC boots the Daily Build over TFTP

  4. Capture the Automated Testing Log and write to the Release Notes

    (See the Automated Test Logs)

    (See the Automated Test Script)

We are also running Daily Automated Testing with an SG2000 Emulator (in GitHub Actions)...

Sophgo SG2000 RISC-V SoC

Read the article...

Sophgo SG2000 SoC has a fascinating mix of 64-bit RISC-V Cores (Arm too)...

  • Main Processor: 64-bit RISC-V Core

    T-Head C906 (1.0 GHz)

    (For NuttX and Linux)

  • Co-Processor: 64-bit RISC-V Core

    T-Head C906 (700 MHz)

    (No Cache)

  • Alt-Main Processor: 64-bit Arm Core

    Cortex-A53 (1.0 GHz)

Plus a Low-Power 8051 MCU (for wakeup duties) and a Tensor Processing Unit (for image recognition, not LLM)

Sophgo SG2000 RISC-V SoC

Whoa RISC-V AND Arm CPUs in a single SoC?

Actually there's a Hardware Switch that selects the Main CPU: RISC-V OR Arm.

(Don't let yer pet hamster flip it... It will get super frustrating!)

Milk-V Duo S

Boot Milk-V Duo S without MicroSD

Read the article...

What happens if we boot Milk-V Duo S? Fresh from the box?

Connect our USB UART Dongle like so (pic above)...

Milk-V Duo S USB UART
GND (Pin 6) GND
TX (Pin 8) RX
RX (Pin 10) TX

(Source)

USB UART Dongle must be CP2102, it doesn't like CH340G 😬

Flip the switch so it's set to "RV" (RISC-V) instead of "Arm"...

Switch to "RV" (RISC-V) instead of "Arm"

Power up the board via the USB-C Port. We should see...

https://gist.github.com/lupyuen/a7c3af98be36dcd5cc5b45f5aadc5d16

C.SCS/0/0.WD.URPL.USBI.USBEF.BS/EMMC.EMI/25000000/12000000. E:bm_emmc_send_cmd_without_data CMD: 1 INT_STAT: 0x E:bm_emmc_send_cmd_without_data CMD: 0 INT_STAT: 0x E:eMMC init failed, 3
 E:eMMC initializing failed
PS. E:bm_emmc_send_cmd_without_data CMD: 6 INT_STAT: 0x E:load param1 (-5)
 E:Boot failed (8).
 E:RESET:plat/mars/platform.c:114
WD.C.SCS/0/0.WD.URPL.USBI.USBEF.BS/EMMC.EMI/25000000/12000000. E:bm_emmc_send_cmd_without_data CMD: 1 INT_STAT: 0x E:bm_emmc_send_cmd_without_data CMD: 0 INT_STAT: 0x E:eMMC init failed, 3
 E:eMMC initializing failed

If we see B.SCS instead of C.SCS...

https://gist.github.com/lupyuen/d55b77a51ee8b258d6d1c0799770742a

B.SCS/0/0.WD.URPL.USBI.USBEF.BS/EMMC.EMI/25000000/12000000.

Nope we're in Arm Mode! Flip the switch back to RISC-V!

If we use CH340G (instead of CP2102): UART Output will be garbled (happens on Linux and macOS, maybe Windows too)...

https://gist.github.com/lupyuen/1d5ba1b2a47c110ee7ff265102b1aae5

Milk-V Duo S doesn't ship with U-Boot Bootloader preinstalled in Flash Memory. We'll need U-Boot on MicroSD...

Boot Linux on Milk-V Duo S

Read the article...

Milk-V Duo S won't boot without MicroSD. How now?

Let's boot Linux on MicroSD, thanks to the awesome work by Justin Hammond (Fishwaldo)...

We download the Latest Release for Milk-V Duo S (SG2000)...

$ brew install lz4
$ lz4 ~/Downloads/duos_sd.img.lz4
## TODO: Write duos_sd.img to MicroSD with Balena Etcher

→ ls -l /Volumes/boot
total 17488
-rwxrwxrwx  1 Luppy  staff  3494900 Apr 24 11:33 System.map-5.10.4-20240329-1+
-rwxrwxrwx  1 Luppy  staff   125534 Apr 24 11:33 config-5.10.4-20240329-1+
drwxrwxrwx  1 Luppy  staff     2048 Apr 24 11:33 extlinux
drwxrwxrwx  1 Luppy  staff     2048 Apr 24 11:33 fdt
-rwxrwxrwx  1 Luppy  staff   388608 Apr 24 11:33 fip.bin
-rwxrwxrwx  1 Luppy  staff  4937389 Apr 24 11:33 vmlinuz-5.10.4-20240329-1+

→ ls -l /Volumes/boot/extlinux
total 4
-rwxrwxrwx  1 Luppy  staff  749 Apr 24 11:33 extlinux.conf

→ ls -l /Volumes/boot/fdt
total 4
drwxrwxrwx  1 Luppy  staff  2048 Apr 24 11:33 linux-image-duos-5.10.4-20240329-1+

→ ls -l /Volumes/boot/fdt/linux-image-duos-5.10.4-20240329-1+
total 44
-rwxrwxrwx  1 Luppy  staff  21575 Apr 24 11:33 cv181x_milkv_duos_sd.dtb

Here's the Boot Config...

→ cat /Volumes/boot/extlinux/extlinux.conf
## /boot/extlinux/extlinux.conf
##
## IMPORTANT WARNING
##
## The configuration of this file is generated automatically.
## Do not edit this file manually, use: u-boot-update

default l0
menu title U-Boot menu
prompt 1
timeout 50


label l0
  menu label Debian GNU/Linux trixie/sid 5.10.4-20240329-1+
  linux /vmlinuz-5.10.4-20240329-1+

  fdtdir /fdt/linux-image-duos-5.10.4-20240329-1+/

  append root=/dev/root console=ttyS0,115200 earlycon=sbi root=/dev/mmcblk0p2 rootwait rw

label l0r
  menu label Debian GNU/Linux trixie/sid 5.10.4-20240329-1+ (rescue target)
  linux /vmlinuz-5.10.4-20240329-1+

  fdtdir /fdt/linux-image-duos-5.10.4-20240329-1+/
  append root=/dev/root console=ttyS0,115200 earlycon=sbi root=/dev/mmcblk0p2 rootwait rw single

Yep Linux boots OK on Milk-V Duo S! (Together with OpenSBI and U-Boot Bootloader)

https://gist.github.com/lupyuen/01d409b7bde9607a96cd4d460e53330a

OpenSBI v0.9
   ____                    _____ ____ _____
  / __ \                  / ____|  _ \_   _|
 | |  | |_ __   ___ _ __ | (___ | |_) || |
 | |  | | '_ \ / _ \ '_ \ \___ \|  _ < | |
 | |__| | |_) |  __/ | | |____) | |_) || |_
  \____/| .__/ \___|_| |_|_____/|____/_____|
        | |
        |_|

Platform Name             : Milk-V DuoS
Platform Features         : mfdeleg
Platform HART Count       : 1
Platform IPI Device       : clint
Platform Timer Device     : clint
Platform Console Device   : uart8250
Platform HSM Device       : ---
Platform SysReset Device  : ---
Firmware Base             : 0x80000000
Firmware Size             : 132 KB
Runtime SBI Version       : 0.3

Domain0 Name              : root
Domain0 Boot HART         : 0
Domain0 HARTs             : 0*
Domain0 Region00          : 0x0000000074000000-0x000000007400ffff (I)
Domain0 Region01          : 0x0000000080000000-0x000000008003ffff ()
Domain0 Region02          : 0x0000000000000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address      : 0x0000000080200020
Domain0 Next Arg1         : 0x0000000080080000
Domain0 Next Mode         : S-mode
Domain0 SysReset          : yes

Boot HART ID              : 0
Boot HART Domain          : root
Boot HART ISA             : rv64imafdcvsux
Boot HART Features        : scounteren,mcounteren,time
Boot HART PMP Count       : 16
Boot HART PMP Granularity : 4096
Boot HART PMP Address Bits: 38
Boot HART MHPM Count      : 8
Boot HART MHPM Count      : 8
Boot HART MIDELEG         : 0x0000000000000222
Boot HART MEDELEG         : 0x000000000000b109
...
Debian GNU/Linux trixie/sid duos ttyS0
duos login: 

Let's dump the U-Boot Config...

https://gist.github.com/lupyuen/000b55a46336cddf217a589f469d60e2

U-Boot 2021.10-ga57aa1f2-dirty (May 07 2024 - 08:13:12 +0000) cvitek_cv181x
DRAM:  510 MiB
gd->relocaddr=0x9fbc6000. offset=0x1f9c6000
set_rtc_register_for_power
MMC:   cv-sd@4310000: 0, wifi-sd@4320000: 1
Loading Environment from FAT... mmc1 : finished tuning, code:53
OK
In:    serial
Out:   serial
Err:   serial
Net:   
Warning: ethernet@4070000 (eth0) using random MAC address - 0a:fa:e9:48:cc:c1
eth0: ethernet@4070000
Hit any key to stop autoboot:  0

cv181x_c906# printenv
arch=riscv
baudrate=115200
board=mars
board_name=mars
boot_a_script=load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} ${prefix}${script}; source ${scriptaddr}
boot_efi_binary=load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} efi/boot/bootriscv64.efi; if fdt addr ${fdt_addr_r}; then bootefi ${kernel_addr_r} ${fdt_addr_r};else bootefi ${kernel_addr_r} ${fdtcontroladdr};fi
boot_efi_bootmgr=if fdt addr ${fdt_addr_r}; then bootefi bootmgr ${fdt_addr_r};else bootefi bootmgr;fi
boot_extlinux=sysboot ${devtype} ${devnum}:${distro_bootpart} any ${scriptaddr} ${prefix}${boot_syslinux_conf}
boot_prefixes=/ /boot/
boot_script_dhcp=boot.scr.uimg
boot_scripts=boot.scr.uimg boot.scr
boot_syslinux_conf=extlinux/extlinux.conf
boot_targets=mmc0 dhcp pxe 
bootcmd=run distro_bootcmd || run sdboot || run sdbootauto
bootcmd_dhcp=devtype=dhcp; if dhcp ${scriptaddr} ${boot_script_dhcp}; then source ${scriptaddr}; fi;setenv efi_fdtfile ${fdtfile}; setenv efi_old_vci ${bootp_vci};setenv efi_old_arch ${bootp_arch};setenv bootp_vci PXEClient:Arch:00027:UNDI:003000;setenv bootp_arch 0x1b;if dhcp ${kernel_addr_r}; then tftpboot ${fdt_addr_r} dtb/${efi_fdtfile};if fdt addr ${fdt_addr_r}; then bootefi ${kernel_addr_r} ${fdt_addr_r}; else bootefi ${kernel_addr_r} ${fdtcontroladdr};fi;fi;setenv bootp_vci ${efi_old_vci};setenv bootp_arch ${efi_old_arch};setenv efi_fdtfile;setenv efi_old_arch;setenv efi_old_vci;
bootcmd_mmc0=devnum=0; run mmc_boot
bootcmd_pxe=dhcp; if pxe get; then pxe boot; fi
bootdelay=1
consoledev=ttyS0
cpu=generic
distro_bootcmd=for target in ${boot_targets}; do run bootcmd_${target}; done
efi_dtb_prefixes=/ /dtb/ /dtb/current/
fdt_addr_r=0x81200000
fdtcontroladdr=9f27f810
fdtfile=cv181x_milkv_duos_sd.dtb
fdtoverlay_addr_r=0x81300000
gatewayip=192.168.0.11
ipaddr=192.168.0.3
kernel_addr_r=0x80200000
kernel_comp_addr_r=0x81800000
kernel_comp_size=0x1000000
load_efi_dtb=load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} ${prefix}${efi_fdtfile}
mmc_boot=if mmc dev ${devnum}; then devtype=mmc; run scan_dev_for_boot_part; fi
netdev=eth0
netmask=255.255.255.0
othbootargs=earlycon=sbi riscv.fwsz=0x80000   loglevel=9
pxefile_addr_r=0x81400000
ramdisk_addr_r=0x84000000
root=root=/dev/mmcblk0p2 rootwait rw
scan_dev_for_boot=echo Scanning ${devtype} ${devnum}:${distro_bootpart}...; for prefix in ${boot_prefixes}; do run scan_dev_for_extlinux; run scan_dev_for_scripts; done;run scan_dev_for_efi;
scan_dev_for_boot_part=part list ${devtype} ${devnum} -bootable devplist; env exists devplist || setenv devplist 1; for distro_bootpart in ${devplist}; do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then run scan_dev_for_boot; fi; done; setenv devplist
scan_dev_for_efi=setenv efi_fdtfile ${fdtfile}; for prefix in ${efi_dtb_prefixes}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${efi_fdtfile}; then run load_efi_dtb; fi;done;run boot_efi_bootmgr;if test -e ${devtype} ${devnum}:${distro_bootpart} efi/boot/bootriscv64.efi; then echo Found EFI removable media binary efi/boot/bootriscv64.efi; run boot_efi_binary; echo EFI LOAD FAILED: continuing...; fi; setenv efi_fdtfile
scan_dev_for_extlinux=if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${boot_syslinux_conf}; then echo Found ${prefix}${boot_syslinux_conf}; run boot_extlinux; echo SCRIPT FAILED: continuing...; fi
scan_dev_for_scripts=for script in ${boot_scripts}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${script}; then echo Found U-Boot script ${prefix}${script}; run boot_a_script; echo SCRIPT FAILED: continuing...; fi; done
scriptaddr=0x81500000
sdboot=setenv bootargs ${reserved_mem} ${root} ${mtdparts} console=$consoledev,$baudrate $othbootargs;echo Boot from SD dev ${sddev} ...;mmc dev ${sddev} && fatload mmc ${sddev} ${uImage_addr} boot.sd;if test $? -eq 0; then bootm ${uImage_addr}#config-cv181x_milkv_duos_sd;fi;
sdbootauto=cvi_sd_boot;setenv bootargs ${reserved_mem} ${root} ${mtdparts} console=$consoledev,$baudrate $othbootargs;echo Boot from SD dev ${sddev} auto ...;mmc dev ${sddev} && fatload mmc ${sddev} ${uImage_addr} boot.sd;if test $? -eq 0; then bootm ${uImage_addr}#config-cv181x_milkv_duos_sd;fi;
sddev=0
serverip=192.168.56.101
stderr=serial
stdin=serial
stdout=serial
uImage_addr=0x81800000
update_addr=0x9fe00000
vendor=cvitek
Environment size: 4333/131068 bytes

Aha Ethernet Driver is available in U-Boot. Which means we can boot NuttX over TFTP yay!

$ net list

eth0 : ethernet@4070000 00:00:00:00:00:00 active

(See below for the U-Boot Commands)

Here's another Linux Image: https://github.com/logicethos/Milk-V_Duo_Linux2SD

Boot Milk-V Duo S over TFTP

Read the article...

We'll port NuttX by booting over TFTP, but let's test TFTP first. Here are the steps to boot Milk-V Duo S over TFTP...

https://lupyuen.github.io/articles/tftp#configure-u-boot-for-tftp

## Set the U-Boot TFTP Server
setenv tftp_server 192.168.31.10
printenv tftp_server

## If Initial RAM Disk is needed (like for Linux)...
## Set the RAM Disk Size (assume the max)
## setenv ramdisk_size 0x1000000
## printenv ramdisk_size

## Save the U-Boot Config for future reboots
saveenv

We load the NuttX Image over TFTP...

## Fetch the IP Address over DHCP
## Load the NuttX Image from TFTP Server
## kernel_addr_r=0x80200000
## tftp_server=192.168.x.x
dhcp ${kernel_addr_r} ${tftp_server}:Image-sg2000

## Load the Device Tree from TFTP Server
## fdt_addr_r=0x81200000
## TODO: Fix the Device Tree, it's not needed by NuttX
tftpboot ${fdt_addr_r} ${tftp_server}:jh7110-star64-pine64.dtb

## Set the RAM Address of Device Tree
## fdt_addr_r=0x81200000
## TODO: Fix the Device Tree, it's not needed by NuttX
fdt addr ${fdt_addr_r}

## If Initial RAM Disk is needed...
## Load the Intial RAM Disk from TFTP Server
## ramdisk_addr_r=0x81600000
## tftpboot ${ramdisk_addr_r} ${tftp_server}:initrd

## Boot the NuttX Image with the Device Tree
## kernel_addr_r=0x80200000
## fdt_addr_r=0x81200000
## TODO: Fix the Device Tree, it's not needed by NuttX
booti ${kernel_addr_r} - ${fdt_addr_r}

## For Linux: We need the RAM Disk Address
## ramdisk_addr_r=0x81600000
## ramdisk_size=0x1000000
## booti ${kernel_addr_r} ${ramdisk_addr_r}:${ramdisk_size} ${fdt_addr_r}

Which becomes this, mashed up in a single line...

## Boot NuttX Image over TFTP
dhcp ${kernel_addr_r} ${tftp_server}:Image-sg2000 ; tftpboot ${fdt_addr_r} ${tftp_server}:jh7110-star64-pine64.dtb ; fdt addr ${fdt_addr_r} ; booti ${kernel_addr_r} - ${fdt_addr_r}

Now we automate the TFTP Booting for future reboots...

## Add the Boot Command for TFTP
setenv bootcmd_tftp 'dhcp ${kernel_addr_r} ${tftp_server}:Image-sg2000 ; tftpboot ${fdt_addr_r} ${tftp_server}:jh7110-star64-pine64.dtb ; fdt addr ${fdt_addr_r} ; booti ${kernel_addr_r} - ${fdt_addr_r}'
## Check that it's correct
printenv bootcmd_tftp
## Save it for future reboots
saveenv

## Test the Boot Command for TFTP, then reboot
run bootcmd_tftp

## Remember the Original Boot Targets
setenv orig_boot_targets "$boot_targets"
## Should show `mmc0 dhcp pxe`
printenv orig_boot_targets
## Save it for future reboots
saveenv

## Prepend TFTP to the Boot Targets
setenv boot_targets "tftp $boot_targets"
## Should show `tftp mmc0 dhcp pxe`
printenv boot_targets
## Save it for future reboots
saveenv

What happens when we run it at the U-Boot Command Prompt?

This happens...

$ setenv tftp_server 192.168.31.10
$ printenv tftp_server
tftp_server=192.168.31.10

$ setenv ramdisk_size 0x1000000
$ printenv ramdisk_size
ramdisk_size=0x1000000

$ dhcp ${kernel_addr_r} ${tftp_server}:Image
Speed: 100, full duplex
BOOTP broadcast 1
BOOTP broadcast 2
*** Unhandled DHCP Option in OFFER/ACK: 43
*** Unhandled DHCP Option in OFFER/ACK: 43
DHCP client bound to address 192.168.31.47 (550 ms)
Using ethernet@4070000 device
TFTP from server 192.168.31.10; our IP address is 192.168.31.47
Filename 'Image'.
Load address: 0x80200000
Loading: #################################################################
. 1.2 MiB/s
done
Bytes transferred = 2097800 (200288 hex)

$ tftpboot ${fdt_addr_r} ${tftp_server}:jh7110-star64-pine64.dtb
Speed: 100, full duplex
Using ethernet@4070000 device
TFTP from server 192.168.31.10; our IP address is 192.168.31.47
Filename 'jh7110-star64-pine64.dtb'.
Load address: 0x81200000
Loading: ####
. 1.1 MiB/s
done
Bytes transferred = 50235 (c43b hex)

$ fdt addr ${fdt_addr_r}

$ booti ${kernel_addr_r} - ${fdt_addr_r}
## Flattened Device Tree blob at 81200000
   Booting using the fdt blob at 0x81200000
   Loading Ramdisk to 9e27f000, end 9f27f000 ... OK
   Loading Device Tree to 000000009e26f000, end 000000009e27e43a ... OK

Starting kernel ...

NuttX Kernel hangs, but that's OK! We haven't modified NuttX Kernel for SG2000 yet. Let's print something...

UART Controller for SG2000

Read the article...

According to the SG2000 Reference Manual, the UART Controller Base Addresses are...

GPIO Module Base Address
UART0 0x04140000
UART1 0x04150000
UART2 0x04160000
UART3 0x04170000
UART4 0x041C0000
RTCSYS_UART 0x05022000

We'll print to UART0 in NuttX.

What UART Controller is inside SG2000 / Milk-V Duo S?

According to the OpenSBI Log above: The UART Controller is uart8250.

Which is supported by NuttX yay!

Let's modify the NuttX Boot Code to write to the UART Output Register. We'll do this in RISC-V Assembly...

Print to SG2000 UART in RISC-V Assembly

Read the article...

Here's how we print to 8250 UART in RISC-V Assembly...

https://lupyuen.github.io/articles/nuttx2#print-to-qemu-console

From the previous section: SG2000 UART0 Controller is at 0x04140000. We'll write to that address, to print something to Serial Console.

We insert this RISC-V Assembly at the top of the NuttX Boot Code...

https://github.com/lupyuen2/wip-nuttx/blob/sg2000/arch/risc-v/src/bl808/bl808_head.S#L70-L89

/* RISC-V Boot Code for Apache NuttX RTOS */
real_start:

  /* Print `123` to UART */
  /* Load UART Base Address to Register t0 */
  li  t0, 0x04140000

  /* Load `1` to Register t1 */
  li  t1, 0x31
  /* Store byte from Register t1 to UART Base Address, Offset 0 */
  sb  t1, 0(t0)

  /* Load `2` to Register t1 */
  li  t1, 0x32
  /* Store byte from Register t1 to UART Base Address, Offset 0 */
  sb  t1, 0(t0)

  /* Load `3` to Register t1 */
  li  t1, 0x33
  /* Store byte from Register t1 to UART Base Address, Offset 0 */
  sb  t1, 0(t0)

Which will print 123 when NuttX boots. Let's build and test this!

Build Apache NuttX RTOS for Milk-V Duo S

Read the article...

Follow these steps to build (work-in-progress) Apache NuttX RTOS for SG2000 / Milk-V Duo S...

## TODO: Set PATH
export PATH="$HOME/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8-x86_64-apple-darwin/bin:$PATH"

set -e  #  Exit when any command fails
set -x  #  Echo commands

## Build NuttX
function build_nuttx {

  ## Go to NuttX Folder
  pushd ../nuttx

  ## Build NuttX
  make -j 8

  ## Return to previous folder
  popd
}

## Build Apps Filesystem
function build_apps {

  ## Go to NuttX Folder
  pushd ../nuttx

  ## Build Apps Filesystem
  make -j 8 export
  pushd ../apps
  ./tools/mkimport.sh -z -x ../nuttx/nuttx-export-*.tar.gz
  make -j 8 import
  popd

  ## Return to previous folder
  popd
}

## Download WIP NuttX for SG2000 (based on Ox64 BL808)
git clone --branch sg2000 \
  https://github.com/lupyuen2/wip-nuttx \
  nuttx
git clone --branch sg2000 \
  https://github.com/lupyuen2/wip-nuttx-apps \
  apps
cd nuttx

## Pull updates
git pull && git status && hash1=`git rev-parse HEAD`
pushd ../apps
git pull && git status && hash2=`git rev-parse HEAD`
popd
echo NuttX Source: https://github.com/apache/nuttx/tree/$hash1 >nuttx.hash
echo NuttX Apps: https://github.com/apache/nuttx-apps/tree/$hash2 >>nuttx.hash

## Show the version of GCC
riscv64-unknown-elf-gcc -v

## Configure build
tools/configure.sh ox64:nsh

## Build NuttX
build_nuttx

## Build Apps Filesystem
build_apps

## Generate Initial RAM Disk
genromfs -f initrd -d ../apps/bin -V "NuttXBootVol"

## Show the size
riscv64-unknown-elf-size nuttx

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

## Prepare a Padding with 64 KB of zeroes
head -c 65536 /dev/zero >/tmp/nuttx.pad

## Append Padding and Initial RAM Disk to NuttX Kernel
cat nuttx.bin /tmp/nuttx.pad initrd \
  >Image

## Copy the config
cp .config nuttx.config

## Dump the disassembly to nuttx.S
riscv64-unknown-elf-objdump \
  --syms --source --reloc --demangle --line-numbers --wide \
  --debugging \
  nuttx \
  >nuttx.S \
  2>&1

## Dump the init disassembly to init.S
riscv64-unknown-elf-objdump \
  --syms --source --reloc --demangle --line-numbers --wide \
  --debugging \
  ../apps/bin/init \
  >init.S \
  2>&1

## Dump the hello disassembly to hello.S
riscv64-unknown-elf-objdump \
  --syms --source --reloc --demangle --line-numbers --wide \
  --debugging \
  ../apps/bin/hello \
  >hello.S \
  2>&1

## Copy NuttX Image to TFTP Server
scp Image tftpserver:/tftpboot/Image-sg2000
ssh tftpserver ls -l /tftpboot/Image-sg2000

Build Outputs are here: https://github.com/lupyuen2/wip-nuttx/releases/tag/sg2000-1

We have copied the NuttX Image to our TFTP Server. Let's boot this on Milk-V Duo S...

Apache NuttX RTOS boots a tiny bit on Milk-V Duo S

Read the article...

Earlier we have...

  • Inserted the RISC-V Boot Code to print 123 (at NuttX Startup)

  • Compiled Apache NuttX RTOS for Milk-V Duo S

  • Copied the NuttX Image to our TFTP Server

Let's boot NuttX over TFTP, with a little help from U-Boot Bootloader! Run these commands at the U-Boot Command Prompt...

https://gist.github.com/lupyuen/78b54326daf0894a2c23ab6d2c03456d

## TODO: Change to your TFTP Server IP Address
$ setenv tftp_server 192.168.31.10

## Download NuttX Image from TFTP Server
$ dhcp ${kernel_addr_r} ${tftp_server}:Image-sg2000
Speed: 100, full duplex
BOOTP broadcast 1
BOOTP broadcast 2
*** Unhandled DHCP Option in OFFER/ACK: 43
*** Unhandled DHCP Option in OFFER/ACK: 43
DHCP client bound to address 192.168.31.243 (424 ms)
Using ethernet@4070000 device
TFTP from server 192.168.31.10; our IP address is 192.168.31.243
Filename 'Image-sg2000'.
Load address: 0x80200000
Loading: #################################################################
. 1.2 MiB/s
done
Bytes transferred = 14195281 (d89a51 hex)

## TODO: NuttX doesn't need the Device Tree. Remove this.
$ tftpboot ${fdt_addr_r} ${tftp_server}:jh7110-star64-pine64.dtb
Speed: 100, full duplex
Using ethernet@4070000 device
TFTP from server 192.168.31.10; our IP address is 192.168.31.243
Filename 'jh7110-star64-pine64.dtb'.
Load address: 0x81200000
Loading: ####
. 1.2 MiB/s
done
Bytes transferred = 50235 (c43b hex)

## TODO: NuttX doesn't need the Device Tree. Remove this.
$ fdt addr ${fdt_addr_r}

## Boot NuttX from RAM. RAM Disk Address must be `-`!
$ booti ${kernel_addr_r} - ${fdt_addr_r}

## For Linux: We need the RAM Disk Address
## booti ${kernel_addr_r} ${ramdisk_addr_r}:${ramdisk_size} ${fdt_addr_r}

NuttX boots a tiny bit, and prints 123 yay!

## Boot NuttX from RAM
$ booti ${kernel_addr_r} - ${fdt_addr_r}

## Flattened Device Tree blob at 81200000
Booting using the fdt blob at 0x81200000
Loading Ramdisk to 9fe00000, end 9fe00000 ... OK
Loading Device Tree to 000000009f26f000, end 000000009f27e43a ... OK

Starting kernel ...
123

Our NuttX Boot Code is actually running on SG2000 / Milk-V Duo S!

Coming up...

  1. Fix the Boot Address of NuttX, so the rest of NuttX can start

  2. Configure the 16550 UART Driver for NuttX, so can see the Console Output

TODO: Can we run expect with screen to automate the testing of NuttX on SG2000?

TODO: If we prefer to boot NuttX with MicroSD instead of TFTP, try this MicroSD Multiplexer (USB / FTDI)

Set the NuttX Memory Map for SG2000

Read the article...

From the U-Boot Bootloader Config above: We see that SG2000 boots at this address...

kernel_addr_r=0x80200000

Thus we define the NuttX Memory Map for SG2000 like so...

https://github.com/lupyuen2/wip-nuttx/commit/c6c0bd3882a855420acf0d53fa9d37bbd9d125b2

NuttX Kernel will boot at 0x8020_0000, NuttX Apps will run at Virtual Address 0xC000_0000.

Here's the NuttX Config: boards/risc-v/bl808/ox64/configs/nsh/defconfig

## Kernel RAM
CONFIG_RAM_START=0x80200000
CONFIG_RAM_SIZE=1048576

## Kernel Paged Pool (Allocated to NuttX Apps)
CONFIG_ARCH_PGPOOL_PBASE=0x80600000
CONFIG_ARCH_PGPOOL_VBASE=0x80600000
CONFIG_ARCH_PGPOOL_SIZE=4194304

## Virtual Memory for NuttX App Code
CONFIG_ARCH_TEXT_VBASE=0xC0000000
CONFIG_ARCH_TEXT_NPAGES=128

## Virtual Memory for NuttX App Data
CONFIG_ARCH_DATA_VBASE=0xC0100000
CONFIG_ARCH_DATA_NPAGES=128

## Virtual Memory for NuttX App Heap
CONFIG_ARCH_HEAP_VBASE=0xC0200000
CONFIG_ARCH_HEAP_NPAGES=128

And here's the NuttX Linker Script: boards/risc-v/bl808/ox64/scripts/ld.script

MEMORY {
    kflash (rx) : ORIGIN = 0x80200000, LENGTH = 2048K   /* w/ cache */
    ksram (rwx) : ORIGIN = 0x80400000, LENGTH = 2048K   /* w/ cache */
    pgram (rwx) : ORIGIN = 0x80600000, LENGTH = 4096K   /* w/ cache */
    ramdisk (rwx) : ORIGIN = 0x80A00000, LENGTH = 16M   /* w/ cache */
}
...
SECTIONS {
  . = 0x80200000;

Disable the PLIC Interrupt Controller

Read the article...

Most RISC-V SBCs (Ox64, Star64) will manage Interrupts with a Platform-Level Interrupt Controller (PLIC). For now, let's disable PLIC in NuttX...

https://github.com/lupyuen2/wip-nuttx/commit/6d66caa1408d7a7d7b21b0e876ce32ceb5b93ec4

Later we'll dump the SG2000 Linux Device Tree to understand the Interrupt Controller.

Select the NuttX Driver for 16550 UART

Read the article...

From the OpenSBI Log above: We see that SG2000 operates with a 8250 UART Controller.

Thus we select the NuttX Driver for 16550 UART, which is compatible with 8250...

https://github.com/lupyuen2/wip-nuttx/commit/8f8831d15d6ddc913e6dd1c6c49fb0067640f6ec

Here's the NuttX Config: boards/risc-v/bl808/ox64/configs/nsh/defconfig

CONFIG_16550_ADDRWIDTH=0
CONFIG_16550_REGINCR=4
CONFIG_16550_UART0=y
CONFIG_16550_UART0_BASE=0x04140000
CONFIG_16550_UART0_CLOCK=23040000
CONFIG_16550_UART0_SERIAL_CONSOLE=y
CONFIG_16550_UART=y
CONFIG_16550_WAIT_LCR=y
CONFIG_SERIAL_UART_ARCH_MMIO=y

Don't update the NuttX Config File directly! We ran make menuconfig to generate the above file...

## Update NuttX Config
make menuconfig \
  && make savedefconfig \
  && grep -v CONFIG_HOST defconfig \
  >boards/risc-v/bl808/ox64/configs/nsh/defconfig

To find the menuconfig settings: Press "/" and enter the name of the setting, like "16550_ADDRWIDTH". This ensures that the Kconfig Dependencies are correctly updated.

How did we get IRQ 69 for UART?

https://github.com/lupyuen2/wip-nuttx/commit/122c717447f81c310a4fb082101213ad338dfb0e

CONFIG_16550_UART0_IRQ=69

We saw this in the SG2000 Reference Manual...

3.1 Interrupt Subsystem

Table 3.2: Interrupt number and Interrupt source mapping for Master RISCV C906 @ 1.0Ghz

Int #44: UART0

Linx Device Tree also says UART0 IRQ is 44 (0x2C)...

serial@04140000 {
  compatible = "snps,dw-apb-uart";
  reg = <0x00 0x4140000 0x00 0x1000>;
  clock-frequency = <0x17d7840>;
  reg-shift = <0x02>;
  reg-io-width = <0x04>;
  status = "okay";
  interrupts = <0x2c 0x04>;
  interrupt-parent = <0x04>;
};

Thus we compute NuttX IRQ = 25 + RISC-V IRQ = 69

TODO: Fix the UART Clock: 16550_UART0_CLOCK

Enable Logging for NuttX Scheduler and Binary Loader

Read the article...

For easier troubleshooting: We enable the Logging for NuttX Scheduler and Binary Loader...

https://github.com/lupyuen2/wip-nuttx/commit/4cee79630359f6b31fc9fa40f31bb476c8bc4d47

Here's the NuttX Config: boards/risc-v/bl808/ox64/configs/nsh/defconfig

CONFIG_DEBUG_BINFMT=y
CONFIG_DEBUG_BINFMT_ERROR=y
CONFIG_DEBUG_BINFMT_WARN=y
CONFIG_DEBUG_SCHED=y
CONFIG_DEBUG_SCHED_ERROR=y
CONFIG_DEBUG_SCHED_INFO=y
CONFIG_DEBUG_SCHED_WARN=y

Remember: Always use make menuconfig to update the settings!

NuttX Crash Dump on SG2000

Read the article...

We apply the fixes above. Now NuttX boots some more on RISC-V SG2000 SoC / Milk-V Duo S. And shows our very first NuttX Crash Dump yay!

https://gist.github.com/lupyuen/594f0df20d39001bac171412d594d517

## Flattened Device Tree blob at 81200000
   Booting using the fdt blob at 0x81200000
   Loading Ramdisk to 9fe00000, end 9fe00000 ... OK
   Loading Device Tree to 000000009f26f000, end 000000009f27e43a ... OK

Starting kernel ...

123ABCnx_start: Entry
uart_register: Registering /dev/console
uart_register: Registering /dev/ttyS0
work_start_lowpri: Starting low-priority kernel worker thread(s)
nxtask_activate: lpwork pid=1,TCB=0x80408130
nxtask_activate: AppBringUp pid=2,TCB=0x80408740
nx_start_application: Starting init task: /system/bin/init
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
_assert: Current Version: NuttX  12.4.0 f37a380-dirty May  7 2024 10:31:33 risc-v
_assert: Assertion failed 0x17 == (insn & 0x7F): at file: machine/risc-v/arch_elf.c:494 task: AppBringUp process: Kernel 0x80200f34
up_dump_register: EPC: 000000008021087a
up_dump_register: A0: 0000000080401b70 A1: 00000000000001ee A2: 0000000080228ef8 A3: 0000000000000000
up_dump_register: A4: 0000000000000017 A5: 0000000000000002 A6: 000000000000ab9c A7: fffffffffffffff8
up_dump_register: T0: 000000000000002e T1: 0000000000000007 T2: 00000000000001ff T3: 000000008040c3fc
up_dump_register: T4: 000000008040c3f0 T5: 0000000000000009 T6: 000000000000002a
up_dump_register: S0: 0000000000000000 S1: 0000000080408740 S2: 0000000000000017 S3: 0000000000000000
up_dump_register: S4: 0000000080228ef8 S5: 0000000080228de8 S6: 0000000080401e10 S7: 8000000201842022
up_dump_register: S8: 00000000000001ee S9: 000000008040b9a0 S10: 0000000000000070 S11: 000000008040b990
up_dump_register: SP: 000000008040c330 FP: 0000000000000000 TP: 0000000000000000 RA: 000000008021087a
dump_stack: User Stack:
dump_stack:   base: 0x8040c030
dump_stack:   size: 00002000
dump_stack:     sp: 0x8040c330

What's this Assertion Failure?

_assert: Assertion failed 0x17 == (insn & 0x7F):
at file: machine/risc-v/arch_elf.c:494
task: AppBringUp process: Kernel 0x80200f34

Oops we goofed and used the wrong U-Boot Command...

## Nope! This won't work for NuttX. RAM Disk Address must be `-`!
setenv tftp_server 192.168.31.10 ; dhcp ${kernel_addr_r} ${tftp_server}:Image-sg2000 ;
tftpboot ${fdt_addr_r} ${tftp_server}:jh7110-star64-pine64.dtb ; fdt addr ${fdt_addr_r} ;
booti ${kernel_addr_r} ${ramdisk_addr_r}:${ramdisk_size} ${fdt_addr_r}

Which overwrites the NuttX Image in RAM. Watch what happens when we use the correct U-Boot Command...

NuttX Kernel Boots OK on SG2000

NuttX Kernel Boots OK on SG2000

Read the article...

Earlier we made these fixes...

  1. We set the NuttX Memory Map for SG2000: nsh/defconfig

    ## Kernel RAM
    CONFIG_RAM_START=0x80200000
    CONFIG_RAM_SIZE=1048576
  2. Also the NuttX Linker Script: ld.script

    MEMORY {
      kflash (rx) : ORIGIN = 0x80200000, LENGTH = 2048K   /* w/ cache */
      ...
    SECTIONS {
      . = 0x80200000;
  3. We select the NuttX Driver for 16550 UART: nsh/defconfig

    CONFIG_16550_REGINCR=4
    CONFIG_16550_UART0=y
    CONFIG_16550_UART0_BASE=0x04140000
    CONFIG_16550_UART0_SERIAL_CONSOLE=y
    CONFIG_16550_UART=y
    CONFIG_16550_WAIT_LCR=y
    CONFIG_SERIAL_UART_ARCH_MMIO=y
  4. Enable Logging for NuttX Scheduler and Binary Loader: nsh/defconfig

    CONFIG_DEBUG_BINFMT=y
    CONFIG_DEBUG_BINFMT_ERROR=y
    CONFIG_DEBUG_BINFMT_WARN=y
    CONFIG_DEBUG_SCHED=y
    CONFIG_DEBUG_SCHED_ERROR=y
    CONFIG_DEBUG_SCHED_INFO=y
    CONFIG_DEBUG_SCHED_WARN=y
  5. And disable the PLIC Interrupt Controller (until we figure it out)

Also we used the correct U-Boot Command...

## This works OK for NuttX. RAM Disk Address must be `-`!
setenv tftp_server 192.168.31.10 ; dhcp ${kernel_addr_r} ${tftp_server}:Image-sg2000 ;
tftpboot ${fdt_addr_r} ${tftp_server}:jh7110-star64-pine64.dtb ; fdt addr ${fdt_addr_r} ; 
booti ${kernel_addr_r} - ${fdt_addr_r}

NuttX Kernel boots OK on SG2000 yay!

https://gist.github.com/lupyuen/aaa0a6646490d45e5cd99b781cbe59f8

## Flattened Device Tree blob at 81200000
   Booting using the fdt blob at 0x81200000
   Loading Device Tree to 000000009f26f000, end 000000009f27e43a ... OK

Starting kernel ...

123ABCnx_start: Entry
uart_register: Registering /dev/console
uart_register: Registering /dev/ttyS0
work_start_lowpri: Starting low-priority kernel worker thread(s)
nxtask_activate: lpwork pid=1,TCB=0x80408130
nxtask_activate: AppBringUp pid=2,TCB=0x80408740
nx_start_application: Starting init task: /system/bin/init
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
nxtask_activate: /system/bin/init pid=3,TCB=0x80409140
nxtask_exit: AppBringUp pid=2,TCB=0x80408740

(Watch the Demo on YouTube)

But where's the NuttX Shell?

We won't see the NuttX Shell until we fix the Interrupt Controller for SG2000. Which is NOT documented!

Let's sniff around and find out how it works...

Dump the SG2000 Linux Device Tree

Read the article...

Let's dump the SG2000 Linux Device Tree to understand the Interrupt Controller.

From the SG2000 Debian Release: https://github.com/Fishwaldo/sophgo-sg200x-debian/releases

We pick the Latest Release for Milk-V Duo S: https://github.com/Fishwaldo/sophgo-sg200x-debian/releases/download/v1.2.0/duos_sd.img.lz4

We copy out the SG2000 Device Tree Binary: cv181x_milkv_duos_sd.dtb

And convert it to Device Tree Source: cv181x_milkv_duos_sd.dts

## Convert the SG2000 Device Tree
dtc \
  -o cv181x_milkv_duos_sd.dts \
  -O dts \
  -I dtb \
  cv181x_milkv_duos_sd.dtb

Interrupt Controller for SG2000

Read the article...

We dumped the SG2000 Linux Device Tree. Let's extract the Interrupt Controller to understand it.

Based on the SG2000 Device Tree: cv181x_milkv_duos_sd.dts

cpus {
  #address-cells = <0x01>;
  #size-cells = <0x00>;
  timebase-frequency = <0x17d7840>;

  cpu-map {

    cluster0 {

      core0 {
        cpu = <0x01>;
      };
    };
  };

  cpu@0 {
    device_type = "cpu";
    reg = <0x00>;
    status = "okay";
    compatible = "riscv";
    riscv,isa = "rv64imafdvcsu";
    mmu-type = "riscv,sv39";
    clock-frequency = <0x17d7840>;

    interrupt-controller {
      #interrupt-cells = <0x01>;
      interrupt-controller;
      compatible = "riscv,cpu-intc";
      phandle = <0x16>;
    };
  };
};

soc {
  #address-cells = <0x02>;
  #size-cells = <0x02>;
  compatible = "simple-bus";
  ranges;

  interrupt-controller@70000000 {
    riscv,ndev = <0x65>;
    riscv,max-priority = <0x07>;
    reg-names = "control";
    reg = <0x00 0x70000000 0x00 0x4000000>;
    interrupts-extended = <0x16 0xffffffff 0x16 0x09>;
    interrupt-controller;
    compatible = "riscv,plic0";
    #interrupt-cells = <0x02>;
    #address-cells = <0x00>;
    phandle = <0x04>;
  };

  clint@74000000 {
    interrupts-extended = <0x16 0x03 0x16 0x07>;
    reg = <0x00 0x74000000 0x00 0x10000>;
    compatible = "riscv,clint0";
    clint,has-no-64bit-mmio;
  };
};

We see that PLIC is at 0x7000_0000, CLINT at 0x7400_0000. Let's implement this in NuttX...

Fix the PLIC Interrupt Controller for SG2000

Read the article...

Based on the PLIC Address from above: We fix the PLIC Interrupt Controller for SG2000...

https://github.com/lupyuen2/wip-nuttx/commit/f5f1aeac36350b8149fc2a77c817217711f082f6

Platform-Level Interrupt Controller

Now we see a bit more NuttX...

https://gist.github.com/lupyuen/922e6379375fbc5d775d1e83cac4deb5

Starting kernel ...

123ABCnx_start: Entry
uart_register: Registering /dev/console
uart_register: Registering /dev/ttyS0
work_start_lowpri: Starting low-priority kernel worker thread(s)
nxtask_activate: lpwork pid=1,TCB=0x80409130
nxtask_activate: AppBringUp pid=2,TCB=0x80409740
nx_start_application: Starting init task: /system/bin/init
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
nxtask_activate: /system/bin/init pid=3,TCB=0x8040b730
nxtask_exit: AppBringUp pid=2,TCB=0x80409740

Nuttnx_start: CPU0: Beginning Idle Loop

Why did it stop?

Duh we set the wrong UART0 IRQ! Here's the fix...

https://github.com/lupyuen2/wip-nuttx/commit/122c717447f81c310a4fb082101213ad338dfb0e

CONFIG_16550_UART0_IRQ=69

How did we get IRQ 69 for UART?

We saw this in the SG2000 Reference Manual...

3.1 Interrupt Subsystem

Table 3.2: Interrupt number and Interrupt source mapping for Master RISCV C906 @ 1.0Ghz

Int #44: UART0

Linx Device Tree also says UART0 IRQ is 44 (0x2C)...

serial@04140000 {
  compatible = "snps,dw-apb-uart";
  reg = <0x00 0x4140000 0x00 0x1000>;
  clock-frequency = <0x17d7840>;
  reg-shift = <0x02>;
  reg-io-width = <0x04>;
  status = "okay";
  interrupts = <0x2c 0x04>;
  interrupt-parent = <0x04>;
};

Thus we compute NuttX IRQ = 25 + RISC-V IRQ = 69

NuttX Shell runs OK

NuttX Shell runs OK on SG2000

Read the article...

Earlier we made these fixes...

  1. We dumped the Linux Device Tree for SG2000...

    ## Convert the SG2000 Device Tree
    dtc \
      -o cv181x_milkv_duos_sd.dts \
      -O dts \
      -I dtb \
      cv181x_milkv_duos_sd.dtb
  2. Snooped the PLIC Interrupt Controller in the Device Tree: cv181x_milkv_duos_sd.dts

    interrupt-controller@70000000 {
      riscv,ndev = <0x65>;
      riscv,max-priority = <0x07>;
      reg-names = "control";
      reg = <0x00 0x70000000 0x00 0x4000000>;
      interrupts-extended = <0x16 0xffffffff 0x16 0x09>;
      interrupt-controller;
      compatible = "riscv,plic0";
  3. And fixed the NuttX Driver for PLIC Interrupts: bl808_memorymap.h

    // Base Address of PLIC Interrupt Controller
    #define BL808_PLIC_BASE 0x70000000ul

After fixing the Interrupt Controller and UART Interrupt: NuttX Kernel now boots all the way to NuttX Shell yay!

https://gist.github.com/lupyuen/b778986ba87c18067cd993b92c673634

Starting kernel ...

123ABCnx_start: Entry
uart_register: Registering /dev/console
uart_register: Registering /dev/ttyS0
work_start_lowpri: Starting low-priority kernel worker thread(s)
nxtask_activate: lpwork pid=1,TCB=0x80409130
nxtask_activate: AppBringUp pid=2,TCB=0x80409740
nx_start_application: Starting init task: /system/bin/init
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
nxtask_activate: /system/bin/init pid=3,TCB=0x8040b730
nxtask_exit: AppBringUp pid=2,TCB=0x80409740

NuttShell (NSH) NuttX-12.4.0
nsh> nx_start: CPU0: Beginning Idle Loop
ls
posix_spawn: pid=0xc0202968 path=ls file_actions=0xc0202970 attr=0xc0202978 argv=0xc0202a18
exec_internal: ERROR: Failed to load program 'ls': -2
nxposix_spawn_exec: ERROR: exec failed: 2
/:
 dev/
 proc/
 system/
nsh> 
nsh> uname -a
posix_spawn: pid=0xc0202968 path=uname file_actions=0xc0202970 attr=0xc0202978 argv=0xc0202a18
exec_internal: ERROR: Failed to load program 'uname': -2
nxposix_spawn_exec: ERROR: exec failed: 2
NuttX 12.4.0 122c717 May  8 2024 18:13:30 risc-v ox64
nsh> 
nsh> free
posix_spawn: pid=0xc0202968 path=free file_actions=0xc0202970 attr=0xc0202978 argv=0xc0202a18
exec_internal: ERROR: Failed to load program 'free': -2
nxposix_spawn_exec: ERROR: exec failed: 2
                 total       used       free    maxused    maxfree  nused  nfree
      Kmem:    2065400      14296    2051104      76632    2049392     36      3
      Page:   20971520     647168   20324352   20324352
nsh> 
nsh> ls /dev
posix_spawn: pid=0xc0202968 path=ls file_actions=0xc0202970 attr=0xc0202978 argv=0xc0202a18
exec_internal: ERROR: Failed to load program 'ls': -2
nxposix_spawn_exec: ERROR: exec failed: 2
/dev:
 console
 null
 ram0
 ttyS0
 zero
nsh> 
nsh> 

What about the rest of NuttX?

NuttX OSTest is the perfect way to test everything in NuttX...

nsh> ostest
user_main: mutex test
riscv_exception:
  EXCEPTION: Load access fault
  MCAUSE:    5
  EPC:       802189ce
  MTVAL:     0000000000000000
  Segmentation fault in PID 7: ostest

Sadly we're hitting a RISC-V Exception: Load Access Fault. Needs more troubleshooting sigh.

(See the Complete Log)

U-Boot Commands for Milk-V Duo S

Read the article...

Here are the U-Boot Commands available for Milk-V Duo S...

https://gist.github.com/lupyuen/000b55a46336cddf217a589f469d60e2

cv181x_c906# help

?         - alias for 'help'
base      - print or set address offset
bdinfo    - print Board Info structure
blkcache  - block cache diagnostics and control
boot      - boot default, i.e., run 'bootcmd'
bootd     - boot default, i.e., run 'bootcmd'
bootefi   - Boots an EFI payload from memory
bootelf   - Boot from an ELF image in memory
booti     - boot Linux kernel 'Image' format from memory
bootm     - boot application image from memory
bootp     - boot image via network using BOOTP/TFTP protocol
bootvx    - Boot vxWorks from an ELF image
cmp       - memory compare
cp        - memory copy
cpu       - display information about CPUs
cvi_sd_boot- boot from SD card
cvi_update- cvi_update [eth, sd, usb]- check boot status and update if necessary

cvi_utask - bootloader control block command
dcache    - enable or disable data cache
dhcp      - boot image via network using DHCP/TFTP protocol
echo      - echo args to console
efuser    - Read efuse
efuser_dump- Read/Dump efuse
efusew    - Write efuse
efusew_word- Write word to efuse
env       - environment handling commands
erase     - erase FLASH memory
exit      - exit script
ext2load  - load binary file from a Ext2 filesystem
ext2ls    - list files in a directory (default /)
ext4load  - load binary file from a Ext4 filesystem
ext4ls    - list files in a directory (default /)
ext4size  - determine a file's size
false     - do nothing, unsuccessfully
fatinfo   - print information about filesystem
fatload   - load binary file from a dos filesystem
fatls     - list files in a directory (default /)
fatmkdir  - create a directory
fatrm     - delete a file
fatsize   - determine a file's size
fatwrite  - write file into a dos filesystem
fdt       - flattened device tree utility commands
flinfo    - print FLASH memory information
fstype    - Look up a filesystem type
fstypes   - List supported filesystem types
go        - start application at address 'addr'
help      - print command description/usage
icache    - enable or disable instruction cache
iminfo    - print header information for application image
ln        - Create a symbolic link
load      - load binary file from a filesystem
loadb     - load binary file over serial line (kermit mode)
loadx     - load binary file over serial line (xmodem mode)
loady     - load binary file over serial line (ymodem mode)
loop      - infinite loop on address range
ls        - list files in a directory (default /)
md        - memory display
mdio      - MDIO utility commands
mii       - MII utility commands
mm        - memory modify (auto-incrementing address)
mmc       - MMC sub system
mmcinfo   - display MMC info
mw        - memory write (fill)
net       - NET sub-system
nfs       - boot image via network using NFS protocol
nm        - memory modify (constant address)
panic     - Panic with optional message
part      - disk partition related commands
ping      - send ICMP ECHO_REQUEST to network host
printenv  - print environment variables
protect   - enable or disable FLASH write protection
pxe       - commands to get and boot from pxe files
random    - fill memory with random pattern
reset     - Perform RESET of the CPU
run       - run commands in an environment variable
save      - save file to a filesystem
saveenv   - save environment variables to persistent storage
setenv    - set environment variables
setexpr   - set environment variable as the result of eval expression
showvar   - print local hushshell variables
size      - determine a file's size
sleep     - delay execution for some time
source    - run script from memory
sysboot   - command to get and boot from syslinux files
test      - minimal test like /bin/sh
tftpboot  - boot image via network using TFTP protocol
true      - do nothing, successfully
version   - print monitor, compiler and linker version