QuickJS JavaScript Engine on a Real-Time Operating System (Apache NuttX RTOS)

📝 18 Feb 2024

QuickJS JavaScript Engine on a Real-Time Operating System (Apache NuttX RTOS)

(Try the Online Demo)

(Watch the Demo on YouTube)

QuickJS is a small JavaScript Engine that supports POSIX Functions.

Apache NuttX RTOS is a tiny Real-Time Operating System (for all kinds of devices) that’s compatible with POSIX.

Can we run QuickJS on NuttX? And Blink the LED in 4 lines of JavaScript?

// Blink the NuttX LED, on then off
const ULEDIOC_SETALL = 0x1d03;
const fd = os.open("/dev/userleds", os.O_WRONLY);
os.ioctl(fd, ULEDIOC_SETALL, 1);
os.ioctl(fd, ULEDIOC_SETALL, 0);

Let’s do it! In this article we…

QuickJS is perfect for Iterative, Interactive Experiments on NuttX! We go hands-on (fingers too)…

QuickJS JavaScript Engine in Ox64 NuttX Emulator

§1 QuickJS on NuttX Emulator

Click here to try QuickJS JavaScript Engine in NuttX Emulator (pic above)…

Now we do some Finger Exercises (sorry copy-pasta won’t work in the Emulator)

  1. To start QuickJS: Enter this at the NSH Prompt

    qjs
  2. At the QuickJS Prompt: We define the NuttX LED Command

    ULEDIOC_SETALL = 0x1d03

    (About ULEDIOC_SETALL)

  3. Next we open the NuttX LED Device (write-only)…

    fd = os.open("/dev/userleds", os.O_WRONLY)
  4. Watch what happens when we Flip On the LED

    os.ioctl(fd, ULEDIOC_SETALL, 1)

    GPIO 29 turns Green! (Pic above)

  5. When we Flip Off the LED

    os.ioctl(fd, ULEDIOC_SETALL, 0)

    GPIO 29 goes back to Normal!

  6. Our Demo does this…

    NuttShell (NSH) NuttX-12.4.0-RC0
    nsh> qjs
    QuickJS - Type "\h" for help
    
    ## Define the NuttX LED Command
    qjs > ULEDIOC_SETALL = 0x1d03
    7427
    
    ## Open the NuttX LED Device (write-only)
    qjs > fd = os.open("/dev/userleds", os.O_WRONLY)
    3
    
    ## Flip LED On: GPIO 29 turns Green
    qjs > os.ioctl(fd, ULEDIOC_SETALL, 1)
    
    ## Flip LED Off: GPIO 29 goes back to normal
    qjs > os.ioctl(fd, ULEDIOC_SETALL, 0)

    (See the Complete Log)

    (Watch the Demo on YouTube)

Erm our fingers are hurting?

Try this Non-Interactive JavaScript with QuickJS…

nsh> qjs --std /system/bin/blink.js

(See the Blinky JavaScript)

(Option “--std” will import the POSIX Functions)

Wow… A Blinky in JavaScript?

Yep we flipped this NuttX Blinky App from C to Interactive JavaScript!

Does it work on Real Hardware?

The exact same QuickJS blinks a Real LED on Ox64 BL808 SBC, based on 64-bit RISC-V. (We’ll come back to this)

How did we make this happen? Read on to find out…

Auto-Test QuickJS with Expect Scripting

Auto-Test QuickJS with Expect Scripting

§2 Build QuickJS for NuttX

QuickJS compiles OK for NuttX?

Mostly. QuickJS compiles for NuttX with no code changes

Then we hit some Missing Functions

  1. POSIX Functions: popen, pclose, pipe2, symlink, …

  2. Dynamic Linking: dlopen, dlsym, dlclose

  3. Math Functions: pow, floor, trunc, …

  4. Atomic Functions: atomic_fetch_add_2, atomic_fetch_or_1, …

    (See the Missing Functions)

Can we fill in the missing functions?

  1. POSIX Functions: The typical POSIX Functions are OK. The special ones are probably available if we tweak the Build Options for NuttX.

    For now, we stick with the Basic NuttX Config and stub out the Advanced POSIX Functions.

  2. Dynamic Linking: We won’t support Dynamic Linking for NuttX. We stubbed the missing functions.

  3. Math Functions: We linked them with GCC Option “-lm”. The last few stragglers: We stubbed the functions.

  4. Atomic Functions: We filled in the Missing Atomic Functions.

    (About NuttX Atomic Functions)

    (We might Disable Atomic Functions)

After these fixes, QuickJS builds OK for NuttX!

(How to build QuickJS for NuttX)

That’s plenty of stubbing. Will it break QuickJS?

Thankfully we have Automated Testing with an Expect Script (pic above): qemu.exp

#!/usr/bin/expect
## Expect Script for Testing QuickJS with QEMU Emulator

## For every 1 character sent, wait 0.001 milliseconds
set send_slow {1 0.001}

## Start NuttX on QEMU Emulator
## Remove `-bios none` for newer versions of NuttX
spawn qemu-system-riscv64 \
  -semihosting \
  -M virt,aclint=on \
  -cpu rv64 \
  -bios none \
  -kernel nuttx \
  -nographic

## Wait for the prompt and enter this command
expect "nsh> "
send -s "qjs -e console.log(123) \r"

## Check the response...
expect {
  ## If we see this message, exit normally
  "nsh>" { exit 0 }

  ## If timeout, exit with an error
  timeout { exit 1 }
}

Before the Auto-Test, we solve the Auto-Crash…

Loopy Stack Trace probably means Stack Full

Loopy Stack Trace probably means Stack Full

§3 NuttX Stack is Full of QuickJS

QuickJS builds OK for NuttX. Does it run?

Sorry nope! QuickJS ran into Bizarre Crashes on NuttX (with looping Stack Traces, pic above)…

After plenty of headscratching troubleshooting, this Vital Clue suddenly pops up…

riscv_exception: EXCEPTION: Load page fault. MCAUSE: 000000000000000d, EPC: 00000000c0006d52, MTVAL: ffffffffffffffff
PID GRP PRI POLICY TYPE    NPX STATE EVENT      STACKBASE  SIZE USED FILLED  COMMAND
                                                0x802002b0 2048 2040  99.6%! irq
0   0    0  FIFO   Kthread - Ready              0x80206010 3056 1856  60.7%  Idle_Task
1   1  100  RR     Kthread - Waiting Semaphore  0x8020a050 1968  704  35.7%  lpwork 0x802015f0 0x80201618
2   2  100  RR     Task    - Waiting Semaphore  0xc0202040 3008  744  24.7%  /system/bin/init
3   3  100  RR     Task    - Running            0xc0202050 1968 1968 100.0%! qjs }¼uq¦ü®઄²äÅ

The last line shows that the QuickJS Stack (2 KB) is 100% Full! (With the Command Line incorrigibly corrupted)

We follow these steps to increase the App Stack Size

  1. Enter “make menuconfig

  2. Select “Library Routines > Program Execution Options”

  3. Set “Default task_spawn Stack Size” to 65536

    (That’s 64 KB)

  4. Select “Library Routines > Thread Local Storage (TLS)”

    (Why we set Thread Local Storage)

  5. Set “Maximum Stack Size (Log2)” to 16

    (Because 2^16 = 64 KB)

Which becomes this in our NuttX Build Config: ox64/nsh/defconfig

## Upsize the App Stack to 64 KB
CONFIG_POSIX_SPAWN_DEFAULT_STACKSIZE=65536
CONFIG_TLS_LOG2_MAXSTACK=16

QuickJS crashes no more!

Lesson Learnt: If the NuttX Stack Dump loops forever, we’re probably Out Of Stack Space.

POSIX Functions in QuickJS

POSIX Functions in QuickJS

§4 Add ioctl() to QuickJS

ioctl() doesn’t appear in the QuickJS Docs? (Pic above)

// Flip On the NuttX LED
const ULEDIOC_SETALL = 0x1d03;
const fd = os.open("/dev/userleds", os.O_WRONLY);
os.ioctl(fd, ULEDIOC_SETALL, 1);

That’s because we added ioctl() to QuickJS: quickjs-libc.c

// List of JavaScript Functions in `os` Module
static const JSCFunctionListEntry js_os_funcs[] = {
  ...
  // Declare our ioctl() function...
  JS_CFUNC_DEF(
    "ioctl",     // Function Name
    3,           // Parameters
    js_os_ioctl  // Implemented here
  ),
};

// Define our ioctl() function
static JSValue js_os_ioctl(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
  int fd, req;       // ioctl() File Descriptor and Request Number
  int64_t arg, ret;  // ioctl() Parameter and Return Value
  BOOL is_bigint;    // True if we're using BigInt
  
  // First Arg is ioctl() File Descriptor (int32)
  if (JS_ToInt32(ctx, &fd, argv[0]))
    return JS_EXCEPTION;
  
  // Second Arg is ioctl() Request Number (int32)
  if (JS_ToInt32(ctx, &req, argv[1]))
    return JS_EXCEPTION;

  // Third Arg is ioctl() Parameter (int64)
  // TODO: What if it's int32? What about passing a Pointer to a C Struct?
  is_bigint = JS_IsBigInt(ctx, argv[2]);
  if (JS_ToInt64Ext(ctx, &arg, argv[2]))
    return JS_EXCEPTION;

  // Call NuttX ioctl()
  ret = ioctl(fd, req, arg);
  if (ret == -1)
    ret = -errno;

  // Return the Result as BigInt or Normal Integer
  if (is_bigint)
    return JS_NewBigInt64(ctx, ret);
  else
    return JS_NewInt64(ctx, ret);
}

After adding this code to QuickJS, ioctl() comes to life…

NuttShell (NSH) NuttX-12.4.0-RC0
nsh> qjs
QuickJS - Type "\h" for help

qjs > os.ioctl
function ioctl()

qjs > os.ioctl(1,2,3)
-25

qjs > os.ioctl(100,2,3)
-9

We test ioctl() on real hardware…

Connect an LED to Ox64 SBC at GPIO 29, Pin 21

§5 QuickJS Blinks the LED on Ox64 SBC

We added ioctl() to QuickJS. Does it work?

We test ioctl() on a Real Device with a Real LED: Ox64 BL808 RISC-V SBC. Right after these tweaks…

Follow these steps to download (or build) NuttX and QuickJS

Connect an LED to Ox64 SBC at GPIO 29, Pin 21 (pic above)…

ConnectToWire
Ox64 Pin 21
(GPIO 29)
Resistor
(47 Ohm)
Red
Resistor
(47 Ohm)
LED +
(Curved Edge)
Breadboard
LED -
(Flat Edge)
Ox64 Pin 23
(GND)
Black

(See the Ox64 Pinout)

(Resistor is 47 Ohm, yellow-purple-black-gold, almost Karma Chameleon)

Boot NuttX on Ox64. Enter these commands…

NuttShell (NSH) NuttX-12.4.0-RC0
nsh> qjs
QuickJS - Type "\h" for help

## Define the NuttX LED Command
qjs > ULEDIOC_SETALL = 0x1d03
7427

## Open the NuttX LED Device (write-only)
qjs > fd = os.open("/dev/userleds", os.O_WRONLY)
3

## Flip LED to On
qjs > os.ioctl(fd, ULEDIOC_SETALL, 1)
bl808_gpiowrite: regaddr=0x20000938, set=0x1000000
0

## Flip LED to Off
qjs > os.ioctl(fd, ULEDIOC_SETALL, 0)
bl808_gpiowrite: regaddr=0x20000938, clear=0x1000000
0

(Or run the Blinky JavaScript)

Yep ioctl() works great on a Real Device, with a Real LED!

(Watch the Demo on YouTube)

(See the NuttX Log)

Apache NuttX RTOS on Ox64 BL808 RISC-V SBC: QuickJS blinks our LED

If we don’t have an Ox64 SBC?

No worries, the exact same steps will work for QEMU Emulator (64-bit RISC-V)…

When we download (or build) NuttX and QuickJS

QuickJS blinks a Simulated LED on NuttX QEMU…

## Start NuttX on QEMU
## Remove `-bios none` for newer versions of NuttX
$ qemu-system-riscv64 -semihosting -M virt,aclint=on -cpu rv64 -bios none -kernel nuttx -nographic

## Run our Blinky JavaScript with QuickJS
NuttShell (NSH) NuttX-12.4.0-RC0
nsh> qjs --std /system/bin/blink.js
led=0, val=1
led=0, val=0
led=0, val=1

To Exit QEMU: Press Ctrl-A then x

(See the Complete Log)

QuickJS Code Size rendered with linkermapviz

QuickJS Code Size rendered with linkermapviz

§6 How Small is QuickJS

Will QuickJS run on all kinds of NuttX Devices?

$ riscv64-unknown-elf-size apps/bin/qjs
   text  data  bss     dec
 554847   260   94  555201

Probably not ALL devices? JavaScript needs a fair bit of RAM to run comfortably.

What’s the Memory Footprint like?

We ran linkermapviz on the QuickJS Linker Map for NuttX QEMU…

## Visualise the QuickJS Linker Map for NuttX QEMU.
## Produces linkermap.html: https://github.com/lupyuen/nuttx-tinyemu/blob/main/docs/quickjs/linkermap.html

git clone https://github.com/PromyLOPh/linkermapviz
cd linkermapviz
pip3 install .
linkermapviz < quickjs-nuttx/nuttx/qjs-riscv.map

Which produces the Visualised Linker Map for QuickJS. (Pics above and below)

Here are the sizes of QuickJS and its options…

Size of Code + Data (Read-Only)
QuickJS with All The Toppings554 KB
Without REPL538 KB
Without BigInt522 KB
Without BigInt, REPL506 KB

(REPL is for Interactive Commands)

(BigInt is for Big Numbers and Calculator)

QuickJS Data Size

What about Heap Memory Size?

Based on the NuttX Logs with Heap Logging Enabled: “Build Setup > Debug Options > Enable Debug Features > Memory Manager Debug Features > Info Output”

Computing the QuickJS Heap Usage with a Spreadsheet

We compute the Heap Usage with a Spreadsheet (pic above)…

(“Free Size” might be inaccurate because it uses VLOOKUP for Top-Down Lookup, though we actually need Down-Top Lookup)

And we derive the QuickJS Heap Usage (pic below)…

Max Heap Usage
QuickJS without REPL276 KB
QuickJS with REPL371 KB

QuickJS Heap Usage

Which totals 782 KB for Barebones QuickJS. And a whopping 925 KB for Turducken QuickJS. (Nearly 1 MB for Code + Data + Heap!)

For newer Upsized NuttX Gadgets that are Extra Roomy (and Vroomy), there’s a high chance that we can run QuickJS…

And experiment with all kinds of NuttX Drivers via ioctl(). The Interactive JavaScript Way!

QEMU vs Ox64 QuickJS: 4 MB vs 22 MB

QEMU vs Ox64 QuickJS: Any diff? (Pic above)

QuickJS for NuttX QEMU is more Memory-Efficient because it uses Full Linking.

(Instead of ELF Loader patching the Relocatable Symbols at runtime)

Ox64 QuickJS was slower and multi-deca-mega-chonky: 22 MB! So we switched to QuickJS with Full Linking, QuickJS is now 4 MB on Ox64. (Similar to QEMU)

(About NuttX Full Linking)

QuickJS JavaScript Engine to Apache NuttX RTOS

Watch the Demo on YouTube

§7 Simulate the LED on Ox64 Emulator

NuttX Emulator blinks a Simulated LED…

How does it work? (Pic above, lower right)

We modded NuttX Emulator (in WebAssembly) to…

  1. Watch for updates to GPIO Registers

    (Like 0x2000_0938 for GPIO 29)

  2. Notify our Web Browser JavaScript of any updates

    (Like {“nuttxemu”:{“gpio29”:1}})

  3. And our Web Browser JavaScript Flips the Simulated LED

    (On or Off)

Simulate the LED on Ox64 Emulator

Here’s our NuttX Emulator (WebAssembly) intercepting all Writes to GPIO 29: riscv_cpu.c

// WebAssembly called by TinyEmu to emulate
// Writes to RISC-V Addresses
int target_write_slow(...) {
  ...
  // Intercept Writes to Memory-Mapped I/O
  switch(paddr) {

    // If we're writing to BL808 GPIO 29 (0x2000_0938)...
    case 0x20000938: {
      // Send an Emulator Notification to the Console:
      // {"nuttxemu":{"gpio29":1}}

      // Check if the Output Bit is Off or On
      #define reg_gpio_xx_o 24
      const char b =
        ((val & (1 << reg_gpio_xx_o)) == 0)
        ? '0' : '1';

      // Send the Notification to Console
      char notify[] = "{\"nuttxemu\":{\"gpio29\":0}}\r\n";
      notify[strlen(notify) - 5] = b;
      print_console(NULL, notify, strlen(notify));
    }

Which sends a Notification to the Web Browser (JavaScript), saying that the GPIO Output has changed

{"nuttxemu":
  {"gpio29": 1}
}

Our Web Browser (JavaScript) receives the Notification and Flips the Simulated LED: term.js

// JavaScript called by our WebAssembly
// to print something to Console
Term.prototype.write = function(str) {

  // If this is a Notification JSON from Emulator WebAssembly:
  // {"nuttxemu":{"gpio29":1}}
  if (str.indexOf(`{"nuttxemu":`) == 0) {
    
    // Get the GPIO Number and GPIO Value from JSON
    const notify = JSON.parse(str).nuttxemu;  // {gpio29:1}
    const gpio   = Object.keys(notify)[0];    // "gpio29"
    const val    = notify[gpio];              // 0 or 1

    // Render the GPIO in HTML:
    // <td id="gpio29" class="gpio_on">GPIO29</td>
    document.getElementById("status").style.width = document.getElementById("term_wrap").style.width;  // Space out the GPIO Status
    const gpio_status = document.getElementById(gpio);
    gpio_status.style.display = "block";

    // GPIO Off or On
    gpio_status.className = (val == 0)
      ? "gpio_off"  // Normal CSS Style
      : "gpio_on";  // Green CSS Style
    return;         // Don't show in Console Output
  }

(status and gpio29 are in HTML)

(gpio_off and gpio_on are in CSS)

Emulator Notifications won’t appear in the Emulator Console Output. (Because we suppressed them)

We’ll see the Notifications in the JavaScript Console. (Pic below)

Notifications from NuttX Emulator appear in JavaScript Console

§8 What’s Next

Thanks to the QuickJS Team, we have a fun new way to Experiment Interactively with NuttX Gadgets!

Many Thanks to my GitHub Sponsors (and the awesome NuttX Community) for supporting my work! This article wouldn’t have been possible without your support.

Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…

lupyuen.github.io/src/quickjs.md

Auto-Test QuickJS with Expect Scripting

Auto-Test QuickJS with Expect Scripting

§9 Appendix: Build QuickJS for NuttX

Before building QuickJS: Build NuttX for QEMU or Ox64

(Or download the NuttX and QuickJS Binaries from these links)

Then follow these steps to build QuickJS for NuttX (QEMU or Ox64)…

## Download and build QuickJS for NuttX
git clone https://github.com/lupyuen/quickjs-nuttx
cd quickjs-nutttx/nuttx
./build.sh

(See the Build Script)

Remember to…

How did we figure out the steps to build QuickJS for NuttX?

We ran “make --trace” to observe the QuickJS Build: make.log

## Build QuickJS for Debian x64 and observe the build
$ make --trace

## Build qjs.o
gcc \
  -g \
  -Wall \
  -MMD \
  -MF .obj/qjs.o.d \
  -Wno-array-bounds \
  -Wno-format-truncation \
  -fwrapv  \
  -D_GNU_SOURCE \
  -DCONFIG_VERSION=\"2024-01-13\" \
  -DCONFIG_BIGNUM \
  -O2 \
  -c \
  -o .obj/qjs.o \
  qjs.c

## Omitted: Build a bunch of other binaries
...
## Link them together
gcc \
  -g \
  -rdynamic \
  -o qjs \
  .obj/qjs.o \
  .obj/repl.o \
  .obj/quickjs.o \
  .obj/libregexp.o \
  .obj/libunicode.o \
  .obj/cutils.o \
  .obj/quickjs-libc.o \
  .obj/libbf.o \
  .obj/qjscalc.o \
  -lm \
  -ldl \
  -lpthread

And we know that NuttX builds NuttX Apps like this…

## Build NuttX Apps for QEMU and observe the build
$ cd ../apps
$ make --trace import

## Compile hello app
## For riscv-none-elf-gcc: "-march=rv64imafdc_zicsr_zifencei"
## For riscv64-unknown-elf-gcc: "-march=rv64imafdc"
riscv-none-elf-gcc \
  -c \
  -fno-common \
  -Wall \
  -Wstrict-prototypes \
  -Wshadow \
  -Wundef \
  -Wno-attributes \
  -Wno-unknown-pragmas \
  -Wno-psabi \
  -fno-common \
  -pipe  \
  -Os \
  -fno-strict-aliasing \
  -fomit-frame-pointer \
  -ffunction-sections \
  -fdata-sections \
  -g \
  -mcmodel=medany \
  -march=rv64imafdc_zicsr_zifencei \
  -mabi=lp64d \
  -isystem apps/import/include \
  -isystem apps/import/include \
  -D__NuttX__  \
  -I "apps/include"   \
  hello_main.c \
  -o  hello_main.c.workspaces.bookworm.apps.examples.hello.o

## Link hello app
## For riscv-none-elf-ld: "rv64imafdc_zicsr/lp64d"
## For riscv64-unknown-elf-ld: "rv64imafdc/lp64d
riscv-none-elf-ld \
  --oformat elf64-littleriscv \
  -e _start \
  -Bstatic \
  -Tapps/import/scripts/gnu-elf.ld \
  -Lapps/import/libs \
  -L "xpack-riscv-none-elf-gcc-13.2.0-2/lib/gcc/riscv-none-elf/13.2.0/rv64imafdc_zicsr/lp64d" \
  apps/import/startup/crt0.o  \
  hello_main.c.workspaces.bookworm.apps.examples.hello.o \
  --start-group \
  -lmm \
  -lc \
  -lproxies \
  -lgcc apps/libapps.a \
  xpack-riscv-none-elf-gcc-13.2.0-2/lib/gcc/riscv-none-elf/13.2.0/rv64imafdc_zicsr/lp64d/libgcc.a \
  --end-group \
  -o  apps/bin/hello

(Ox64 Build is a little different)

Thus we…

Everything builds OK without changing any code in QuickJS! Though we stubbed out some functions because NuttX works a little differently.

repl.c and qjscalc.c are missing?

They’re generated by the QuickJS Compiler

## Compile the REPL from JavaScript to C
./qjsc -c -o repl.c -m repl.js

## Compile the BigNum Calculator from JavaScript to C
./qjsc -fbignum -c -o qjscalc.c qjscalc.js

So we borrow the output from another QuickJS Build (Debian x64) and add to NuttX…

What’s inside repl.c and qjscalc.c?

They contain plenty of JavaScript Bytecode for REPL and BigNum Calculator. Brilliant!

/* File generated automatically by the QuickJS compiler. */
#include <inttypes.h>
const uint32_t qjsc_repl_size = 16280;
const uint8_t qjsc_repl[16280] = {
 0x02, 0xa5, 0x03, 0x0e, 0x72, 0x65, 0x70, 0x6c,
 0x2e, 0x6a, 0x73, 0x06, 0x73, 0x74, 0x64, 0x04, ...

That’s why REPL and BigNum will require more Heap Memory, to execute the extra JavaScript Bytecode.

QuickJS on NuttX QEMU

QuickJS on NuttX QEMU

§10 Appendix: Build NuttX for QEMU

In this article, we compiled a Work-In-Progress Version of Apache NuttX RTOS for QEMU RISC-V (64-bit Kernel Mode) that has these updates…

We may download the NuttX Binaries for QEMU

  1. Download the NuttX Kernel: nuttx

    Copy to $HOME/nuttx/

  2. Download the NuttX Apps: apps-bin.zip

    Unzip and copy the files inside (not the folder) into $HOME/apps/bin/

    (We should see $HOME/apps/bin/qjs and blink.js)

  3. Then run…

    $ cd $HOME/nuttx/
    $ qemu-system-riscv64 \
        -semihosting \
        -M virt,aclint=on \
        -cpu rv64 \
        -bios none \
        -kernel nuttx \
        -nographic

    (Remove -bios none for newer versions of NuttX)

    (See the NuttX Log)

Or if we prefer to build NuttX ourselves

## Download the WIP NuttX Source Code
git clone \
  --branch qemuled \
  https://github.com/lupyuen2/wip-nuttx \
  nuttx
git clone \
  --branch qemuled \
  https://github.com/lupyuen2/wip-nuttx-apps \
  apps

## Configure NuttX for QEMU RISC-V (64-bit Kernel Mode)
cd nuttx
tools/configure.sh rv-virt:knsh64

## Build NuttX
make

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

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

(Remember to install the Build Prerequisites and Toolchain)

(See the Build Script)

(See the Build Outputs)

This produces the NuttX ELF Image nuttx that we’ll boot on QEMU RISC-V Emulator in a while.

But first: We build QuickJS for QEMU, which will produce qjs and blink.sh in the apps/bin folder…

Now we boot nuttx on QEMU RISC-V Emulator…

## Start the QEMU RISC-V Emulator (64-bit) with NuttX RTOS
qemu-system-riscv64 \
  -semihosting \
  -M virt,aclint=on \
  -cpu rv64 \
  -bios none \
  -kernel nuttx \
  -nographic

(Remove -bios none for newer versions of NuttX)

At the NuttX Prompt, enter…

qjs --std /system/bin/blink.js

(See the Blinky JavaScript)

QuickJS runs our Blinky JavaScript and blinks a Simulated LED

## Remove `-bios none` for newer versions of NuttX
$ qemu-system-riscv64 -semihosting -M virt,aclint=on -cpu rv64 -bios none -kernel nuttx -nographic

NuttShell (NSH) NuttX-12.4.0-RC0
nsh> qjs --std /system/bin/blink.js
led=0, val=1
led=0, val=0
led=0, val=1

(See the Complete Log)

To Exit QEMU: Press Ctrl-A then x

QuickJS on NuttX Ox64

QuickJS on NuttX Ox64

§11 Appendix: Build NuttX for Ox64

In this article, we compiled a Work-In-Progress Version of Apache NuttX RTOS for Ox64 that has these updates…

We may download the NuttX Binaries for Ox64

  1. Download the NuttX Image: Image

  2. Prepare a Linux microSD for Ox64 as described in the previous article.

    (Remember to flash OpenSBI and U-Boot Bootloader)

  3. Copy the Image file (from above) and overwrite the Image in the Linux microSD…

    ## Overwrite the Linux Image
    ## on Ox64 microSD
    cp Image \
      "/Volumes/NO NAME/Image"
    diskutil unmountDisk /dev/disk2
  4. Insert the microSD into Ox64 and power up Ox64.

  5. Ox64 boots OpenSBI, which starts U-Boot Bootloader, which starts NuttX Kernel and the NuttX Shell (NSH).

  6. At the NuttX Prompt, enter…

    qjs --std /system/bin/blink.js

    (See the Blinky JavaScript)

  7. QuickJS executes our Blinky JavaScript and blinks our LED on GPIO 29

    NuttShell (NSH) NuttX-12.4.0-RC0
    nsh> qjs --std /system/bin/blink.js
    
    bl808_gpiowrite: regaddr=0x20000938, set=0x1000000
    bl808_gpiowrite: regaddr=0x20000938, clear=0x1000000
    bl808_gpiowrite: regaddr=0x20000938, set=0x1000000
    bl808_gpiowrite: regaddr=0x20000938, clear=0x1000000

    (See the Complete Log)

Or if we prefer to build NuttX ourselves

## Download the WIP NuttX Source Code
git clone \
  --branch gpio2 \
  https://github.com/lupyuen2/wip-nuttx \
  nuttx
git clone \
  --branch gpio2 \
  https://github.com/lupyuen2/wip-nuttx-apps \
  apps

## Configure NuttX for Ox64 BL808 RISC-V SBC
cd nuttx
tools/configure.sh ox64:nsh

## Build NuttX
make

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

## 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 hello_nim disassembly to hello_nim.S
riscv64-unknown-elf-objdump \
  --syms --source --reloc --demangle --line-numbers --wide \
  --debugging \
  ../apps/bin/hello_nim \
  >hello_nim.S \
  2>&1

(Remember to install the Build Prerequisites and Toolchain)

We build the NuttX Apps Filesystem that contains NuttX Shell and NuttX Apps…

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

Next we build QuickJS for Ox64, which will produce qjs and blink.sh in the apps/bin folder…

We bundle QuickJS into the Initial RAM Disk and append it to the NuttX Image…

## Generate the Initial RAM Disk `initrd`
## in ROMFS Filesystem Format
## from the Apps Filesystem `../apps/bin`
## and label it `NuttXBootVol`
genromfs \
  -f initrd \
  -d ../apps/bin \
  -V "NuttXBootVol"

## 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

(See the Build Script)

(See the Build Outputs)

This produces the NuttX Image for Ox64: Image

Follow the earlier instructions to copy Image to a Linux microSD, boot it on Ox64 and run QuickJS. (Pic below)

(Watch the Demo on YouTube)

(See the NuttX Log)

What about the NuttX Image for Ox64 Emulator? (Pic above)

The exact same Build Outputs from above, we copied to the NuttX Emulator for Ox64.

QuickJS blinks an LED on Ox64 BL808 SBC

QuickJS blinks an LED on Ox64 BL808 SBC