đź“ť 28 Aug 2022
Apache NuttX RTOS booting on Pine64 PinePhone
UPDATE: PinePhone is now officially supported by Apache NuttX RTOS (See this)
Suppose we’re creating our own Operating System (non-Linux) for Pine64 PinePhone…
What’s the File Format?
Where in RAM should it run?
Can we make a microSD that will boot our OS?
What happens when PinePhone powers on?
This article explains how we ported Apache NuttX RTOS to PinePhone. And we’ll answer the questions along the way!
Let’s walk through the steps to create our own PinePhone Operating System…
What’s inside PinePhone?
At the heart of PinePhone is the Allwinner A64 SoC (System-on-a-Chip) with 4 Cores of 64-bit Arm Cortex-A53…
The A64 SoC in PinePhone comes with 2GB RAM (or 3GB RAM via a mainboard upgrade)…
A64’s Memory Map says that the RAM starts at address 0x4000
0000
.
So our OS will run at 0x4000
0000
?
Not quite! Our OS will actually be loaded at 0x4008
0000
We’ll see why in a while, but first we talk about a Very Important Cable…
PinePhone connected to USB Serial Debug Cable
Can we watch what happens when PinePhone boots?
There’s a magical cable for that: USB Serial Debug Cable (pic above)…
It connects to PinePhone’s Headphone Port (pic below) and exposes PinePhone’s hidden UART Port. Genius!
(Remember to flip the Headphone Switch to OFF)
I highly recommend the USB Serial Debug Cable for PinePhone Hacking.
What secrets will the Debug Cable reveal?
We’ll find out shortly! First we need to prep our microSD Card for hacking…
PinePhone UART Port in disguise
Let’s watch and learn how a Linux Kernel boots on PinePhone.
We pick a small, simple Linux Kernel: PinePhone Jumpdrive…
And prepare a microSD Card for Jumpdrive…
Download pine64-pinephone.img.xz
Write the downloaded file to a microSD Card with Balena Etcher
Insert the microSD Card into PinePhone
Don’t power up PinePhone yet! We need to talk about PinePhone’s Bootloader…
PinePhone Jumpdrive on microSD
What happens when we power up PinePhone?
The U-Boot Bootloader runs, searching for a Linux Kernel to boot (on microSD or eMMC)…
(Secondary Program Loader)
Whoa! These docs look so superdry…
There’s an easier way to grok U-Boot. Let’s watch PinePhone boot Jumpdrive!
U-Boot Bootloader on PinePhone
Now we’re ready to watch what happens when PinePhone boots…
Insert the Jumpdrive microSD into PinePhone
Flip PinePhone’s Headphone Switch to OFF
Connect our computer to PinePhone via the USB Serial Debug Cable
Launch a Serial Terminal (115.2 kbps) on our computer and connect to PinePhone…
For Linux:
## Change ttyUSB0 to the USB Serial Device
sudo screen /dev/ttyUSB0 115200
For macOS: Use screen
or CoolTerm
For Windows: Use PuTTY
Power up PinePhone
This is the Boot Log that we’ll see…
$ sudo screen /dev/ttyUSB0 115200
DRAM: 2048 MiB
Trying to boot from MMC1
NOTICE: BL31: v2.2(release):v2.2-904-gf9ea3a629
NOTICE: BL31: Built : 15:32:12, Apr 9 2020
NOTICE: BL31: Detected Allwinner A64/H64/R18 SoC (1689)
NOTICE: BL31: Found U-Boot DTB at 0x4064410, model: PinePhone
NOTICE: PSCI: System suspend is unavailable
BL31 refers to Arm Trusted Firmware, the very first thing that runs on PinePhone.
BL31 finds the Device Tree (DTB) for the U-Boot Bootloader, and starts U-Boot…
U-Boot 2020.07 (Nov 08 2020 - 00:15:12 +0100)
DRAM: 2 GiB
MMC: Device 'mmc@1c11000': seq 1 is in use by 'mmc@1c10000'
mmc@1c0f000: 0, mmc@1c10000: 2, mmc@1c11000: 1
Loading Environment from FAT... *** Warning - bad CRC, using default environment
starting USB...
No working controllers found
Hit any key to stop autoboot: 0
Yep U-Boot can be stopped! Later we’ll hit some keys to stop U-Boot and run some commands.
But for now we let U-Boot do its booting thing…
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot.scr
653 bytes read in 3 ms (211.9 KiB/s)
U-Boot scans our microSD and discovers a U-Boot Script boot.scr
(We’ll talk more about the script)
The U-Boot Script loads the Linux Kernel into RAM and starts it…
## Executing script at 4fc00000
gpio: pin 114 (gpio 114) value is 1
4275261 bytes read in 192 ms (21.2 MiB/s)
Uncompressed size: 10170376 = 0x9B3008
36162 bytes read in 4 ms (8.6 MiB/s)
1078500 bytes read in 50 ms (20.6 MiB/s)
## Flattened Device Tree blob at 4fa00000
Booting using the fdt blob at 0x4fa00000
Loading Ramdisk to 49ef8000, end 49fff4e4 ... OK
Loading Device Tree to 0000000049eec000, end 0000000049ef7d41 ... OK
Starting kernel ...
/ #
The Linux Kernel is running! It works like we expect…
/ # uname -a
Linux (none) 5.9.1jumpdrive #3 SMP Sun Nov 8 00:41:50 CET 2020 aarch64 GNU/Linux
/ # ls
bin info.sh root telnet_connect.sh
config init sbin usr
dev init_functions.sh splash.ppm
error.ppm.gz linuxrc splash.ppm.gz
etc proc sys
And that’s how PinePhone boots a Linux Kernel, thanks to the U-Boot Bootloader!
What’s boot.scr
?
Found U-Boot script /boot.scr
According to the log above, the U-Boot Bootloader runs the U-Boot Script boot.scr
to…
Light up the PinePhone LED (I think?)
Load the Linux Kernel Image.gz
into RAM
(At 0x4408
0000
)
Unzip the Linux Kernel Image.gz
in RAM
(At 0x4008
0000
)
Load the Linux Device Tree…
sun50i-a64-pinephone-1.2.dtb
(At 0x4FA0
0000
)
Load the RAM File System initramfs.gz
(At 0x4FE0
0000
)
Boot the Unzipped Linux Kernel in RAM
(At 0x4008
0000
)
Here’s the Source File: Jumpdrive/src/pine64-pinephone.txt
setenv kernel_addr_z 0x44080000
setenv bootargs loglevel=0 silent console=tty0 vt.global_cursor_default=0
gpio set 114
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_z} /Image.gz; then
unzip ${kernel_addr_z} ${kernel_addr_r}
if load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} /sun50i-a64-pinephone-1.2.dtb; then
if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} /initramfs.gz; then
booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r};
else
booti ${kernel_addr_r} - ${fdt_addr_r};
fi;
fi;
fi
(We’ll explain fdt_addr_r, kernel_addr_r and ramdisk_addr_r)
The above U-Boot Script pine64-pinephone.txt
is compiled to boot.scr
by this Makefile: Jumpdrive/Makefile
%.scr: src/%.txt
@echo "MKIMG $@"
@mkimage -A arm -O linux -T script -C none -n "U-Boot boot script" -d $< $@
What are fdt_addr_r, kernel_addr_r and ramdisk_addr_r?
They are Environment Variables defined in U-Boot. To see the variables in U-Boot…
Power off PinePhone
On our computer’s Serial Terminal, keep hitting Enter, don’t stop…
Power up PinePhone
U-Boot should stop and reveal the U-Boot Prompt…
U-Boot 2020.07 (Nov 08 2020 - 00:15:12 +0100)
Hit any key to stop autoboot:
=>
Enter printenv
to print the Environment Variables…
=> printenv
kernel_addr_r=0x40080000
fdt_addr_r=0x4FA00000
ramdisk_addr_r=0x4FE00000
When we match these addresses with our U-Boot Script, we discover…
kernel_addr_r
: Linux Kernel Image
will be unzipped into RAM at 0x4008
0000
. And it will execute at that address.
fdt_addr_r
: Linux Device Tree sun50i*.dtb
will be loaded into RAM at 0x4FA0
0000
ramdisk_addr_r
: Linux RAM File System initramfs
will be loaded into RAM at 0x4FE0
0000
Aha that’s why our kernel must start at 0x4008
0000
!
Yep! Thus we can…
Compile our own operating system to start at 0x4008
0000
Replace Image.gz
in the microSD by our compiled OS (gzipped)
And PinePhone will boot our own OS! (Theoretically)
But there’s a catch: U-Boot expects to find a Linux Kernel Header in our OS…
What! A Linux Kernel Header in our non-Linux OS?
Yep it’s totally strange, but U-Boot Bootloader expects our OS to begin with an Arm64 Linux Kernel Header as defined here…
The doc says that a Linux Kernel Image (for Arm64) should begin with this 64-byte header…
u32 code0; /* Executable code */
u32 code1; /* Executable code */
u64 text_offset; /* Image load offset, little endian */
u64 image_size; /* Effective Image size, little endian */
u64 flags; /* kernel flags, little endian */
u64 res2 = 0; /* reserved */
u64 res3 = 0; /* reserved */
u64 res4 = 0; /* reserved */
u32 magic = 0x644d5241; /* Magic number, little endian, "ARM\x64" */
u32 res5; /* reserved (used for PE COFF offset) */
Let’s make a Linux Kernel Header to appease U-Boot…
How do we make a Linux Kernel Header in our non-Linux OS?
Apache NuttX RTOS can help!
This is how we created the Arm64 Linux Kernel Header in NuttX: arch/arm64/src/common/arm64_head.S
/* Kernel startup entry point.
* ---------------------------
*
* This must be the very first address in the loaded image.
* It should be loaded at any 4K-aligned address.
* __start will be set to 0x4008 0000 in the Linker Script
*/
.globl __start;
__start:
/* DO NOT MODIFY. Image header expected by Linux boot-loaders.
*
* This add instruction has no meaningful effect except that
* its opcode forms the magic "MZ" signature of a PE/COFF file
* that is required for UEFI applications.
*/
add x13, x18, #0x16 /* the magic "MZ" signature */
b real_start /* branch to kernel start */
(“MZ” refers to Mark Zbikowski)
The header begins at Kernel Start Address 0x4008
0000
.
At the top of the header we jump to real_start
to skip the header.
(NuttX code begins at real_start
after the header)
Then comes the rest of the header…
/* PinePhone Image load offset from start of RAM */
.quad 0x0000
/* Effective size of kernel image, little-endian */
.quad _e_initstack - __start
/* Informative flags, little-endian */
.quad __HEAD_FLAGS
.quad 0 /* reserved */
.quad 0 /* reserved */
.quad 0 /* reserved */
.ascii "ARM\x64" /* Magic number, "ARM\x64" */
.long 0 /* reserved */
/* NuttX OS Code begins here, after the header */
real_start: ...
(_e_initstack
is End of Stack Space)
(__HEAD_FLAGS
is defined here)
(UPDATE: We don’t need to change the Image Load Offset)
What’s the value of __start
?
Remember kernel_addr_r
, the Kernel Start Address from U-Boot?
In NuttX, we define the Kernel Start Address __start
as 0x4008
0000
in our Linker Script: boards/arm64/qemu/qemu-a53/scripts/dramboot
SECTIONS
{
/* PinePhone uboot load address (kernel_addr_r) */
. = 0x40080000;
_start = .;
We also updated the Kernel Start Address in the NuttX Memory Map…
We’re almost ready to boot NuttX on PinePhone!
Will we see anything when NuttX boots on PinePhone?
Not yet. We need to implement the UART Driver…
Allwinner A64 UART Controller Registers
Our operating system will show some output on PinePhone’s Serial Debug Console as it boots.
To do that, we’ll talk to the UART Controller on the Allwinner A64 SoC…
Flip the A64 User Manual to page 562 (“UART”) and we’ll see the UART Registers. (Pic above)
PinePhone’s Serial Console is connected to UART0 at Base Address 0x01C2
8000
The First Register of UART0 is what we need: UART_THR at 0x01C2
8000
…
What’s UART_THR?
UART_THR is the Transmit Holding Register.
We’ll write our output data to 0x01C2
8000
, byte by byte, and the data will appear in the Serial Console. Let’s do that!
Did we forget something?
Rightfully we should wait for THR Empty (Transmit Buffer Empty) before sending our data.
And we should initialise the UART Baud Rate. We’ll come back to this.
NuttX writes to the UART Port with some clever Arm Assembly Macros.
This Assembly Code in NuttX…
cpu_boot:
PRINT(cpu_boot, "- Ready to Boot CPU\r\n")
Calls our PRINT
Macro to print a string at startup.
Which is super convenient because our Startup Code has plenty of Assembly Code!
What’s inside the macro?
Our PRINT
Macro calls…
boot_stage_puts
Function, which calls…
up_lowputc
Function
Which loads our UART Base Address…
/* PinePhone Allwinner A64 UART0 Base Address: */
#define UART0_BASE_ADDRESS 0x1C28000
/* Print a character on the UART - this function is called by C
* x0: character to print
*/
GTEXT(up_lowputc)
SECTION_FUNC(text, up_lowputc)
ldr x15, =UART0_BASE_ADDRESS /* Load UART Base Address */
early_uart_ready x15, w2 /* Wait for UART ready */
early_uart_transmit x15, w0 /* Transmit to UART */
ret
And calls early_uart_transmit
Macro to transmit a character…
/* UART transmit character
* xb: register which contains the UART base address
* wt: register which contains the character to transmit
*/
.macro early_uart_transmit xb, wt
/* Write to UART_THR (Transmit Holding Register) */
strb \wt, [\xb]
.endm
That’s how we print a string to the console at startup!
What’s early_uart_ready
?
early_uart_ready
Macro waits for the UART Port to be ready to transmit, as explained here…
How do we initialise the UART Port?
Right now we don’t initialise the UART Port because U-Boot has kindly done it for us. Eventually this needs to be fixed: qemu_lowputc.S
/* UART initialization
* xb: register which contains the UART base address
* c: scratch register number
*/
GTEXT(up_earlyserialinit)
SECTION_FUNC(text, up_earlyserialinit)
## TODO: Set PinePhone Allwinner A64 Baud Rate Divisor:
## Write to UART_LCR (DLAB), UART_DLL and UART_DLH
...
(up_earlyserialinit
is called by our Startup Code)
More about this in the Appendix…
We’re finally ready to boot our own PinePhone Operating System!
Apache NuttX RTOS booting on Pine64 PinePhone
Can we boot our own OS on PinePhone… By replacing a single file on Jumpdrive microSD?
Earlier we said that we’ll overwrite Image.gz
on Jumpdrive microSD to boot our own OS…
Let’s do it!
Prepare a microSD Card with PinePhone Jumpdrive…
Follow these steps to build Apache NuttX RTOS…
“Build NuttX for PinePhone”
Or download nuttx.bin
from here (includes BASIC Interpreter)…
Compress the NuttX Binary Image…
## Compress the NuttX Binary Image
cp nuttx.bin Image
rm -f Image.gz
gzip Image
Or download Image.gz
from here (includes BASIC Interpreter)…
Overwrite Image.gz
on Jumpdrive microSD…
## Copy compressed NuttX Binary Image to Jumpdrive microSD
## TODO: Change the microSD Path
cp Image.gz "/Volumes/NO NAME"
Connect PinePhone to our computer with a USB Serial Debug Cable…
Insert Jumpdrive microSD into PinePhone and power up
On our computer’s Serial Terminal, we see PinePhone’s U-Boot Bootloader loading NuttX RTOS into RAM…
U-Boot 2020.07 (Nov 08 2020 - 00:15:12 +0100)
Found U-Boot script /boot.scr
653 bytes read in 3 ms (211.9 KiB/s)
## Executing script at 4fc00000
gpio: pin 114 (gpio 114) value is 1
99784 bytes read in 8 ms (11.9 MiB/s)
Uncompressed size: 278528 = 0x44000
36162 bytes read in 4 ms (8.6 MiB/s)
1078500 bytes read in 51 ms (20.2 MiB/s)
## Flattened Device Tree blob at 4fa00000
Booting using the fdt blob at 0x4fa00000
Loading Ramdisk to 49ef8000, end 49fff4e4 ... OK
Loading Device Tree to 0000000049eec000, end 0000000049ef7d41 ... OK
Then NuttX runs…
Starting kernel ...
HELLO NUTTX ON PINEPHONE!
- Ready to Boot CPU
- Boot from EL2
- Boot from EL1
- Boot to C runtime for OS Initialize
nx_start: Entry
up_allocate_heap: heap_start=0x0x400c4000, heap_size=0x7f3c000
arm64_gic_initialize: TODO: Init GIC for PinePhone
arm64_gic_initialize: CONFIG_GICD_BASE=0x1c81000
arm64_gic_initialize: CONFIG_GICR_BASE=0x1c82000
arm64_gic_initialize: GIC Version is 2
up_timer_initialize: up_timer_initialize: cp15 timer(s) running at 24.00MHz, cycle 24000
up_timer_initialize: _vector_table=0x400a7000
up_timer_initialize: Before writing: vbar_el1=0x40227000
up_timer_initialize: After writing: vbar_el1=0x400a7000
uart_register: Registering /dev/console
uart_register: Registering /dev/ttyS0
work_start_highpri: Starting high-priority kernel worker thread(s)
nx_start_application: Starting init thread
lib_cxx_initialize: _sinit: 0x400a7000 _einit: 0x400a7000 _stext: 0x40080000 _etext: 0x400a8000
nsh: sysinit: fopen failed: 2
nshn:x _msktfaarttf:s :C PcUo0m:m aBnedg innonti nfgo uInddle L oNouptt
Shell (NSH) NuttX-10.3.0-RC2
(Yeah the output is slightly garbled, here’s the workaround)
NuttX Shell works perfectly OK on PinePhone…
nsh> uname -a
NuttX 10.3.0-RC2 fc909c6-dirty Sep 1 2022 17:05:44 arm64 qemu-armv8a
nsh> help
help usage: help [-v] [<cmd>]
. cd dmesg help mount rmdir true xd
[ cp echo hexdump mv set truncate
? cmp exec kill printf sleep uname
basename dirname exit ls ps source umount
break dd false mkdir pwd test unset
cat df free mkrd rm time usleep
Builtin Apps:
getprime hello nsh ostest sh
nsh> hello
task_spawn: name=hello entry=0x4009b1a0 file_actions=0x400c9580 attr=0x400c9588 argv=0x400c96d0
spawn_execattrs: Setting policy=2 priority=100 for pid=3
Hello, World!!
nsh> ls /dev
/dev:
console
null
ram0
ram2
ttyS0
zero
Yep NuttX boots on PinePhone… After replacing a single Image.gz
file!
Right now we’re running NuttX on a Single Arm64 CPU. In future we might run on all 4 Arm64 CPUs of PinePhone…
We fixed some issues with Arm64 Interrupts on PinePhone…
And we fixed UART Input in our UART Driver…
Now we’re ready to build the Missing Drivers for PinePhone! Like MIPI DSI Display, I2C Touch Panel, LTE Modem, …
Below are tips for debugging the NuttX Boot Sequence on PinePhone…
Apache NuttX RTOS has plenty of Arm64 Code that will be helpful to creators of PinePhone Operating Systems.
The Arm64 Architecture Functions (pic above) are defined here…
These functions implement all kinds of Arm64 Features: FPU, Interrupts, MMU, Tasks, Timers, …
The Arm64 Startup Code (including Linux Kernel Header) is at…
Previously NuttX supports only one Arm64 Target Board: QEMU Emulator.
Below are the Source Files and Build Configuration for QEMU Emulator…
We clone this to create a Target Board for PinePhone…
And we start the Board-Specific Drivers for PinePhone in pinephone_bringup.c
Our Board calls the Architecture-Specific Drivers at…
The UART Driver is located at a64_serial.c and a64_lowputc.S
The QEMU Target for NuttX is described in this article…
It’s indeed possible to boot our own OS on PinePhone… By replacing a single file on Jumpdrive microSD!
We’ve done that with Apache NuttX RTOS, which has plenty of code that will be helpful for PinePhone OS Developers.
Will NuttX work with all PinePhone features?
NuttX on PinePhone might take a while to become a Daily Driver…
But today NuttX is ready to turn PinePhone into a valuable Learning Resource!
There’s plenty to be done for NuttX on PinePhone, please lemme know if you would like to join me 🙏
Please check out the other articles on NuttX for PinePhone…
Many Thanks to my GitHub Sponsors 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/uboot.md
This article is the expanded version of this Twitter Thread
Check out this detailed doc on porting Genode OS to PinePhone…
“Genode Operating System Framework 22.05”
PinePhone’s Touch Display is explained in pages 171 to 197.
PinePhone’s LTE Modem is covered in pages 198 to 204.
PinePhone is now officially supported by Apache NuttX Mainline!
Follow these steps to build and boot the master
branch of NuttX…
Or download the Build Outputs from…
We’ll see this on the Serial Console…
We have updated these articles to point to the PinePhone code in NuttX Mainline…
Upcoming Features for NuttX on PinePhone…
And we’ll be able to run LVGL Touchscreen Apps on PinePhone. Stay Tuned!
These are the Upcoming Fixes for NuttX on PinePhone…
RAM Size will be increased to 2 GB: chip.h
// Allwinner A64 Memory Map
// TODO: Increase RAM to 2 GB
#define CONFIG_RAMBANK1_ADDR 0x40000000
#define CONFIG_RAMBANK1_SIZE MB(128)
Only Single Core CPU has been tested on PinePhone: pinephone/defconfig
We shall test Quad Core CPU on PinePhone: qemu-armv8a/nsh_smp/defconfig
## TODO: Enable Symmetric Multiprocessing (SMP) for PinePhone
CONFIG_ARCH_INTERRUPTSTACK=8192
CONFIG_DEFAULT_TASK_STACKSIZE=16384
CONFIG_IDLETHREAD_STACKSIZE=16384
CONFIG_PTHREAD_STACK_MIN=16384
CONFIG_SMP=y
CONFIG_SYSTEM_TASKSET=y
CONFIG_TESTING_OSTEST_STACKSIZE=16384
CONFIG_TESTING_SMP=y
Enable Memory Protection so that NuttX Apps can’t access NuttX Kernel Memory and Hardware Registers.
Porting Notes for NuttX on PinePhone…
Image Load Offset in the Linux Kernel Header isn’t used: arm64_head.S
.quad 0x480000 /* Image load offset from start of RAM */
NuttX boots OK without changing the Image Load Offset.
(Seems the Image Load Offset is not used by the U-Boot Bootloader. It’s probably used by the Linux Kernel only)
Previously the Vector Base Address Register for EL1 was set incorrectly. (See this)
The new code doesn’t have this problem.
(Is it due to the Image Load Offset?)
UPDATE: PinePhone is now officially supported by Apache NuttX RTOS (See this)
Follow these steps to build Apache NuttX RTOS for PinePhone…
Download the Source Code for NuttX…
## Create NuttX Directory
mkdir nuttx
cd nuttx
## Download NuttX OS
git clone \
https://github.com/apache/nuttx \
nuttx
## Download NuttX Apps
git clone \
https://github.com/apache/nuttx-apps \
apps
## We'll build NuttX inside nuttx/nuttx
cd nuttx
Install the Build Prerequisites below, but skip the RISC-V Toolchain…
Download the Arm Toolchain for AArch64 Bare-Metal Target aarch64-none-elf
…
(Skip the section for Beta Releases)
For Linux x64 and WSL:
For macOS:
(I don’t recommend building NuttX on Plain Old Windows CMD, please use WSL instead)
Add the downloaded Arm Toolchain to the PATH
…
## For Linux x64 and WSL:
export PATH="$PATH:$HOME/gcc-arm-11.2-2022.02-x86_64-aarch64-none-elf/bin"
## For macOS:
export PATH="$PATH:/Applications/ArmGNUToolchain/11.3.rel1/aarch64-none-elf/bin"
Check the Arm Toolchain…
$ aarch64-none-elf-gcc -v
gcc version 11.3.1 20220712 (Arm GNU Toolchain 11.3.Rel1)
Finally we configure and build NuttX…
## For QEMU: Configure NuttX for Arm Cortex-A53 Single Core
tools/configure.sh qemu-armv8a:nsh
## For PinePhone: Configure NuttX for PinePhone
tools/configure.sh pinephone:lcd
## Build NuttX
make
## Dump the disassembly to nuttx.S
aarch64-none-elf-objdump \
-t -S --demangle --line-numbers --wide \
nuttx \
>nuttx.S \
2>&1
On an old MacBook Pro 2012, NuttX builds in 2 minutes.
If we wish to use the BASIC Interpreter, follow these steps to enable it…
Then run make
to rebuild NuttX.
If the build fails with this error…
token "@" is not valid in preprocessor
Look for this file in the Arm64 Toolchain…
aarch64-none-elf/include/_newlib_version.h
And apply this patch, so that it looks like this…
// Near the end of _newlib_version.h, insert this...
#define _NEWLIB_VERSION "4.2.0"
#define __NEWLIB__ 4
#define __NEWLIB_MINOR__ 2
#endif /* !_NEWLIB_VERSION_H__ */
The NuttX Output Files may be found here…
The NuttX Binary Image nuttx.bin
will be gzipped and copied to Jumpdrive microSD as Image.gz
…
For Troubleshooting: Refer to these files…
This article explains how we may load the NuttX ELF Image nuttx
into Ghidra for inspection…
Earlier we talked about our implementation of Allwinner A64 UART…
early_uart_ready
needs to wait for UART to be ready to transmit
up_earlyserialinit
needs to initialise the UART Port
UART Driver needs to support UART Input
Let’s talk about these changes…
Allwinner A64 UART Register UART_THR
How do we wait for the UART Port to be ready before we transmit data?
See the pic above. According to the Allwinner A64 UART doc (page 563, “UART”)…
We should write data to the UART Port…
ONLY WHEN the THRE Bit is set.
(THRE means Transmit Holding Register Empty)
Where’s the THRE Bit?
THRE Bit is Bit 5 (0x20
) of UART_LSR.
UART_LSR (Line Status Register) is at Offset 0x14
from the UART Base Address.
In Arm64 Assembly, this is how we wait for the UART to be ready: a64_lowputc.S
/* PinePhone Allwinner A64:
* Wait for UART to be ready to transmit
* xb: register which contains the UART base address
* wt: scratch register number
*/
.macro early_uart_ready xb, wt
1:
/* Load the Line Status Register at Offset 0x14 from UART Base Address */
ldrh \wt, [\xb, #0x14]
/* Check the THRE Bit (Tx Holding Register Empty) */
tst \wt, #0x20
/* If UART is not ready (THRE=0), jump back to label `1:` */
b.eq 1b
.endm
How will we initialise the UART Port?
UPDATE: See the UART Configuration steps here…
According to the Allwinner A64 UART doc (page 562, “UART”)…
We might initialise the UART Port in up_earlyserialinit
like so…
Set DLAB Flag to allow update of UART Divisor…
ldr x15, =UART1_BASE_ADDRESS
mov x0, #0x80
strb w0, [x15, #0x0C]
Write the UART Divisor (Least Significant Byte) to UART_DLL…
mov x0, #(divisor % 256)
strb w0, [x15, #0x00]
Write the UART Divisor (Most Significant Byte) to UART_DLH…
mov x0, #(divisor / 256)
strb w0, [x15, #0x04]
Clear DLAB Flag to disallow update of UART Divisor…
mov x0, #0x00
strb w0, [x15, #0x0C]
(Confused? x0
and w0
are actually the same register, 64-bit vs 32-bit)
Where…
DLAB (Divisor Latch Access Bit) is Bit 7 of UART_LCR
UART_LCR (Line Control Register) is at Offset 0x0C
of the UART Base Address
UART_DLL (Divisor Latch Low) is at Offset 0x00
UART_DLH (Divisor Latch High) is at Offset 0x04
UART Divisor is computed as…
(Serial Clock Frequency / 16) / Baud Rate
TODO: What is the Serial Clock Frequency (SCLK)?
We have implemented the UART Driver for PinePhone’s Allwinner A64 UART Port…
Check out the details in this article…