Ubuntu application layer support

Video hardware codec support

Mpp is a set of video codec APIs provided by Rockchip for RK3399, and based on mpp, Rockchip provides a set of gstreamer codec plugins. Users can use the gstreamer to do video codec application according to their own needs, or directly call mpp to realize hardware codec acceleration.

The Ubuntu system released by Firefly has provided complete gstremaer and mpp support, and provides a corresponding demo for users to develop reference.


  • Ubuntu 16.04 下,gstreamer 1.12 Already installed in the /opt/ directory

  • Ubuntu 18.04下, gstreamer 1.12 Already installed in the system

/usr/local/bin/h264dec.sh Test H264 decoding

/usr/local/bin/h264enc.sh Test H264 coding

Users can refer to these two scripts to configure their own gstreamer application.


  • The mpp-related dev packages are already installed on the system.

    /opt/mpp/ are the related demos and source files of MPP codec respectively.

    /opt/mpp/ files are the demo and source files for the mpp codec.


ROC-3399-PC-PLUS support OpenGL ES1.1/2.0/3.0/3.1。

The Ubuntu system released by Firefly already provides full OpenGL-ES support. Run glmark2-es2 to test openGL-ES support. If you want to avoid the impact of the screen refresh rate on the test results, you can use the following command test on the serial terminal.

# systemctl stop lightdm
# export DISPLAY=:0
# Xorg &
# glmark2-es2 –off-screen

In the Chromium browser, type: chrome://gpu in the address bar to see hardware acceleration support.


  1. EGL is an extension of the OpenGL for the x window system on the arm platform, which is equivalent to the glx library under x86.

  2. Since the driver mode setting used by Xorg will load libglx.so by default (disabling glx will cause some applications failing to detect the glx environment), libglx.so will search the dri implementation library in the system. However, rk3399 Xorg 2D acceleration is directly based on DRM, and does not implement dri library, so libglx.so will report the following error during startup.

    (EE) AIGLX error: dlopen of /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so failed

This has no effect on system operation and does not need to be processed.

  1. Based on the same reason, some applications will report the following errors during startup, without processing, and will not affect the operation of the application.

    libGL error: unable to load driver: rockchip_dri.so
    libGL error: driver pointer missing
    libGL error: failed to load driver: rockchip
  2. Some versions of Ubuntu released by Firefly have turned off loading libglx.so by default. In some cases, some applications will run the following error.

    GdkGLExt-WARNING **: Window system doesn't support OpenGL.

    The method of correction is as follows:

    Delete /etc/X11/xorg.conf.d/20-modesetting.conf three-line configuration。

    Section "Module"
         Disable     "glx"


The Ubuntu system released by Firefly has added opencl1.2 support, and can run the system’s built-in clinfo to get the platform opencl related parameters.

firefly@firefly:~$ clinfo
Platform #0
 Name:                                  ARM Platform
 Version:                               OpenCL 1.2 v1.r14p0-01rel0-git(966ed26).f44c85cb3d2ceb87e8be88e7592755c3

 Device #0
   Name:                                Mali-T860
   Type:                                GPU
   Version:                             OpenCL 1.2 v1.r14p0-01rel0-git(966ed26).f44c85cb3d2ceb87e8be88e7592755c3
   Global memory size:                  1 GB 935 MB 460 kB
   Local memory size:                   32 kB
   Max work group size:                 256
   Max work item sizes:                 (256, 256, 256)

TensorFlow Lite

RK3399 support for neural network GPU acceleration scheme LinuxNN, Firefly released Ubuntu system, has added support for LinuxNN.

Under opt/tensorflowbin/, run test.sh to test the Demo of the MobileNet model image classifier and the target detection of the MobileNet-SSD model.

firefly@firefly:/opt/tensorflowbin$ ./test.sh
Loaded model mobilenet_ssd.tflite
resolved reporter
nn version: 1.0.0
filename:libarmnn-driver.so     d_info:40432     d_reclen:40s
[D][ArmnnDriver]: Register Service: armnn (version: 1.0.0)!
first invoked time: 1919.17 ms
average time: 108.4 ms
validCount: 26
car     @ (546, 501) (661, 586)
car     @ (1, 549) (51, 618)
person  @ (56, 501) (239, 854)
person  @ (332, 530) (368, 627)
person  @ (391, 541) (434, 652)
person  @ (418, 477) (538, 767)
person  @ (456, 487) (602, 764)
car     @ (589, 523) (858, 687)
person  @ (826, 463) (1034, 873)
bicycle @ (698, 644) (1128, 925)
write out.jpg succ!

Screen rotation

The Ubuntu system released by Firefly, if you need to rotate the display direction of the system by default, you can modify the direction of the corresponding display device in /etc/default/xrandr.

firefly@firefly:~$ cat /etc/default/xrandr

# Rotation can be one of 'normal', 'left', 'right' or 'inverted'.

# xrandr --output HDMI-1 --rotate normal
# xrandr --output LVDS-1 --rotate normal
# xrandr --output EDP-1 --rotate normal
# xrandr --output MIPI-1 --rotate normal
# xrandr --output VGA-1 --rotate normal
# xrandr --output DP-1 --rotate normal

For platforms with a touch screen, if you need to rotate the direction of the touch screen, you can modify the SwapAxes / InvertX / InvertY values in /etc/X11/xorg.conf.d/05-gslX680.conf.

firefly@firefly:~$ cat /etc/X11/xorg.conf.d/05-gslX680.conf
Section "InputClass"
        Identifier "gslX680"
        MatchIsTouchscreen "on"
        MatchProduct  "gslX680"
        Driver "evdev"
        Option "SwapAxes" "off"
       # Invert the respective axis.
        Option "InvertX" "off"
        Option "InvertY" "off"

Virtual keyboard

In the Ubuntu system released by Firefly, you can execute the onboard at the menu to open a virtual keyboard.


Sound setting

The ROC-3399-PC-PLUS board generally has two or more audio devices. Headphone and HDMI are two common audio devices. Below is an example of audio settings for users to refer to.

Specify audio devices in terminal

Audio files in the system are in /usr/share/sound/alsa, please check your sound card before play them:

root@firefly:~# aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: rockchiprt5640c [rockchip,rt5640-codec], device 0: ff890000.i2s-rt5640-aif1 rt5640-aif1-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: rkhdmidpsound [rk-hdmi-dp-sound], device 0: HDMI-DP multicodec-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0

And then specify a sound card to play an audio file. Among them, card0 stands for headphone and card1 stands for HDMI. We usually run command aplay -Dhw:0,0 Fornt_Center.wav to play an audio file, but in order to avoid playback failure because the audio files in the system are mono, so you can run as follow:

#choose headphone to play
root@firefly:/usr/share/sounds/alsa# aplay -Dplughw:0,0 Front_Center.wav
Playing WAVE 'Front_Center.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono

#choose HDMI to play
root@firefly:/usr/share/sounds/alsa# aplay -Dplughw:1,0 Front_Center.wav
Playing WAVE 'Front_Center.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono

Select audio device in graphic interface

Click on the sound icon, open Sound Setting,then click Configuration, and you can see two audio devices. For example, if HDMI is set to output audio, HDMI’s sound card device is selected as Output, and the other sound card is set as Off. (If HDMI is silent or quiet, try pressing the physical button on the HDMI screen to increase the volume.)


Start-up applications while booting up

In the system, the application program can be set up automatically according to the user’s needs.

Select in turn from the System Menu Bar, Preferences -> Default applications for LXSession, open the setting interface, and you can set the default opening mode of the application in Launching applocations.


In the Autostart column, you can select applications that you need to boot. For example, Bluetooth does not start by default. If it needs to start automatically, it can start Bluetooth automatically by ticking the Blueman Applet.


ADB debug

ADB, called Android Debug Bridge, is a command line debugging tool for Android that can perform various functions such as tracking system logs, uploading and downloading files, and installing applications. Take ROC-3399-PC as an example, in order to use the ADB tool for debugging on the Ubuntu system, we have transplanted the ADB service on the board. However, since it is not the Android system, many ADB commands such as adb logcat, adb install, etc. are not available, and are only used as ordinary debugging assistant tools to perform operations such as shell interaction, uploading and downloading of files, etc. Similarly, network remote ADB debugging is not available.

Download ADB

In the Ubuntu system, run:

sudo apt-get install android-tools-adb

Kernel configuration

In the kernel directory, run:

make ARCH=arm64 firefly_linux_defconfig
make menuconfig

And then select in turn, Device Drivers -> USB Support -> USB Gadget Support.

Select USB functions configurable through configfs in the USB Gadget Driver. Meanwhile, select Function filesystem (FunctionFS).

<*>   USB Gadget Drivers (USB functions configurable through configfs)  --->
        USB functions configurable through configfs
[*]       Function filesystem (FunctionFS)

And then compile the kernel in the kernel directory.

make ARCH=arm64 rk3399-roc-pc-plus.img -j12

After compiling, flash the kernel into the development board. For the flashing process, please refer to the wiki tutorial: Flash partition image

ADB connect

After the ADB is installed, the development board and PC are connected with the Micro USB OTG cable. Then check whether there is a device connection by command adb devices :

firefly@Desktop:~$ adb devices
List of devices attached
0123456789ABCDEF	device

When the device is connected successfully, enter the command line mode by entering the command adb shell :

firefly@Desktop:~$ adb shell

In this state, the current path of the command line can not be seen, and the Tab key completion function is invalid. It needs to enter a user to operate normally.

firefly@Desktop:~$ adb shell
# su firefly
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.


It can also enter the normal command line by entering the command adb shell /bin/bash.

Command adb help can print help information. Note that not all commands are available. Help information just for reference only.

MIPI Camera (OV13850)

The support of dual MIPI cameras with OV13850 sensor has been added to the Linux SDK. It is disabled by default in the kernel device tree. Follow the instructions below to turn it on and run the demo on the RK3399 series boards with the MIPI CSI interface.

DTS setting

Take rk3399-roc-pc-plus.dtsi as an example. The device tree nodes that need to activate are shown below. Two cameras have been set up to support dual MIPI cameras. If you want to use one camera, you only need to activate one of them.

    ov13850: ov13850@36 {
        compatible = "ovti,ov13850";
        status = "disabled";
        reg = <0x36>;
        clocks = <&cru SCLK_CIF_OUT>;
        clock-names = "xvclk";

        /* conflict with csi-ctl-gpios */
        reset-gpios = <&gpio0 8 GPIO_ACTIVE_HIGH>;  /*GPIO0_B0 MIP_RST*/
        pwdn-gpios = <&gpio2 2 GPIO_ACTIVE_HIGH>;   /*GPIO2_A2 DVP_PDN0*/
        pinctrl-names = "rockchip,camera_default", "rockchip,camera_sleep";
        //pinctrl-0 = <&cif_clkout>;
        pinctrl-0 = <&pwdn_cam0 &mipi_rst>;
        pinctrl-1 = <&cam0_default_pins>;
        pinctrl-2 = <&cam0_sleep_pins>;
        rockchip,camera-module-index = <0>;
        rockchip,camera-module-facing = "back";
        rockchip,camera-module-name = "CMK-CT0116";
        rockchip,camera-module-lens-name = "Largan-50013A1";

        avdd-supply = <&vcc_mipi>; /* GPIO1_C6 CIF_PWR  AGND*/
        dovdd-supply = <&vcc_mipi>; /* GPIO1_C6 CIF_PWR  AGND */
        dvdd-supply = <&dvdd_1v2>; /* GPIO1_C7 DVP_PWR DVDD_1V2 */

        port {
            ucam_out0: endpoint {
                remote-endpoint = <&mipi_in_ucam0>;
                data-lanes = <1 2>;

Note: If you are using the core-3399 board , you may need to modify the corresponding GPIO setting.


Firstly confirm that the OV13850 sensor is registered successfully by checking the kernel log:

root@firefly:~# dmesg | grep ov13850
//MIPI camera1
[    1.276762] ov13850 1-0036: GPIO lookup for consumer reset
[    1.276771] ov13850 1-0036: using device tree for GPIO lookup
[    1.276803] of_get_named_gpiod_flags: parsed 'reset-gpios' property of node '/i2c@ff110000/ov13850@36[0]' - status (0)
[    1.276855] ov13850 1-0036: Looking up avdd-supply from device tree
[    1.277034] ov13850 1-0036: Looking up dovdd-supply from device tree
[    1.277170] ov13850 1-0036: Looking up dvdd-supply from device tree
[    1.277535] ov13850 1-0036: GPIO lookup for consumer pwdn
[    1.277544] ov13850 1-0036: using device tree for GPIO lookup
[    1.277575] of_get_named_gpiod_flags: parsed 'pwdn-gpios' property of node '/i2c@ff110000/ov13850@36[0]' - status (0)
[    1.281862] ov13850 1-0036: Detected OV00d850 sensor, REVISION 0xb2
//MIPI camera2
[    1.284442] ov13850 1-0046: GPIO lookup for consumer pwdn
[    1.284461] ov13850 1-0046: using device tree for GPIO lookup
[    1.284523] of_get_named_gpiod_flags: parsed 'pwdn-gpios' property of node '/i2c@ff110000/ov13850@46[0]' - status (0)
[    1.288235] ov13850 1-0046: Detected OV00d850 sensor, REVISION 0xb2

Confirm whether the following device nodes are generated:

root@firefly:~# ls /dev/video
//MIPI camera1
video0  video1  video2  video3
//MIPI camera2
video4  video5  video6  video7

Note: If you have configurated only one camera, you can see one sensor registered here.


The dual cameras test script of /usr/local/bin/dual-camera-rkisp.sh has been integrated into the system, with the following content:


export DISPLAY=:0
export XAUTHORITY=/home/firefly/.Xauthority
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
export LD_LIBRARY_PATH=/usr/lib/gstreamer-1.0


/etc/init.d/S50set_pipeline start

# camera closed to the border of the board
gst-launch-1.0 rkisp device=/dev/video0 io-mode=1 analyzer=1 enable-3a=1 path-iqf=/etc/cam_iq.xml ! video/x-raw,format=NV12,width=${WIDTH},height=${HEIGHT}, framerate=30/1 ! videoconvert ! $SINK &

sleep 2

# the other camera
gst-launch-1.0 rkisp device=/dev/video5 io-mode=1 analyzer=1 enable-3a=1 path-iqf=/etc/cam_iq.xml ! video/x-raw,format=NV12,width=${WIDTH},height=${HEIGHT}, framerate=30/1 ! videoconvert ! $SINK &


Here is the snapshot running the test script:


USB Ethernet

Kernel setting

In the kernel directory, run:

make firefly_linux_defconfig
make menuconfig

And then select in turn, Device Drivers -> USB Support -> USB Gadget Support.

Set option USB Gadget Drivers to compile into a kernel module in the USB Gadget Support, then set option Ethernet Gadget (with CDC Ethernet support) to compile into a kernel module too. Meanwhile, select RNDIS support.

<M>   USB Gadget Drivers
<M>     USB functions configurable through configfs
<M>     Ethernet Gadget (with CDC Ethernet support)
[*]       RNDIS support (NEW)

And then compile the kernel in the kernel directory.

make ARCH=arm64 rk3399-roc-pc-plus.img -j12

After compiling, flash the kernel into the development board. For the flashing process, please refer to the wiki tutorial: Flash partition image. After that, you can copy the following modules generated under the kernel directory to the development board:

* drivers/usb/gadget/function/u_ether.ko
* drivers/usb/gadget/function/usb_f_ecm_subset.ko
* drivers/usb/gadget/function/usb_f_ecm.ko
* drivers/usb/gadget/function/usb_f_rndis.ko
* drivers/usb/gadget/function/usb_f_eem.ko
* drivers/usb/gadget/legacy/g_ether.ko
* drivers/usb/gadget/libcomposite.ko

Loading modules on the development board:

insmod libcomposite.ko
insmod u_ether.ko
insmod usb_f_ecm_subset.ko
insmod usb_f_rndis.ko
insmod usb_f_ecm.ko
insmod usb_f_eem.ko
insmod g_ether.ko

Note: Please load libcomposite.ko and u_ether.ko first.

IP address setting

Connect PC and development board with Type-C data line and execute lsusb command in PC, if you can see the USB Ethernet device, it means that the connection is successful.

firefly@Desktop:~$ lsusb
Bus 002 Device 003: ID 09da:5814 A4Tech Co., Ltd.
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 005: ID 04f2:b2ea Chicony Electronics Co., Ltd Integrated Camera [ThinkPad]
Bus 001 Device 004: ID 0a5c:21e6 Broadcom Corp. BCM20702 Bluetooth 4.0 [ThinkPad]
Bus 001 Device 003: ID 147e:1002 Upek Biometric Touchchip/Touchstrip Fingerprint Sensor
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 003: ID 0525:a4a2 Netchip Technology, Inc. Linux-USB Ethernet/RNDIS Gadget
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

In the output information, ID 0525:a4a2 Netchip Technology, Inc. Linux-USB Ethernet/RNDIS Gadget is the USB Ethernet.

Development board insert wire and can connect external network.

  • Setting Development Board IP

Enter ifconfig -a, and you can get the information.

root@firefly:~# ifconfig -a

# eth0 is the Cable Network Card
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet  netmask  broadcast
        inet6 fe80::1351:ae2f:442e:e436  prefixlen 64  scopeid 0x20<link>
        ether 8a:4f:c3:77:94:ac  txqueuelen 1000  (Ethernet)
        RX packets 9759  bytes 897943 (897.9 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 236  bytes 35172 (35.1 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 42  base 0x8000


# usb0 is the virtual USB Network Card
usb0: flags=4098<BROADCAST,MULTICAST>  mtu 1500
        ether 4a:81:b1:34:d2:ad  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Then customize an appropriate IP for the usb0 network card. Note that the IP of usb0 and eth0 are not in the same network segment.

ifconfig usb0
  • Setting IP in PC

# First, check the IP of USB virtual network card
firefly@Desktop:~$ ifconfig
enp0s20u2i1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet  netmask  broadcast
        inet6 fe80::871c:b87e:1327:7fd4  prefixlen 64  scopeid 0x20<link>
        ether 46:fe:6e:97:ee:a6  txqueuelen 1000  (以太网)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1  bytes 54 (54.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# Setting IP address of USB network card and usb0 IP address of development board in the same network segment.
firefly@Desktop:~$ sudo ifconfig enp0s20u2i1

#The default gateway is set to the IP of development board usb0
firefly@Desktop:~$ sudo route add default gw

After IP setting, PC and development board can ping each other.

PC Internet Access

In the development board, turn on the forwarding function of IPv4.

echo 1 > /proc/sys/net/ipv4/ip_forward

If you want to automatically turn on the forwarding function every time you restart the development board, please modify the net.ipv4.ip_forward value of the /etc/sysctl.conf file to 1. And remember to run sysctl -p to make IPv4 forwarding effective.

Add rules for traffic forwarding.

iptables -t nat -A POSTROUTING -s -o eth0 -j SNAT --to-source

Now, the PC can access the net. If the PC can ping development board usb0 and eth0, but can not access the Internet, the following additions need to be made in the etc/resolv.conf file:


The following points should be noted in the configuration process:

  • Corresponding to each IP address on the device in the above steps, pay attention to the fact that the USB virtual network card IP and wired network IP on the development board are not in the same network segment.

  • The gateway of PC USB network card should be set as IP address of virtual USB network card on development board.

  • The virtual network card IP address, IP forwarding function, traffic forwarding rules and other settings on the development board will be restored after the device is restarted.

External storage device rootfs

In addition to using the root filesystem in the internal eMMC, you can also use the root filesystem of the external storage device, such as SD card. The following is an example of rk3399-roc-pc-plus mounting the root filesystem of SD card.

Create partitions on SD card

Insert SD card on PC and use gdisk tool to separate out a partition that loads the root filesystem.

Using fdisk -l to get the device name of the SD card and enter the corresponding device with gdisk command, and then run:

sudo gdisk /dev/sdb

GPT fdisk (gdisk) version 1.0.3

Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present

Found valid GPT with protective MBR; using GPT.

Command (? for help):


#enter '?' can print the help information
#enter 'p' to display all partitions of SD card

Command (? for help): p
Disk /dev/sdb: 15278080 sectors, 7.3 GiB
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 5801AE61-92ED-42FB-A144-E522E8E15827
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 15278046
Partitions will be aligned on 2048-sector boundaries
Total free space is 15278013 sectors (7.3 GiB)

Number  Start (sector)    End (sector)  Size       Code  Name


#now create a new partition, create the appropriate partition size to the root filesystem according to your actual situation.

Command (? for help): n     #entern 'n' to create a new partition
Partition number (1-128, default 1): 1      #create No.1 partition
First sector (34-15278046, default = 2048) or {+-}size{KMGTP}:   #press enter, use the default value.
Last sector (2048-15278046, default = 15278046) or {+-}size{KMGTP}: +3G     #partition size is 3G
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300):    #press enter, use the default value.
Changed type of partition to 'Linux filesystem'


#then enter 'i' to get the partition's unique GUID, and mark down it.

Command (? for help): i
Using 1
Partition GUID code: 0FC63DAF-8483-4772-8E79-3D69D8477DE4 (Linux filesystem)
Partition unique GUID: 6278AF5D-BC6B-4213-BBF6-47D333B5CB53
First sector: 2048 (at 1024.0 KiB)
Last sector: 6293503 (at 3.0 GiB)
Partition size: 6291456 sectors (3.0 GiB)
Attribute flags: 0000000000000000
Partition name: 'Linux filesystem'


#enter 'wq' save and exit gdisk

Command (? for help): wq

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/sdb.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot or after you
run partprobe(8) or kpartx(8)
The operation has completed successfully.

Format the newly created partition.

sudo mkfs.ext4 /dev/sdb1

When the formatting is completed, use the dd command to flash the root filesystem into the newly created partition of the SD card. The production of root filesystem can be referred to Compile Ubuntu rootfs.

#Fill in the root filesystem path and device name according to your situation.
dd if=/rootfs_path/rootfs.img of=/dev/sdb1

Mount root filesystem of SD card

Firstly, we need to modify the device tree file, open the corresponding DTS file, and rewrite the root value of the bootargs parameter under the chosen node as the unique GUID recorded before.

#Change the root value to the first 12 digits of the unique GUID.
chosen {
    bootargs = "earlycon=uart8250,mmio32,0xff1a0000 swiotlb=1 console=ttyFIQ0 rw root=PARTUUID=6278AF5D-BC6B  rootfstype=ext4 rootwait";

And then compile the kernel and flash it into the development board. After the root filesystem is flashed to SD card, insert SD card into the TF slot of the development board, and the root filesystem can be accessed on boot.


  • Before operations, please backing up important files in USB disk or SD card to avoid data lost.

  • When operating, please confirm the device name corresponding to your SD card.

  • The value of the root parameter in the DTS file is unique GUID. Just write down the first 12 bits. You can also modify the value according to the help information of gdisk.

Network startup

Network startup is to use TFTP to download the kernel and DTB file from the server to the memory of the target machine and mount the network root filesystem to the target machine through NFS server to achieve diskless startup. Following is an example based on rk3399-roc-pc-plus for user reference.


  • rk3399-roc-pc-plus development board.

  • Router and network cable.

  • A server with TFTP and NFS.

  • A root filesystem.

Server deployment

  1. Deploy TFTP on the server

Install TFTP server.

sudo apt-get install tftpd-hpa

Create /tftpboot directory and grant permission:

mkdir /tftpboot
sudo chmod 777 /tftpboot

And then modify TFTP server configuration file /etc/default/tftpd-hpa :

# /etc/default/tftpd-hpa

TDTP_OPTIONS="-c -s -l"

Then restart TFTP server.

sudo service tftpd-hpa restart

Create a new file in /tftpboot to test TFTP:

firefly@Desktop:~$ cd /tftpboot/
firefly@Desktop:/tftpboot$ touch test
firefly@Desktop:/tftpboot$ cd /tmp
firefly@Desktop:/tmp$ tftp
tftp> get test
tftp> q

And if you can see the test file in tmp, it means TFTP server is working.

  1. Deloy NFS on the server

Install NFS server:

sudo apt-get install nfs-kernel-server

Create a shared directory:

sudo mkdir /nfs
sudo chmod 777 /nfs
cd /nfs
sudo mkdir rootfs
sudo chmod 777 rootfs

And then copy the root filesystem into the /nfs/rootfs. The production of root filesystem can be referred to Compile Ubuntu rootfs.

Add the shared directory path in /etc/exports:

/nfs/rootfs *(rw,sync,no_root_squash,no_subtree_check)

The shared directory is set according to the user’s actual situation, in which * represents all users’ access.

Restart NFS server:

sudo /etc/init.d/nfs-kernel-server restart

Locally mount the shared directory to test whether the NFS server is available:

sudo mount -t nfs /mnt

Consistent content with nfs/rootfs seen in the mnt directory means NFS server is working. And then umount:

sudo umount /mnt

Kernel configuration

If you want to mount the network root filesystem, you need to do the relevant configuration in the kernel and modify the configuration in DTS.

Firstly, setting kernel, run make menuconfig in kernel directory, and select the relevant configuration:

[*] Networking support  --->
         Networking options  --->
                [*]   IP: kernel level autoconfiguration
                [*]     IP: DHCP support
                [*]     IP: BOOTP support
                [*]     IP: RARP support

File systems  --->
        [*] Network File Systems  --->
                [*]   Root file system on NFS

Modify the device tree file rk3399-roc-pc-plus.dts,add chosen and rewrite the root value of the bootargs parameter under the chosen:

chosen {
    bootargs = "earlycon=uart8250,mmio32,0xff1a0000 swiotlb=1 console=ttyFIQ0 root=/dev/nfs rootfstype=ext4 rootwait";

root=/dev/nfs means mount network root filesystem by NFS.

Compile kernel:

make ARCH=arm64 rk3399-roc-pc-plus.img -j12

After compile kernel, copy boot/img, rk3399-roc-pc-plus.dtb to the /tftpboot:

cp boot.img /tftpboot
cp arch/arm64/boot/dts/rockchip/rk3399-roc-pc-plus.dtb /tftpboot

Detailed instructions can be referred to in the kernel directory: kernel/Documentation/filesystems/nfs/nfsroot.txt

U-Boot setting

The target machine plugs in the wire and connects to the server. Then startup and enter U-Boot command line mode and set the following parameters:

=> setenv ipaddr     #target machine IP
=> setenv serverip   #set serverip as the server IP

#Set up to download the kernel and DTB files from TFTP. Modify the corresponding address according to actual target machine.
=> setenv bootcmd tftpboot 0x0027f800 boot.img \; tftpboot 0x08300000 rk3399-roc-pc-plus.dtb \; bootm 0x0027f800 - 0x08300000

#Set up mount network root filesystem, ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>
=> setenv bootargs root=/dev/nfs rw nfsroot=,v3 ip=

#save the setting
=> saveenv
Saving Environment to MMC...
Writing to MMC(0)... done

#start up the target machine
=> boot
ethernet@fe300000 Waiting for PHY auto negotiation to complete. done
Speed: 100, full duplex
Using ethernet@fe300000 device
TFTP from server; our IP address is
Filename 'boot.img'.
Load address: 0x27f800
Loading: #################################################################
         475.6 KiB/s
Bytes transferred = 20072448 (1324800 hex)
Speed: 100, full duplex
Using ethernet@fe300000 device
TFTP from server; our IP address is
Filename 'rk3399-roc-pc-plus.dtb'.
Load address: 0x8300000
Loading: #######
         645.5 KiB/s
Bytes transferred = 97212 (17bbc hex)
## Booting Android Image at 0x0027f800 ...
Kernel load addr 0x00280000 size 19377 KiB
## Flattened Device Tree blob at 08300000
   Booting using the fdt blob at 0x8300000
   XIP Kernel Image ... OK
   Loading Device Tree to 0000000073edc000, end 0000000073ef6bbb ... OK
Adding bank: 0x00200000 - 0x08400000 (size: 0x08200000)
Adding bank: 0x0a200000 - 0x80000000 (size: 0x75e00000)
Total: 912260.463 ms

Starting kernel ...


You can see in the boot kernel log:

[   12.146297] VFS: Mounted root (nfs filesystem) on device 0:16.

This means that the network root filesystem has been mounted.


  • Ensure that TFTP server and NFS server are working.

  • Make sure that the target machine inserts the network cable first and then boot-up, and in the same LAN with the server. If it is directly connected to the target machine and the server, please use the crossover network cable.

  • In the kernel configuration, Root file system on NFS depends on IP: kernel level autoconfiguration, please select IP: kernel level autoconfiguration first, and then you can find Root file system on NFS and select it.

  • When setting up the remote root file system for mounting, nfsroot =,v3, v3 represents the version information of NFS, Please add it to avoid unsuccessful mounting.

Update kernel and U-Boot online

This section describes a simple process for online updates. Package the kernel, U-Boot or other files that need to be updated into DEB, and then import them into the local package repository to download and update automatically on the development board.

Prepare DEB installation package

Kernel and U-Boot update files are ready: boot.img, trust.img, uboot.img.

DEB is a software package format of Debian Linux. The key to packaging is to create a control file in the DEBIAN directory. Now create DEB working directory:

mkdir deb
cd deb
mkdir firefly-firmware
cd firefly-firmware
mkdir DEBIAN
mkdir -p usr/share/{kernel,uboot}

# put the update files to corresponding directory
mv ~/boot.img ~/deb/firefly-firmware/usr/share/kernel
mv ~/uboot.img ~/deb/firefly-firmware/usr/share/uboot
mv ~/trust.img ~/deb/firefly-firmware/usr/share/uboot

The files stored in the DEBIAN directory are the control files for DEB package installation and the corresponding script files.

Create control files control and script files postinst under the DEBIAN directory, the control file is used to record software package name, version number, platform, dependency information, and other data.

Package: firefly-firmware #directly name
Version: 1.0
Architecture: arm64
Maintainer: neg
Installed-Size: 1
Section: test
Priority: optional
Descriptionon: This is a deb test

The postinst file is as follows:

echo "-----------uboot updating------------"
dd if=/usr/share/uboot/uboot.img of=/dev/disk/by-partlabel/uboot

echo "-----------trust updating------------"
dd if=/usr/share/uboot/trust.img of=/dev/disk/by-partlabel/trust

echo "-----------kernel updating------------"
dd if=/usr/share/kernel/boot.img of=/dev/disk/by-partlabel/boot

Note: postinst is a script that runs after unpacking data, and other correspondingly scripts:

  • preinst: A script that runs before unpacking data.

  • prerm: A script that runs before deleting files when uninstalled.

  • postrm: A script that runs after deleting a file.

Only the preinst script is used here.

Here is the directory tree created:

└── firefly-firmware
    ├── DEBIAN
    │   ├── control
    │   └── postinst
    └── usr
        └── share
            ├── kernel
            │   └── boot.img
            └── uboot
                ├── trust.img
                └── uboot.img

Enter the deb directory and generate the DEB package with the dpkg command:

dpkg -b firefly-firmware firefly-firmware_1.0_arm64.deb

When generating DEB packages, they are usually named according to this specification: package_version-reversion_arch.deb.

Create a local repository

Install the packages we need:

sudo apt-get install reprepro gnupg

Then use GnuPG tool to generate a GPG key. After executing the command, follow the prompt to operate.

gpg --gen-key

Run sudo gpg --list-keys, can see the key information:

sudo gpg --list-keys

gpg: WARNING: unsafe ownership on homedir '/home/firefly/.gnupg'
pub   rsa3072 2019-05-31 [SC] [expires: 2021-05-30]
	  uid           [ultimate] firefly <firefly@t-chip.com>
	  sub   rsa3072 2019-05-31 [E] [expires: 2021-05-30]

Next, create the package repository:

cd /var/www
mkdir apt
mkdir -p ./apt/incoming
mkdir -p ./apt/conf
mkdir -p ./apt/key

Export the previously generated keys to the repository folder. Please correspond to the user name and mailbox address you created.

gpg --armor --export firefly firefly@t-chip.com > /var/www/apt/key/deb.gpg.key

Create a distributions file in the conf directory, which reads as follows:

Origin: Neg   #your name
Label: Mian     #package repository name
Suite: stable   #(stable or unstable)
Codename: bionic    #codename
Version: 1.0
Architectures: arm64
Components: main    #components name,such as main,universe.
Description: Deb source test
SignWith: BCB65788541D632C057E696B8CBC526C05417B76 #the key you generated

Establishment of repository tree:

reprepro --ask-passphrase -Vb /var/www/apt export

Add firefly-firmware_1.0_arm64.deb into repository:

reprepro --ask-passphrase -Vb /var/www/apt includedeb bionic ~/deb/firefly-firmware_1.0_arm64.deb

View the file in the repository:

root@Desktop:~# reprepro -b /var/www/apt/ list bionic
bionic|main|arm64: firefly-firmware 1.0

If you want to remove the file in the repository, just run:

reprepro --ask-passphrase -Vb /var/www/apt remove bionic firefly-firmware

And then install nginx server:

sudo apt-get install nginx

Modify nginx configuration file /etc/nginx/sites-available/default:

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/apt;

    access_log /var/log/nginx/repo.access.log;
    error_log   /var/log/nginx/repo.error.log;

    location ~ /(db|conf) {
        deny all;
        return 404;

Restart nginx server.

sudo service nginx restart

Client update

In the client development board, first, add the source of the local package repository and add a new configuration file bionic. list under the directory etc/apt/sources.list.d, which is as follows:

deb bionic main

IP address is the nginx server address, bionic is the repository codename, main is the components name.

Get and add the GPG key from the server:

wget -O - | apt-key add -

Update and install firefly-firmware_1.0_arm64:

root@firefly:/home/firefly# apt-get update
Hit:1 bionic InRelease
Hit:2 http://wiki.t-firefly.com/firefly-rk3399-repo bionic InRelease
Hit:3 http://ports.ubuntu.com/ubuntu-ports bionic InRelease
Hit:4 http://archive.canonical.com/ubuntu bionic InRelease
Hit:5 http://ports.ubuntu.com/ubuntu-ports bionic-updates InRelease
Hit:6 http://ports.ubuntu.com/ubuntu-ports bionic-backports InRelease
Hit:7 http://ports.ubuntu.com/ubuntu-ports bionic-security InRelease
Reading package lists... Done

root@firefly:/home/firefly# apt-get install firefly-firmware
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following package was automatically installed and is no longer required:
Use 'apt autoremove' to remove it.
The following NEW packages will be installed:
0 upgraded, 1 newly installed, 0 to remove and 3 not upgraded.
Need to get 0 B/6982 kB of archives.
After this operation, 1024 B of additional disk space will be used.
Selecting previously unselected package firefly-firmware.
(Reading database ... 117088 files and directories currently installed.)
Preparing to unpack .../firefly-firmware_1.0_arm64.deb ...
Unpacking firefly-firmware (1.0) ...
Setting up firefly-firmware (1.0) ...
-----------uboot updating------------
8192+0 records in
8192+0 records out
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.437281 s, 9.6 MB/s
-----------trust updating------------
8192+0 records in
8192+0 records out
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.565762 s, 7.4 MB/s
-----------kernel updating------------
39752+0 records in
39752+0 records out
20353024 bytes (20 MB, 19 MiB) copied, 0.1702 s, 120 MB/s

You can see that the poinst script in the DEB package is executed during installation. After installation, restart the development board to take effect by loading the new images.

In /usr/share directory, we can see kernel and uboot directory with boot.img, uboot.img, trust.img in it.


  • In making DEB package, the directory at the same level as DEBIAN is regarded as the root directory. That is, the files placed in the same directory as DEBIAN, after the DEB package is installed on the client side, can be found in the root directory of the target system.

  • The files and scripts in DEB package should be adjusted according to your actual situation.

  • Every time the configuration file is modified in the repository, the repository directory tree should be re-imported.

  • In nginx server configuration, the root parameter configures the path of the repository. Please modify it according to the actual situation.

  • When the client adds a new download source file, pay attention to check the correct server IP address, package repository codename and component name. Note that the client needs to connect to the server.

  • Clients need to add GPG keys with the apt-key add command before getting information about the local repository.

Distributed compiling with Docker

distcc is a program that compiles C, C++, Objective-C or Objective-C++ code distributed by several machines on the network. distcc does not require all machines to share file systems, have synchronous clocks, or install the same libraries and header files, as long as the server machine has the appropriate compilation tools. This demo deploys distcc service using Docker to two rk3399-roc-pc-plus development boards (arm64) and one PC (x86_64), which make it possible to utilizing distcc services to accelerate the compilation of Linux kernel on any one of the devices.


  • Tow rk3399-roc-pc-plus development boards.

  • Routers and network cable.

  • PC machine.

Connect both client and server to the same LAN. After the connection, the corresponding IP addresses are:

  • PC :

  • development board A:

  • development board B:

PC Deployment

Install Docker with scripts:

wget -qO- https://get.docker.com/ | sh

In order to enable the current ordinary user can execute the docker command, you need to add the current user to the docker group:

sudo groupadd docker            #add the docker group
sudo gpasswd -a $USER docker    #add current user to the docker group
newgrp docker                   #update docker group

Start the Docker server:

sudo service docker start

Create a file Dockerfile_distcc.x86_64, which reads as follows:

FROM ubuntu:bionic
MAINTAINER Firefly <service@t-firefly.com>

ARG DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
	&& apt-get install -y --no-install-recommends --no-install-suggests\
		gcc-aarch64-linux-gnu distcc\
    && apt-get clean \
    && rm -rf /var/lib/apt/lists

RUN ln -s aarch64-linux-gnu-gcc /usr/bin/gcc &&\
	ln -s aarch64-linux-gnu-gcc /usr/bin/cc


ENTRYPOINT ["/usr/bin/distccd"]
CMD ["--no-detach" , "--log-stderr" , "--log-level", "debug", "--allow" , ""]

Generate a Docker image:

docker build -t distcc_server:x86_64 -f Dockerfile_distcc.x86_64 .

The generated Docker image can be viewed with the command docker images:

docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
distcc_server       x86_64              138c0b7e3801        9 minutes ago       66.1MB

Run a Docker container with the new image, exposing distcc service on TCP port 3632 of the host network:

docker run -d -p 3632:3632 distcc_server:x86_64

List the running containers with the command docker ps:

docker ps
CONTAINER ID    IMAGE                  COMMAND                  CREATED         STATUS          PORTS                    NAMES
fa468d068185    distcc_server:x86_64   "/usr/bin/distccd --…"   9 minutes ago   Up 9 minutes>3632/tcp   epic_chatterjee

Development boards deployment

The rk3399-roc-pc-plus kernel does not support docker by default and needs to be configured. File /kernel/arch/arm64/configs/firefly_linux_defconfig can be modified with reference to Firefly Github .

After modification, compile and update the kernel to the development board. And then install docker on development board:

sudo apt-get update
wget -qO- https://get.docker.com/ | sh

Add the current user to the docker group:

sudo groupadd docker
sudo gpasswd -a $USER docker
newgrp docker

And then start Docker service:

sudo service docker start

Create a file Dockerfile_distcc.arm64, which reads as follows:

FROM ubuntu:bionic
MAINTAINER Firefly <service@t-firefly.com>

ARG DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
	&& apt-get install -y --no-install-recommends --no-install-suggests\
	gcc distcc\
    && apt-get clean \
    && rm -rf /var/lib/apt/lists


ENTRYPOINT ["/usr/bin/distccd"]
CMD ["--no-detach" , "--log-stderr" , "--log-level", "debug", "--allow" , ""]

Generate a Docker image:

docker build -t distcc_server:arm64 -f Dockerfile_distcc.arm64 .

Run a container with the new image, exposing distcc service on TCP port 3632 of the host network:

docker run -d -p 3632:3632 distcc_server:arm64

Export the new image with the docker save command:

docker save -o distcc_server.tar distcc_server:arm64

Copy the generated distcc_server.tar file to another development board and import the Docker image file there:

docker load -i distcc_server.tar

Run a container with the imported image:

docker run -d -p 3632:3632 distcc_server:arm64

Note: If you have a Docker Hub account, you can push the image to the remote Docker repository with docker push, and pull the image simply with docker pull on another development board. Check the Docker document if you are not familiar.

Client compiles the kernel with distcc

Now that all three machines have deployed a distributed compilation environment, you can choose any of them as the client to invoke all the services. Here we choose the development boards as the client to compile the Linux kernel.

Create a file Dockerfile_compile.arm64, which reads as follows:

FROM ubuntu:bionic
MAINTAINER Firefly <service@t-firefly.com>

ARG DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
	&& apt-get install -y --no-install-recommends --no-install-suggests\
	bc make python sed libssl-dev binutils build-essential distcc\
    liblz4-tool gcc \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists

Generate a Docker image:

docker build -t compile:arm64 -f Dockerfile_compile.arm64 .

Prepare the kernel source code before starting the container. Create a /etc/distcc/hosts file, which lists the IP addresses of all machines that provide distcc services. The content of the /etc/distcc/hosts file is as follows:

# As described in the distcc manpage, this file can be used for a global
# list of available distcc hosts.
# The list from this file will only be used, if neither the
# environment variable DISTCC_HOSTS, nor the file $HOME/.distcc/hosts
# contains a valid list of hosts.
# Add a list of hostnames in one line, seperated by spaces, here.

To get more accurate results, first clear the cache on the client development board:

echo 3 > /proc/sys/vm/drop_caches

Start a Docker container with the compile:arm64 image, mount the current kernel directory of the host into the mnt directory of the container, mount the etc/distcc/hosts file of the host into the etc/distcc/hosts of the container, and start the container in interactive mode with the parameter it:

docker run -it --rm -v $(pwd):/mnt -v /etc/distcc/hosts:/etc/distcc/hosts compile:arm64 /bin/bash
root@f4415264351b:/# ls
bin  boot  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@f4415264351b:/# cd mnt/

Enter the mnt directory in the container, and then start compiling the kernel with distcc. Add the time command to view the time-consuming execution of the compilation command. The CC parameter specifies compiling with distcc.

time make ARCH=arm64 rk3399-roc-pc-plus.img -j32 CC="distcc"

If you use a PC as a client, you need to compile with the following commands:

time make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rk3399-roc-pc-plus.img -j32 CC="distcc aarch64-linux-gnu-gcc"

During compilation, you can use the command distccmon-text 1 to view the compilation in a new window in the container:

distccmon-text 1
15713  Compile     perf_regs.c                    [0]
15327  Compile     fork.c                         [2]
15119  Compile     dma-mapping.c                  [3]
15552  Compile     signal32.c                     [0]
15644  Compile     open.c                         [2]
15112  Compile     traps.c                        [3]
15670  Compile     arm64ksyms.c                   [0]
15629  Compile     mempool.c                      [2]
15606  Compile     filemap.c                      [3]
15771  Preprocess                                                localhost[0]
15573  Preprocess                                                localhost[1]
15485  Preprocess                                                localhost[2]

When the final compilation command is completed, you can see the time taken for compilation:

real    15m44.809s
user    16m0.029s
sys     6m22.317s

Below is the time spent compiling the kernel using a single development board:

real    23m33.002s
user    113m2.615s
sys     9m29.348s

Comparisons show that distributed compilation using distcc can effectively improve the compilation speed.


  • Different platforms require different compilers. For example, on the x86_64 platform, the gcc-aarch64-linux-gnu cross-compiler tool needs to be installed. On the arm64 platform, only the gcc compiler tool needs to be installed. Users need to install the right tools in the Dockerfile to match the actual situation.

  • If the PC is used as the client, the parameter CC= "distcc aarch64-linux-gnu-gcc" should be used to specify compilation with aarch64-linux-gnu-gcc cross-compiler tool when compiling the kernel in the container.