Monday, March 24, 2025

Armbian on NanoPi R2S Plus and RK3328 boot

I have several Friendly Elec NanoPi boards. Recently I have been working on my NanoPi R2S Plus because Armbian didn't have support for it and while it booted the Armbian images for NanoPi R2S, the WAN port and eMMC didn't work.

I have made changes to Armbian to support the NanoPi R2S Plus, including working WAN port and eMMC. These changes have been merged into armbian/build so it is now possible to build with BOARD=nanopi-r2s-plus, to build images for current and edge kernels.

When I started I was not familiar with armbian/build, u-boot or the rk3328 boot process. I didn't find much that gave a good overview, to put all the bits into context. At least, not in terms that I could understand and feel confident I understood them correctly.

The RK3328 Datasheet Revision 1.1 Mar. 2017 should be a reliable source but some of the English is a bit unclear.

The RK3328 has internal memory including BootROM (and Internal SRAM (36KB), utilized for the initial stages of boot, including initialization of DDR RAM and loading the subsequent boot programs.

The BootROM is, as far as I can tell, truly ROM. I found no indication that it can be updated. 

The Friendly Elec Wiki page for NanoPi R2S Plus describes the Unbricking Method. This is the worst case means to load software to the system, if it is not bootable by any other means. Systems based on the RK3328 (and several other RockChip SoC models) are often described as 'unbrickable'. What this means is that no software update can make the system completely unbootable. This is because to BootROM is truly ROM: it can't be updated to invalid code. And it supports loading software via USB OTG interface, regardless of the state of any other memory. So, as long as there isn't a hardware fault, the system can be booted and updated, even when it is otherwise unbootable.

Unfortunately, Section 12 Unbricking Method of the Wiki page begins with:

If the ROM is not installed correctly, causing the development board to become bricked, and you might not have the opportunity to reinstall the ROM via an SD card, you need to enter Maskrom mode to unbrick it by erasing the storage device.

This mislead me into thinking that the ROM could be updated to an invalid state. I spent a long time searching for information about how to update the ROM, but never found any.

My conclusion is that the BootROM is truly ROM: it cannot be modified. There are no images to be installed. There is no way to 'reinstall the ROM' via an SD card or otherwise.

The code that can make the system difficult to boot ('bricked') is that on the eMMC. The SD card can always be re-flashed on another system, with a good image. Or the SD card can be replaced. But the boot code in the BootROM checks for bootable code on the eMMC before it tries to boot from the SD card. If it finds data on the eMMC consistent with it containing bootable code, then it copies the code to RAM and executes it. If the code is bad, then the boot will fail and boot from the SD card will not be attempted, even if it contains good code. The Unbricking Method allows the eMMC to be completely erased, in which case the boot code on the BootROM will find it empty and will then attempt boot from the SD card. Thus the system becomes 'unbrlcked'.

The Rockchip RK3328 Technical Reference Manual Part 1 Revision 1.2 July 2017 (I haven't yet found a Part 2), describes the boot process in a bit more detail, in Section 1.2 System Boot.

RK3328 provides system boot from off-chip devices such as SDMMC card, eMMC memory, serial nand or nor flash. When boot code is not ready in these devices, also provide system
code download into them by USB OTG interface. All of the boot code will be stored in internal bootrom. The following is the whole boot procedure for boot code, which will be stored in bootrom in advance.

The boot code in the BootROM 'which will be stored in bootrom in advance' is, as far as I can tell, written once, by Rockchip, as part of SoC manufacturing. It is not possibly to modify it via the USB OTG interface.

The initial boot code checks for an ID Block at a particular location on each of the bootable devices. I don't yet know what the ID Block content is or where it is stored, but Armbian builds SD card images that contain it, so it shouldn't be too difficult to find.

The following devices are checked for an ID Block:

  1. eMMC
  2. external SPI Nor Flash
  3. external SPI Nand Flash
  4. SDMMC card

The devices are checked in this order and code is loaded to SDRAM from the first one with a valid ID Block, and that code is executed. Subsequent devices are checked only if preceding devices do not have a valid ID Block. This is how bad data on the eMMC can 'brick' the system: if the eMMC has a valid ID Block but does not have good boot code, the boot will fail.

If none of the devices have a valid ID Block then the USB OTG interface is initialized and this can be used to load a program to SDRAM. There are programs available that allow the various devices to be erased and written with new images. It is sufficient to simply erase the eMMC, external SPI Nor Flash and external SPI Nand Flash, in order to be able to boot from the SDMMC card. In fact, the NanoPi R2S Plus does not have external SPI Nor or Nand Flash, so it is sufficient to erase the eMMC, unless the system has been modified.

What the Technical Reference Manual doesn't mention, in its 'whole boot procedure for boot code' is that there is a way to skip checking the external devices for valid ID Block and jump straight to initialization of and loading code from the USB OTG interface.

Rockchip has a Wiki page Boot Option, that describes the boot procedures in somewhat more detail but it barely mentions the option to boot via USB OTG interface. Rather confusingly, in section Boot from eMMC, it describes how to update the eMMC via the USB OTG interface. It provides sample commands but almost no explanation of what the requirements are or how the commands work. But there is a link to 'Get the board into maskrom mode'

This Rockusb wiki page provides some description of this other boot option supported by the code in the BootROM.

The NanoPi R2S Plus provides a convenient 'Mask' key, to boot into Maskrom mode regardless of whether any of the usually checked storage devices have a valid ID Block. If this button is pressed while the system is booting up, the BootROM code will not find a valid ID Block on any device and will proceed to initialize the USB OTG interface and wait for commands there.

It looks like the Reset button holds eMMC Data 0 line low. Presumably this prevents the boot code from reading a valid ID Block from the eMMC. But what about the SPI Flash and SD card? If the button only affects eMMC, then if there is bootable code in those other devices when there is unbootable code in eMMC, then holding the Reset button may result in boot from one of those other devices, rather than USB OTG. Presumably this is why the Unbricking Method specifies to remove the SD card. As NanoPi R2S Plus doesn't have any SPI Flash device, if the SD card is removed and eMMC access is effectively disabled by holding Data 0 line low, then the boot program will not find any valid ID Block on any device and will proceed to the USB OTG interface.

Details of how the next stage of boot program is loaded from eMMC or SD card are in Wiki Boot Option and can be deduced from how Armbian prepares SD card images and how the armbian-install utility writes the eMMC for boot from eMMC. The U-Boot documentation also describes how images can be prepared - there are several options.

Being unable to modify the BootROM code is a limitation of the SoC. Not a problem as long as it works but if there are any bug or limitations in the SoC BootROM code, then the only solution will be to get a new SoC (practically, a new board) with a different, improved BootROM. For example, I see some reports of the BootROM code not handling SPI Flash very well. Not much of an issue for NanoPi R2S Plus, as it doesn't have wiring for SPI Flash, but NanoPi R2S has such wiring, despite it is delivered without a chip installed. But, if an SPI Flash chip is installed, then one must work around the limitations of the BootROM. In particular, I saw reports that if a large SPI Flash is installed, the interface switches from 3bit to 4bit address mode but reset doesn't switch back so on reset the SPI Flash cannot be accessed. Only a cold boot allows boot from SPI Flash.

The BootROM code is proprietary and not made available by Rockchip. But Tom Trebisky and Marcin Wozniac, at least, have extracted the BootROM code for investigation.

See ig3/trebisky-Rockchip and ig3/MarcinWad-RK3328-Uboot-SPI for clones of their GitHub repositories, including RK3328 BootROM images, and some cryptically documented code for extracing the BootROM code in the former.

See also dmkikushin-RK3328-Uboot-SPI for a somewhat enhanced version of Marcin's repo.

Marcin described some of his difficulties getting NanoPi R2S to boot from SPI Flash in his post to the u-boot mailing list 2022-12-22.

From u-boot  Thu Dec 22 20:15:53 2022
From: =?UTF-8?Q?Marcin_Wo=c5=baniak?= <marcin.wadowice () gmail ! com>
Date: Thu, 22 Dec 2022 20:15:53 +0000
To: u-boot
Subject: Rockchip RK3328 4-byte addressing problem in SPI
Message-Id: <f3451d5f-7b3b-e393-4ea7-370a86d45458 () gmail ! com>
X-MARC-Message: https://marc.info/?l=u-boot&m=167175500517093

Hello,

I would like to let you know how i've lost about 2 weeks because of lack 
of Rockchip BOOTROM documentation.

I tried to use Mainline Uboot to Boot NanoPI R2S using soldered SPI 
Winbond 25Q256 chip.

I had to make some patches in Uboot core files to make it work.

First one adds SPI boot device. File 
|arch/arm/mach-rockchip/rk3328/rk3328.c with adding: |

|[BROM_BOOTSOURCE_SPINOR] "/spi@ff190000", |const char * const 
boot_devices ||

Second was |arch/arm/mach-rockchip/spl-boot-order.c and last if statement:|

|if (!uclass_get_device_by_of_offset(UCLASS_SPI, node, &parent)) return 
BOOT_DEVICE_SPI;|

||

This patch is added because RockChip SPI driver is using UCLASS_SPI 
instead of SPI_FLASH so boot device wasn't connecting with right DM 
Driver rk_spi.c

Third one fixed SPL and it was added at the end: |drivers/spi/rk_spi.c
|

|DM_DRIVER_ALIAS(rockchip_rk3288_spi, rockchip_rk3328_spi);|

After that board booted successfully. Second bigger problem was a 
mistake and lack of RockChip Bootrom documentation.

I've soldered 32Mbyte Flash and didn't know that U-Boot in SPL was 
switching it's 3-byte address mode to 4-byte one to gain access to full 
32 Mbytes.

The problem was that board booted in cold-start but after issuing 
"reset" - it was going to MASKROM mode like no SPI was soldered or empty.

After loosing two weeks i figured that RockChip bootrom talks to SPI 
using only 3-byte addressing.

So leaving 4-byte switched by UBoot SPI chip made it unusable to 
RockChip Bootrom. I found this by dumping Bootrom and decompiling it.

I don't know if it's enough information to make a patch to MainLine  (at 
least for SPI boot in RK3328).

Problem with 3-byte addressing is a complete different story.

Sorry for my "unproffesional" mail. I am writing this type of message 
for a first time.

Marcin

 

Follow the thread for all the followups but note this from Kever:

Sync with engineer working on these area, and get below: Yes, this "4-byte addressing problem in SPI" issue is in SoCs including rk3328, this only happen on SPI NOR size at least 32MB, most of customers only use smaller size SPI NOR. If 32MB+ SPI NOR is needed for the project, them we would suggest to use those SPI flash with 3&4 byte command set support; or else workaround to do the snor_deinit and exit_4byte_address_mode will needed before every reboot/hot reset in the system.

 And a further response from Michael:

I'm not sure this is a problem of the SoC (bootrom). The issue is either board design or flash usage pattern. If you enable 4byte mode and don't have a hardware reset pin (or don't assert it during board reset) you are likely in trouble. Have a look at JESD216D. The 16th DWORD lists 6 different software reset methods, there is even a "no software reset instruction is supported". That's also true for the "exit 4byte mode": there is no common way to exit it. You could try to read the SFDP tables. But that will only work for flash devices with (valid) SFDP. So in the general case, you are probably f** when you enable the 4 byte mode, boot from that flash and don't have a reset. As mentioned here, you should instead use the 4byte opcodes where possible. -michael

These details aren't important for my NanoPi R2S Plus, but they illustrate why it is sometimes important to have complete information about the board, including the boot code and how it initializes hardware. For the RK3328 SoC, it seems the only way to get the full details of the boot code is to extract it from the relevant system, disassemble it and figure out what it is doing, as Rockchip does not provide binaries or source code for the BootROM code. 

It would also be interesting to know:

  • If there are different versions of the BootROM code
  • What, if anything, the BootROM code writes to console
  • How it initializes the SoC and external devices
  • How it checks for the ID Block
  • How it loads the next stage software and executes it
  • The full implementation of the USB OTG interface

 

Saturday, March 15, 2025

systemctl is-enabled is strange

The is-enabled command of the systemd systemctl command is purported to:

Checks whether any of the specified unit files are enabled (as with enable). Returns an exit code of 0 if at least one is enabled, non-zero otherwise.

But read the documentation carefully because, in my opinion, it doesn't do this.

In particular, its exit code cannot be relied on to indicate if a unit is enabled.

The text output is more useful but to use this in automation requires capturing the text output and parsing it to determine whether the unit might be enabled.

But look at the table of text outputs. Look at 'static'. The documentation says:

The unit file is not enabled, and has no provisions for enabling in the [Install] unit file section.

But it also says that for such a unit the exit code of 'is-enabled' will be 0, indicating the unit is enabled.

This is inconsistent: is the unit enabled as indicated by the exit code? Or is it "not enabled, and has no provisions for enabling" as indicated by the specification of the text output "static"?

In my opinion, if the unit is not enabled then the exit code should be non-zero, to be consistent with the intent that a 0 exit code indicates an enabled unit.

Similarly for "indirect":

The unit file itself is not enabled, but it has a non-empty Also= setting in the [Install] unit file section, listing other unit files that might be enabled, or it has an alias under a different name through a symlink that is not specified in Also=. For template unit files, an instance different than the one specified in DefaultInstance= is enabled.

But again, despite "The unit file itself is not enabled", the exit code will be 0. So, is it enabled per the exit code or not enabled per the definition of the output message?

There are other cases with similar ambiguities. Some have descriptions that are so laden with esoteric systemd jargon that they are effectively meaningless to the ordinary reader, leaving it unknown if the unit is enabled and how it will behave in any particular circumstance.

And even parsing the text message is not always adequate to determine if a unit is enabled. Consider "symlink"

The name is an alias (symlink to another unit file).

So, regardless of whether that other unit file is enabled or not, the exit code will be 0 and the text message will be "symlink". That's not very helpful. In the case of a symlink, it will be necessary to follow the link and determine whether that linked unit is enabled or not: which is the purported purpose of the 'is-enabled' command - but it just doesn't bother to do it.

For some particular types of unit files (e.g. service units that are not symlinks, aliases, dynamic, etc.) then the exit code and output text both provide unambiguous indication of whether the unit is enabled, but for others, not so much.

So, check to be sure the unit you are checking is one of the types for which 'is-enabled' provides unambiguous, correct output, or write your own function that doesn't depend on the 'is-enabled' command.
 

Wednesday, March 5, 2025

Armbian Build Framework

 I have a NanoPi R2S Plus. Armbian supports many of the NanoPi boards but doesn't have a configuration specific for the NanoPi R2S Plus. I have been running the NanoPi R2S configuration. They are similar boards but the Plus adds eMMC memory and has a different chip for the WAN network port so neither of these work.

Recently I started looking to add support for the NanoPi R2S Plus to Armbian to enable the eMMC and WAN port. It was the first time I had used the Armbian Build Framework. Previously I had only downloded pre-built images. What I discovered is that the build framework is very flexible but, therefore, complex and there is little documentation. This makes it difficult to work with: simple objectives are difficult to achieve.

Given the lack of documentation, copying working examples is the best way to get things done. There is support for over 300 boards in armbian/build. Configuration for some is seemingly simple while others are very sophisticated. But even the simple ones have a lot going on under the covers: the build framework itself is very sophisticated.

The problems with the NanoPi R2S Plus eMMC and WAN port were rooted in the device tree. But there is not one but two device trees to deal with: the device tree of the Linux kernel affects the booted system but u-boot has its own device tree that must be complete enough to boot Linux. In my case, I wanted to be able to boot from eMMC so needed to add that to both the u-boot and Linux device trees.

I had to add a new board. That required adding a configuration for it in the config/boards directory. To start, I copied the configuration for the NanoPi R2S to create config/boards/nanopi-r2s-plus.csc. This was the first step: the exact same configuration, just a different name. And learning to build an image with the compile.sh script.

Then I had to modify the device tree for the kernel, to enable the eMMC and WAN ports. FriendlyElec provides images for the NanoPi R2S Plus that have working eMMC and WAN port, including a Debian Bookworm image and images based on OpenWRT, one of which was pre-installed on the system. So I began to learn enough about device trees to be able to read and compare these and understand how the device tree for the NanoPi R2S is assembled by the Armbian build framework.

The syntax of the device tree is simple enough but the semantics and relations to the hardware and Linux kernel and device drivers is still bewildering. The device tree solves a complex problem and is necessarily complex itself. To master it, one must know intimate details of the hardware and its configuration and of the Linux drivers that read the device tree to get the information they require about the hardware. While I can compare and cut and paste the device tree source files, I am a long way from understanding the end-to-end interactions between the device drivers and the hardware. Fortunately, with all the examples available, it wasn't necessary to develop the device tree from hardware specs and the device driver code. All I had to do is copy the bits relevant to the eMMC and WAN port from the images where they worked into the Armbian build framework configuration I was creating for the NanoPi R2S Plus. Not quite trivial, but nearly so. 

My first attempt to change the kernel device tree failed, yielding an image that wouldn't boot. It couldn't find an image to boot and failed back to TFTP/BOOTP. I never did find out what went wrong but I think a collection of incompatible patches applied to the kernel that, at the time, I didn't know persisted between builds.

I scrubbed my clone of armbian/build and second attempt produced a working image that I progressed through several modifications to get first the eMMC and then the WAN port working. This time, every image booted and worked as expected.

In addition to the new board configuration, I had to create device tree source (dts) file for the board. I did this two ways: first making one that included the dts for the NanoPi R2S in armbian/build then overrode the bits related to the eMMC and WAN port. But while doing this I saw that the Linux kernel itself has support for the NanoPi R2S Plus. It has working eMMC but the WAN port doesn't work. So I made a second version of dts for NanoPi R2S Plus that includes the dts from the kernel then overrides the bits related to the WAN port.

Ultimately, I only created two files: the board configuration and the dts.

The board configuration is 'config/boards/nanopi-r2s-plus.csc':

# Rockchip RK3328 quad core 1GB 2 x GBE USB2
BOARD_NAME="Nanopi R2S Plus"
BOARDFAMILY="rockchip64"
BOARD_MAINTAINER=""
BOOTCONFIG="nanopi-r2s-rk3328_defconfig"
KERNEL_TARGET="current,edge"
KERNEL_TEST_TARGET="current"
DEFAULT_CONSOLE="serial"
MODULES="g_serial"
MODULES_BLACKLIST="rockchipdrm analogix_dp dw_mipi_dsi dw_hdmi gpu_sched lima hantro_vpu"
SERIALCON="ttyS2:1500000,ttyGS0"
HAS_VIDEO_OUTPUT="no"
BOOT_FDT_FILE="rockchip/rk3328-nanopi-r2s-plus-a1.dtb"

This is a copy of the configuration for NanoPi R2S. I changed the BOARD_NAME and BOOT_FDT_FILE. 'dtb' is 'device tree blob' - a binary form of the device tree. The build framework creates this from the device tree source (dts) files. So I had to create the dts file that corresponds to the specified dtb.

First I created patch/kernel/archive/rockchip64-6.12/dt/rk3328-nanopi-r2s-plus-a1.dts. 

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 FriendlyElec Computer Tech. Co., Ltd.
 * (http://www.friendlyarm.com)
 */

/dts-v1/;
#include <dt-bindings/input/linux-event-codes.h>
#include "rk3328-nanopi-r2s.dts"

/ {
    model = "FriendlyElec NanoPi R2S Plus";
    compatible = "friendlyelec,nanopi-r2s-plus", "friendlyelec,nanopi-r2", "rockchip,rk3328";

    gpio-keys {
        compatible = "gpio-keys";
        #address-cells = <1>;
        #size-cells = <0>;
        autorepeat;

        pinctrl-names = "default";
        pinctrl-0 = <&gpio_key1>;

        button@0 {
            gpios = <&gpio0 RK_PA0 GPIO_ACTIVE_LOW>;
            label = "reset";
            linux,code = <BTN_1>;
            linux,input-type = <1>;
            gpio-key,wakeup = <1>;
            debounce-interval = <100>;
        };
    };

    vcc_rtl8153: vcc-rtl8153-regulator {
        compatible = "regulator-fixed";
        gpio = <&gpio2 RK_PC6 GPIO_ACTIVE_HIGH>;
        pinctrl-names = "default";
        pinctrl-0 = <&usb30_en_drv>;
        regulator-always-on;
        regulator-name = "vcc_rtl8153";
        regulator-min-microvolt = <5000000>;
        regulator-max-microvolt = <5000000>;
        off-on-delay-us = <5000>;
        enable-active-high;
    };
};

&mach {
    hwrev = <0>;
    model = "NanoPi R2S";
};

&emmc {
    status = "okay";
};

&i2c0 {
    status = "okay";
};

&leds {
    status = "okay";

    led@2 {
        gpios = <&gpio2 RK_PB7 GPIO_ACTIVE_HIGH>;
        label = "lan_led";
    };

    led@3 {
        gpios = <&gpio2 RK_PC2 GPIO_ACTIVE_HIGH>;
        label = "wan_led";
    };
};

&rk805 {
    interrupt-parent = <&gpio1>;
    interrupts = <RK_PD0 IRQ_TYPE_LEVEL_LOW>;
};

&vccio_sd {
    status = "okay";
};

&io_domains {
    vccio3-supply = <&vccio_sd>;
};

&sdmmc {
    vqmmc-supply = <&vccio_sd>;
    max-frequency = <150000000>;
    sd-uhs-sdr50;
    sd-uhs-sdr104;
    status = "okay";
};

&sdmmc_ext {
    status = "disabled";
};

&sdio_pwrseq {
    status = "disabled";
};

&pinctrl {
    pmic {
        pmic_int_l: pmic-int-l {
            rockchip,pins = <1 RK_PD0 RK_FUNC_GPIO &pcfg_pull_up>;
        };
    };

    rockchip-key {
        gpio_key1: gpio-key1 {
            rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;
        };
    };

    usb {
        otg_vbus_drv: otg-vbus-drv {
            rockchip,pins = <1 RK_PD2 RK_FUNC_GPIO &pcfg_pull_none>;
        };

        usb30_en_drv: usb30-en-drv {
            rockchip,pins = <2 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;
        };
    };
};

/delete-node/ &rtl8211e;

&gmac2io {
    phy-handle = <&ethpy1>;
    snps,reset-delays-us = <0 15000 50000>;
    tx_delay = <0x22>;
    rx_delay = <0x12>;

    mdio {
        compatible = "snps,dwmac-mdio";
        #address-cells = <1>;
        #size-cells = <0>;

        ethpy1: ethernet-phy@1 {
            compatible = "ethernet-phy-ieee802.3-c22";
            reg = <0x1>;
            realtek,ledsel = <0xae00>;
        };
    };
};


This is a copy of the dts for the NanoPi R2s, with modifications to make the eMMC and WAN port work. 

In the node for '&emmc' I changes status from "disabled" to "okay". That's all it took to get the eMMC working - all the relevant content was already there, just disabled because the NanoPi R2S doesn't have eMMC memory.

The changes to get the WAN port working are a little more complicated but I was able to copy them from the dts in the FriendlyElec kernel source. The changes are at the end of the file. First '/delete-node/ &rtl8211e;' which deletes the node for the rtl8211e chip that's in the NanoPi R2s, then the '&gmac2io' node that overrides some parameters and adds a new 'ethpy1' node for the rtl8211f chip that is in the NanoPi R2S Plus. I couldn't have written these myself. I don't know enough about the hardware or the device drivers, despite having downloaded technical papers that describe the differences between the rtl8211e and rtl8211f. But copy/paste is easy. I only had to figure out what were the relevant bits, which is simple, in hindsight, though it took me a while to learn enough about the device tree syntax.

This version is based on the support for NanoPi R2S in Armbian build. Through a chain of includes, it includes several other files from Armbian build.

My second implementation was based on the support in the kernel. I created patch/kernel/archive/rockchhip64-6.12/dt/rk3328-nanopi-r2s-plus-rev00.dts:

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2025 Ian Goodacre
 */

/dts-v1/;
#include "rk3328-nanopi-r2s-plus.dts"

/delete-node/ &rtl8211e;

&gmac2io {
    phy-handle = <&rtl8211f>;
    snps,reset-delays-us = <0 15000 50000>;
    tx_delay = <0x22>;
    rx_delay = <0x12>;

    mdio {
        rtl8211f: ethernet-phy@1 {
            compatible = "ethernet-phy-ieee802.3-c22";
            reg = <0x1>;
            realtek,ledsel = <0xae00>;
        };
    };
};

This includes the dts for NanoPi R2S Plus from the kernel, in which the eMMC is already enabled but the nodes for the WAN port are still for the old rtl8211e chip. So, again, I had to delete the node for rtl8211e, add a node for rtl8211f and override a very few parameters in the gmac2io node.

I chose the name of the dts to be clear what board it was for and with 'rev00' to avoid name conflict with the dts in the kernel and following the pattern in the FriendlyElec kernel of 'revXX' for various versions supporting various boards. The 'patch' gets added to the kernel source without changing any of the existing files.

With these changes I build a kernel that booted from SD card and had eMMC and WAN port both working.

Finally, I copied the dts file to patch/kernel/archive/rockchhip64-6.13/dt/rk3328-nanopi-r2s-plus-rev00.dts. 6.12 is the 'current' kernel and 6.13 is the 'edge' kernel, at least, currently. Then I build an 'edge' image and all was well.

The final complication is that I want to boot from eMMC rather than SD card. But u-boot device tree is separate from the kernel device tree and it, like the kernel device tree, was for the NanoPi R2S which doesn't have eMMC. So I had to modify the u-boot device tree. I found this harder because the way the u-boot source is patched is different and there was a little bug in the tool for generating the patch file that held me up for most of a day. But, ultimately, it isn't much more difficult to patch the dts in u-boot than in the kernel.

But, along the way, someone pointed out the more recent version of u-boot has native support for NanoPi R2S Plus. The configuration for NanoPi R2S was based on u-boot v2022.07 but v2025.01 has the support. It took me a while to learn enough to get the build for NanoPi R2S Plus to use the more recent version of u-boot but in the end the changes were simple.

I changed the board config: config/boards/nanopi-r2s-plus.csc:

# Rockchip RK3328 quad core 1GB 2 x GBE USB2
BOARD_NAME="Nanopi R2S Plus"
BOARDFAMILY="rockchip64"
BOARD_MAINTAINER=""
BOOTBRANCH="tag:v2025.01"
BOOTPATCHDIR="v2025.01"
BOOTCONFIG="nanopi-r2s-plus-rk3328_defconfig"
KERNEL_TARGET="current,edge"
KERNEL_TEST_TARGET="current"
DEFAULT_CONSOLE="serial"
MODULES="g_serial"
MODULES_BLACKLIST="rockchipdrm analogix_dp dw_mipi_dsi dw_hdmi gpu_sched lima hantro_vpu"
SERIALCON="ttyS2:1500000,ttyGS0"
HAS_VIDEO_OUTPUT="no"
BOOT_FDT_FILE="rockchip/rk3328-nanopi-r2s-plus-rev00.dtb"
 

BOOTBRANCH, BOOTPATCHDIR and BOOTCONFIG all affect the u-boot build. BOOTBRANCH specifies the branch of the u-boot repository, overriding the 'default' v2022.07 for rockchip64. And BOOTPATCHDIR specifies what directory to get u-boot patches from. The default directory contains patches for many boards, some of which are incompatible with v2025.01, causing build failure. There aren't many patches in the 'v2025.02' directory (patch/u-boot/v2025.01). All I had to do was add a directory for the NanoPi R2S Plus: patch/u-boot/v2025.01/board_nanopi-r2s-plus and put a patch file in it. The directory name is important. It is specific to the board, because it begins with 'board_' and the remainder matches the name of the board configuration file (I think that's where 'nanopi-r2s-plus' comes from).

In patch/u-boot/v2025.01/board_nanopi-r2s-plus I created patch file 001-add-trust-ini.patch:

diff --git a/trust.ini b/trust.ini
new file mode 100644
index 0000000..4af021a
--- /dev/null
+++ b/trust.ini
@@ -0,0 +1,15 @@
+[VERSION]
+MAJOR=1
+MINOR=0
+[BL30_OPTION]
+SEC=0
+[BL31_OPTION]
+SEC=1
+PATH=bl31.elf
+ADDR=0x10000
+[BL32_OPTION]
+SEC=0
+[BL33_OPTION]
+SEC=0
+[OUTPUT]
+PATH=trust.bin

This adds file 'trust.ini' to the root of the u-boot source, which is required to build the u-boot artifact. Before I added this patch the build failed because it couldn't find it. I just copied the patch from the configuration for v2022.07.

And with these changes, the build succeeded. It isn't tested yet, but I think it is getting close.

It took me a few weeks to work through all this, from setting up the build environment through learning about device trees, RockChip SOC booting, u-boot and the Armbian build framework. It looks simple in hindsight, but initially I was almost completely in the dark, stumbling about, breaking things.

Thursday, February 20, 2025

Rust in the Linux kernel

I have been seeing a lot of news about drama and conflict related to Rust in the Linux kernel recently.

My first experience with Rust was some years ago when I was debugging and enhancing Anki. Rust had been introduced in the back end and some of the bugs I was working to understand and resolve were in the Rust code, so I had to learn enough about Rust to set up a development environment and to be able to read, understand and modify the Anki Rust code. It was an unpleasant experience. I found the Rust documentation to be inadequate, leading to many days of research and experimentation to learn enough about what the Rust toolchain was doing. I was able to hack at the Anki Rust code sufficiently to find and fix several bugs but it did not leave me enamoured with Rust. The experience made me sympathetic to the concerns of the Linux kernel maintainers who are concerned about the complexity and supportability of the Linux kernel if Rust is included.

The Linux kernel documentation has a section on Rust. It begins with an overview that includes:

If you are an end user, please note that there are currently no in-tree drivers/modules suitable or intended for production use, and that the Rust support is still in development/experimental, especially for certain kernel configurations.

Given the intensity of the disputes I had been hearing about, I wondered if this was obsolete and in fact Rust was now essential to the kernel.

 So I downloaded the current mainline kernel and built it without installing any of the Rust toolchain. No problem, so evidently Rust is not yet required to build a kernel.

Then I wanted to see what it was like building a kernel that included the Rust support.

I was doing this on a system running Debian 12 (Bookworm).

I installed the required Rust packages from the Debian repositories but they were too old:

$ make LLVM=1 rustavailable
***
*** Rust compiler 'rustc' is too old.
***   Your version:    1.63.0
***   Minimum version: 1.78.0
***
***
*** Please see Documentation/rust/quick-start.rst for details
*** on how to set up the Rust support.
***

I found a page that described how to install Rust on Debian 12:

https://idroot.us/install-rust-debian-12/

I downloaded https://sh.rustup.rs, reviewed it then ran it, proceeding with standard installation.

Through a sequence of trial and error making the rustavailable target, I installed various other prerequisites:

$ cargo install bindgen-cli

$ sudo apt install libclang-dev clang

$ rustup component add rust-src

After these I get 'Rust is available!'

But also:

*** libclang (used by the Rust bindings generator 'bindgen')
*** version does not match Clang's. This may be a problem.
***   libclang version: 15.0.6
***   Clang version:    14.0.6

While I had installed Debian libclang-dev and it was version 14, somehow libclang 15 was installed and found. I guess it came along with the installation of Rust but I didn't investigate to determine where version 15 was found and how it was installed.

So, the rustavailable target reports Rust is available but when I make the menuconfig target, there is no option under General setup for Rust support.

Fortunately I found

 
This provided the configuration requirement missing from the Rust Quick Start Guide in the Linux kernel documentation:

'Module versioning support' in section 'Enable loadable module support' must not be enabled. Somehow my configuration had it enabled: I had copied the configuration from /boot, so either it is enabled there or it is a new configuration option enabled by default. I didn't determine where the setting came from but I disabled it and then 'Rust support' appeared in 'General setup'.

So I enabled Rust support and re-ran make and saw it building various targets in the 'rust' directory.

There were no obvious errors during the build, but I haven't tried running the built kernel and wouldn't know how to test the bits written in Rust if I did. I haven't learned much about what is written in Rust. But, superficially, it seems I now have the Rust build tools set up well enough that I can build a kernel including components written in Rust.

That I could not use the Rust packages from Debian 12 (Bookworm) is a reflection of how young and volatile the Rust language and build tools are. This makes me feel that Rust is not yet mature enough to be writing essential components of the Linux kernel in it, but that isn't happening yet. Rust in the Linux kernel is, it seems to me, still experimental. If the experiment succeeds, perhaps by then the Rust language and build tools will have stabilized sufficiently that the version of Rust in stable version of Debian will be sufficient.

In summary, Rust is not yet an essential part of the Linux kernel. The integration of Rust is still experimental.

Despite being experimental or, at least, still optional, according to
The Road to Rustification: Lessons from the Linux Kernel Development Process, the Rust code is already 15% of the kernel codebase with an expectation that it will become 25% in 2025. 

I understand that Rust offers memory safety that is not guaranteed by the existing C code and build tools and that errors in memory management are a significant percentage of all the errors in the Linux kernel. I haven't come across an explanation why, of all the new languages that guarantee memory safety, Rust was selected for inclusion in the Linux kernel. 
 
Evidently, Rust has advocates with resources to push it forward into the kernel. But where is the record of comparison of Rust with the other options for ensuring memory safety and avoiding or eliminating a common type of error from the kernel? Where is the evidence that Rust is the best choice?

Maybe Rust, like C, isn't the best choice. Maybe it is just a choice with enough support that it is being implemented, despite concerns and objections from some quarters. But if there is some other language that would be better, more effective, easier and less contentious, it is irrelevant if no one is advocating it and willing to do the work to make it available and use it in the kernel.

I have great respect for the people who build and maintain the Linux kernel. Their achievements to date are amazing. While past success is no guarantee of future performance, as they say in the financial industry, I think there is good reason to trust and respect the decisions of the kernel maintainers.

And, ultimately, Linux is open source. Rather than complaining about the team at kernel.org, the Rust advocates can always do as they wish with the fork of the kernel and, benefiting from the superiority of Rust, they may so outperform the current development effort in speed and quality that soon everyone will follow them. There is no good reason for dispute and disrespect. They are free to do better.


 

Labels