mnemos-d1

MnemOS for the Allwinner D1

This directory contains MnemOS platform support for the Allwinner D1 RISC-V SoC.

Folder Layout

  • src/: The top-level crate that produces MnemOS binaries for the Allwinner D1.
  • board-configs/: [mnemos-config] configurations for supported D1 single-board computers.
  • d1-core/: Core MnemOS implementation for the Allwinner D1. This is factored out into a separate crate so that it can be built without forced-target="riscv64imac-unknown-none-elf" (unlike the top-level mnemos-d1 crate), so that unit tests for the platform implementation can be run on development host targets.
  • d1-config/: [mnemos-config] type definition crate for the Allwinner D1.

Getting started with MnemOS on the D1

Building

This crate contains a separate Cargo bin target for each supported D1 board. These bin targets depend on the mnemos-d1 crate for the majority of the platform implementation, and configure the D1's I/O pins based on how those pins are mapped to pins on the board. The following bin targets are currently provided:

The simplest way to build a MnemOS image for an Allwinner D1 board is to use the just build-d1 Just recipe.

[!IMPORTANT]

Running Just recipes requires Just to be installed. See https://just.systems for details on using Just.

The just build-d1 recipe takes an optional argument to select which bin target is built; by default, the mq-pro bin target is selected. For example:

$ just build-d1             # builds MnemOS for the MangoPi MQ Pro
$ just build-d1 mq-pro      # also builds MnemOS for the MQ Pro
$ just build-d1 lichee-rv   # builds MnemOS for the Lichee RV

Alternatively, Allwinner D1 images can be built manually using Cargo. To build using Cargo, run the following commands:

# set which board binary to build
$ export BOARD="mq-pro" # or "lichee-rv"

# build the MnemOS binary for that board
$ cargo build -p mnemos-d1 --board $BOARD --release

# produce a binary that can be flashed to the board
$ cargo objcopy -p mnemos-d1 --bin $BOARD -- release \
     -O binary target/riscv64imac-unknown-none-elf/mnemos-$BOARD.bin

[!IMPORTANT]

Note that cargo-binutils must be installed in order to use cargo objcopy. The just build-d1 recipe will prompt to install it automatically.

Running

The quickest way to get MnemOS running on a D1 SBC is using xfel. An explanation of xfel and alternative ways to run MnemOS follows in a later section.

The just flash-d1 recipe will build MnemOS and then flash it to your D1 board. Like build-d1, this recipe takes an optional argument to select which board target to build, and defaults to building for the MQ Pro if none is provided.

For example, running just flash-d1 mq-pro should print output like this:

$ just flash-d1 mq-pro
       Found cargo objcopy
   Compiling mnemos-d1 v0.1.0 (/home/eliza/Code/mnemos/platforms/allwinner-d1/boards)
    Finished release [optimized] target(s) in 2.66s
    Finished release [optimized] target(s) in 0.07s
xfel ddr d1
xfel write 0x40000000 platforms/allwinner-d1/boards/target/riscv64imac-unknown-none-elf/mnemos-mq-pro.bin
100% [================================================] 241.281 KB, 450.510 KB/s
xfel exec 0x40000000

After flashing you can connect using just crowtty serial <UART-DEVICE> (this is not the FEL device!). Alternatively any serial terminal will do - note that we use 115200 baud unlike the builtin bootloader which is at 9600.

[!NOTE]

When flashing the MangoPi MQ Pro using just flash-d1, ensure that the USB cable is plugged in to the USB-C port on the board labeled as "OTG" on the silkscreen, not the one labeled as "HOST".

For the Lichee RV, use the port next to the FEL button. Enter FEL mode by holding down FEL, and then plugging in USB-C. lsusb should show: Allwinner Technology sunxi SoC OTG connector in FEL/flashing mode

Once a board has been successfully flashed, attempting to flash it again using xfel may fail. This can be fixed by unplugging the USB cable from the board and then plugging it back in.

Dependencies

In order to use the just flash-d1 recipe, the cargo-binutils Cargo plugin is required. If it is not found, the flash-d1 recipe will prompt the user to install it.

The llvm-tools-preview Rustup component is a dependency of cargo-binutils. It should be automatically installed by the rust-toolchain.toml file in this repo, but can be manually installed by running $ rustup component add llvm-tools-preview.

Finally, xfel is necessary to actually flash the board. Instructions for building xfel from source for Linux, MacOS, and Windows can be found here. Pre-built xfel binaries for Windows are available here.

[!NOTE]

In addition to the official distribution channels, I (Eliza) have written a Nix derivation for xfel. Eventually, I'd like to upstream this to Nixpkgs, but it can currently be used as a git dependency. Note that when using this, xfel's udev rules must be added to the system's udev rules; see here for an example.

Boot Procedure

On reset, the D1 executes its internal Boot ROM (BROM), which either loads a first stage bootloader or enters FEL mode.

The BROM will:

  • Do some initial setup of the clocks
  • Check the FEL pin: if it is low (connected to GND), it will enter FEL mode
  • Check the SD card (SMHC0) and connected SPI flash for a valid eGON header
  • Fall back to FEL mode if no valid header is found

You can find additional info about BROM and FEL on the linux-sunxi wiki. In short, in FEL mode the D1 will present itself as a USB device and allow (using a custom protocol) things like reading data, writing data and starting code execution. Different tools like sunxi-fel and xfel have been developed that speak this protocol and implement functionality like initializing DRAM (by loading code that does this into SRAM and then executing it). However, this is all volatile, so these actions have to be repeated on reset.

To have a persistent boot, we can use one of the media that is probed by the BROM for an eGON header: the SD card or SPI flash (if you have this on your board). On this persistent medium, a first stage bootloader needs to be present, preceded by a valid eGON header. The BROM will then load it into SRAM and execute it.

This bootloader has to, at a minimum, initialize DRAM and load either a second stage bootloader or the actual application (in our case MnemOS) into DRAM so it can transfer control of execution to it.

DRAM initialization

The initialization code for the DDR3 RAM is somewhat of a black box. It is hard to determine who was first in reverse-engineering the necessary Allwinner blobs, but one candidate is this, which served as a basis for the original SPL work by smaeul. This is now deprecated, as it is included in smaeul's u-boot fork, which will hopefully be merged upstream one day.

Oreboot has a Rust port of this code. TODO: figure out why some of the DRAM parameters depend on which board is selected.

SD card layout

The linux-sunxi wiki has some more information on the required layout of the SD card. The eGON header with first stage bootloader has to be located at an offset of either 8KB or 128KB, in order to leave some space for a partition table (so you can have, e.g., a FAT filesystem on your SD card at the same time).

To prepare your SD card, you can do the following (where sdX is the SD card):

sudo dd if=first-stage-boot.bin of=/dev/sdX bs=1024 seek=8 conv=sync
sudo dd if=mnemos.bin of=/dev/sdX bs=1024 seek=40 conv=sync

MnemOS currently does not have its own first stage bootloader, but it is possible to adapt the oreboot bt0 for this role.

License

MIT + Apache 2.0.