[BPI-R2] How to build an Ubuntu/Debian SD image from scratch

I tried to build an Ubuntu/Debian image from scratch (i.e. not using any of the pre-compiled images). It’s working now, and maybe someone finds it useful. This is what i did, step by step:

I’m using the kernel 4.14, from the official github repository. You have to use a PC running Ubuntu 16.04 (64 Bit). Ubuntu 18.04 will not work correctly because of the gcc version!

To download and compile the kernel:

1. Install tools required tools:

apt-get install git build-essential automake autoconf gcc-arm-linux-gnueabihf u-boot-tools libc6-armhf-cross bc libc6-dev libncurses5-dev libssl-dev bison flex pv

2. If your gcc is newer than version 5 (check with arm-linux-gnueabihf-gcc), install gcc 5:

apt-get install gcc-5-arm-linux-gnueabihf

update-alternatives --install /usr/bin/arm-linux-gnueabihf-gcc arm-linux-gnueabihf-gcc /usr/bin/arm-linux-gnueabihf-gcc-7  50

update-alternatives --install /usr/bin/arm-linux-gnueabihf-gcc arm-linux-gnueabihf-gcc /usr/bin/arm-linux-gnueabihf-gcc-5  100

update-alternatives --config arm-linux-gnueabihf-gcc

3. Clone BSP for kernel 4.14 into a subdirectory bsp14:

git clone https://github.com/BPI-SINOVOIP/BPI-R2-bsp-4.14.git bsp14

4. Build kernel 4.14 and bootloader (select “1”):

cd bsp14


cd ..

Now create the image:

1. Install required tools:

apt-get install qemu-user-static debootstrap binfmt-support parted zip

2. Create image file (8GB):

dd if=/dev/zero bs=1M count=7296 | pv | dd of=bpir2.img

3. Load image as virtual drive:

losetup /dev/loop8 bpir2.img

4. Make partitions and format:

parted -s /dev/loop8 mklabel msdos

parted -s /dev/loop8 unit MiB mkpart primary fat32 -- 100MiB 356MiB

parted -s /dev/loop8 unit MiB mkpart primary ext2 -- 356MiB 7295MiB

partprobe /dev/loop8

mkfs.vfat /dev/loop8p1 -I -n BPI-BOOT

mkfs.ext4 -O ^has_journal -E stride=2,stripe-width=1024 -b 4096 /dev/loop8p2 -L BPI-ROOT


parted -s /dev/loop8 print

The last step should show 2 partitions (one FAT and one EXT). Hint: the parted software prints the start offsets and lengths in MB (1000-based) and not in MiB (1024 based), so the start offset 105MB is correct.

Now fill the linux root filesystem:

1. Mount filesystems:

mkdir /mnt/rootfs

mount /dev/loop8p2 /mnt/rootfs

mkdir /mnt/rootfs/boot

mount /dev/loop8p1 /mnt/rootfs/boot

2. Bootstrap ubuntu rootfs (part 1):

debootstrap --arch=armhf --foreign xenial /mnt/rootfs

cp /usr/bin/qemu-arm-static /mnt/rootfs/usr/bin/

3. Continue bootstrapping (part 2) in the chroot:

chroot /mnt/rootfs

export LANG=C

/debootstrap/debootstrap --second-stage

4. Set apt packages 16.04 and update/upgrade (still in chroot):

echo "" > /etc/apt/sources.list

echo "deb http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted">>/etc/apt/sources.list

echo "deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted">>/etc/apt/sources.list

echo "deb http://ports.ubuntu.com/ubuntu-ports/ xenial universe">>/etc/apt/sources.list

echo "deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial universe">>/etc/apt/sources.list

echo "deb http://ports.ubuntu.com/ubuntu-ports/ xenial multiverse">>/etc/apt/sources.list

echo "deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial multiverse">>/etc/apt/sources.list

echo "deb http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted">>/etc/apt/sources.list

echo "deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted">>/etc/apt/sources.list

echo "deb http://ports.ubuntu.com/ubuntu-ports/ xenial-updates universe">>/etc/apt/sources.list

echo "deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-updates universe">>/etc/apt/sources.list

echo "deb http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted">>/etc/apt/sources.list

echo "deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted">>/etc/apt/sources.list

echo "deb http://ports.ubuntu.com/ubuntu-ports/ xenial-security multiverse">>/etc/apt/sources.list

echo "deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-security multiverse">>/etc/apt/sources.list

apt-get update

apt-get upgrade

apt-get dist-upgrade

5. Install packages (still in chroot), add more packages as you need:

apt-get install sudo nano openssh-server locales ntpdate htop pv

6. Set hostname (still in chroot):

echo "bpi-r2" >/etc/hostname

nano /etc/hosts

-> add "bpi-r2" to line with

7. Set root password (still in chroot), if you want to enable root login:


8. Create one or more users (still in chroot):

useradd -m -G users,sudo,ssh -s /bin/bash bpi

passwd bpi

9. Configure locales (still in chroot), add the locales that you need:

locale-gen en_US

locale-gen en_US.UTF-8

dpkg-reconfigure locales

dpkg-reconfigure tzdata

update-locale LANG=en_US.UTF-8

10. Configure fstab (still in chroot):

nano /etc/fstab

proc /proc proc defaults 0 0

LABEL=BPI-BOOT /boot vfat errors=remount-ro 0 1

LABEL=BPI-ROOT / ext4 defaults,noatime 0 0

11. Configure network 16.04 (still in chroot), update for your requirements: nano /etc/network/interfaces


auto lo

iface lo inet loopback

auto eth0

iface eth0 inet manual

auto wan

iface wan inet dhcp

auto lan0

iface lan0 inet static




auto lan1

iface lan1 inet static




auto lan3

iface lan3 inet static




auto lan4

iface lan4 inet static




12. Configure sshd (still in chroot), i prefer using Pubkey-auth only and disable password auth:

mkdir /root/.ssh

chmod 0700 /root/.ssh

touch /root/.ssh/authorized_keys

chmod 0600 /root/.ssh/authorized_keys

nano /etc/ssh/sshd_config


PubkeyAuthentication yes

PasswordAuthentication no

Your public key must be appended to /root/.ssh/authorized_keys.

20. Exit and remove qemu:


rm /mnt/rootfs/usr/bin/qemu-arm-static

Now write the image file:

1. Copy boot partition files:

tar -x -f bsp14/SD/BPI-BOOT-bpi-r2.tgz --keep-directory-symlink -C /mnt/rootfs/boot

2. Copy kernel files to root file system:

tar -x -f bsp14/SD/4.14.34-BPI-R2-Kernel.tgz --keep-directory-symlink -C /mnt/rootfs

tar -x -f bsp14/SD/4.14.34-BPI-R2-Kernel-net.tgz --keep-directory-symlink -C /mnt/rootfs

3. Copy bootloader files to root file system:

tar -x -f bsp14/SD/BOOTLOADER-bpi-r2.tgz --keep-directory-symlink -C /mnt/rootfs

4. Blacklist power button module:

echo "blacklist mtk_pmic_keys" > /mnt/rootfs/etc/modules-load.d/mtk_pmic_keys.conf

5. Unmount:

umount /mnt/rootfs/boot

umount /mnt/rootfs

6. Write header blocks (get these files from the BPI-files repository on github):

gunzip -c BPI-R2-HEAD440-0k.img.gz | dd of=/dev/loop8 bs=1024 seek=0

gunzip -c BPI-R2-HEAD1-512b.img.gz | dd of=/dev/loop8 bs=512 seek=1

7. Write preloader and u-boot bootloader:

dd if=bsp14/mt-pack/mtk/bpi-r2/bin/preloader_iotg7623Np1_sd_1600M.bin of=/dev/loop8 bs=1024 seek=2

dd if=bsp14/u-boot-mt/u-boot.bin of=/dev/loop8 bs=1024 seek=320


8. Remove loop device:

losetup -d /dev/loop8

9. Zip image (optional):

zip bpir2.img.zip bpir2.img

Now it’s finished. Write the image file to a 8GB SD card (e.g. with the Etcher tool, or simply with dd), put it in your BPI-R2 and power on.

Only the debug UART is used (no HDMI), so connect a USB serial adapter to the 3-pin debug header and start a terminal like minicom.


One important hint: the u-boot bootloader will not work if compiled with gcc 5.5.0 or greater! It only works with gcc 5.4.0

WIth gcc 5.5, the u-boot.bin is smaller and there are some entries in the jump/pointer table at the end missing, so that it cannot be started by the preloader.

This took me about 2 days to figure out … :confused:

1 Like

you know my wiki and my repos? Most information was already there…in uboot-repo there is a hint it cannot be compiled with gcc5

There are also some other problems like ubuntu 18.4 uses netplan by default so config via interfaces-file is not possible till fallback to ifupdown

yes, i know your wiki. There is a lot of helpful information, thanks for that.

The informations for building an image from the scratch are a little bit “scattered”, imho, so i think it could be helpful to have all detailed informations about this topic in one single post.

I also found that the default build scripts in the official repository always create an image with the u-boot and a pre-loader programming the DRAM to 1333MHz, but there is also an unused pre-loader for the 1600MHz DRAM. I tried the 1600 version and it’s working fine, at least with my board (I don’t know if the kernel re-programs the DRAM speed later; in this case, the preloader version doesn’t matter).

One information is not in your wiki afaik: the problem with the u-boot building with gcc 5.5. If building with gcc 5.5, the pre-loader runs, but the board crashes when u-boot should be started. Do you have any clue why gcc5.5 cannot compile the bootloader correcly?

Ubuntu 18.04 is working as well. Did you try to use netplan for interface configuration? I think it should also work similar to the “old” interface version (the yaml config file has another format, of course). I’'l try that next.

I have no clue why this is happen

but i’ve added info here: https://github.com/frank-w/bpi-r2-uboot/blob/master/README.md

(I don’t know if i tried all gcc versions but also spend some time to it)

First mentioned here: How to extend the uboot-menu found no difference in build.log which causes this problem

But please do not spend to much time in uboot…some people working on mainline-support.

For netplan i did not found out how to bring eth0/eth1 up.

Btw. You use official 4.14…imho it is lacking some features and is older.

Thanks for the info. Btw: u-boot is also working with gcc 5.4, but not with gcc 5.5

I don’t spend more time in the bootloader thing. I’m just happy that it’s working now :slight_smile:

It was just important for me to build a system from the scratch so that I can configure/install only the software that I need. For my project, I just need the UART1, GPIO, USB and network interfaces of the board (and several software packages :wink:). This seems to work for me.

I’ll try your updated 4.14 kernel repository now.

thank you for the info, i’ve updated my bug-report on it: https://bugs.linaro.org/show_bug.cgi?id=3917 will update my uboot-repo also when i’m at home

as far i can see you create also the partitions and boot-block from scratch (i used that from existing image)…i will try to do this my own :wink: thanks for that

seems you overwrite it in step 6 (don’t know how bug the images are, but these are in the first 1M):

  1. Write header blocks (get these files from the BPI-files repository on github):

    gunzip -c BPI-R2-HEAD440-0k.img.gz | dd of=/dev/loop8 bs=1024 seek=0

    gunzip -c BPI-R2-HEAD1-512b.img.gz | dd of=/dev/loop8 bs=512 seek=1

btw. your network-config seems wrong (all lan-ports with same subnet but different addresses)…i can’t imagine that this works :hushed: this block also has broken formatting…and i suggest also a clean-up after installing the packages (apt-get clean + dd if=/dev/zero of=rootfs/file.dat;rm file.dat) to mae the image file smaller after packing

I checked these 2 images with a hex editor: the first is the header with the SDMMC_BOOT signature and the pointer to the 2nd header (0x00000200). It’s exactly 440 bytes long and obviously intended to overwrite the code section in the master boot record created with parted, but not the partition table (which is located at the end of the 512 byte MBR. after the 440 bytes).

The second image is the 2nd header with the signature BRLYT and the pointer to the preloader address (0x00000800), and some other values (i don’t know the meaning).

yes, it’s not working that way :wink:

ok, thx, good hint.

1 Like

have separated the basic SD-Setup from the OS-Part and added to my wiki: https://www.fw-web.de/dokuwiki/doku.php?id=en:bpi-r2:storage#sd-card

Perfect, thx.

Btw: I tried the updated 4.14 kernel from your repository with Ubuntu 18.04. Works like a charm :smiley: I also added the bonding driver, that i need for my project (compiling is disabled by default).

If you have a working config please post it. I can also add bonding-driver as module

I just activated the compiling of the bonding driver as module.

To start the bonding, it’s required to load the module, either with modprobe or by adding it to a modules config, e.g. in /etc/modules-load.d/bonding.conf.

Also, the command echo +bond0 > /sys/class/net/bonding_masters must be executed to create the bonding device bond0.

I mean the complete configuration with definition of bonding ports.

Btw. In gcc-bug-report a bisect is requested. Can you try this?


I’m following the steps you in order to create the image, i’m using virtual box with ubuntu 16.04, and gcc --version: gcc (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609, but i’ve got some problems at step2:

I tried this: sudo update-alternatives --install /usr/bin/arm-linux-gnueabihf-gcc arm-linux-gnueabihf-gcc /usr/bin/arm-linux-gnueabihf-gcc-7 50 and i’ve got this message: update-alternatives: error: alternative path /usr/bin/arm-linux-gnueabihf-gcc-7 does not exist

then I tried update-alternatives --install /usr/bin/arm-linux-gnueabihf-gcc arm-linux-gnueabihf-gcc /usr/bin/arm-linux-gnueabihf-gcc-5 100 and that is OK

but when I try the next code: update-alternatives --config arm-linux-gnueabihf-gcc I’ve got the problem:

There is only one alternative in the group of links arm-linux-gnueabihf-gcc (provee /usr/bin/arm-linux-gnueabihf-gcc): /usr/bin/arm-linux-gnueabihf-gcc-5

Nothing to configure.

Could you help me please?

You need to install gcc7 before defining it as alternative.

You need crosscompiler only if you want to build own kernel/uboot. For image creation you do not need this

Thanks for your response Frank,

I’m new with linux, I already installed gcc-7 with this: sudo add-apt-repository ppa:ubuntu-toolchain-r/test then sudo add-apt-repository ppa:ubuntu-toolchain-r/test then sudo apt-get install gcc-7 g+±7 after that I’ve checked the version gcc-7 --version and it shows gcc-7 (Ubuntu 7.4.0-1ubuntu1~16.04~ppa1) 7.4.0 but when I look for it at /usr/bin/ there is not arm-linux-gnueabihf-gcc-7, hence when I repeat the steps before I get the same problem. Could you please give me and idea? what i’m doing wrong?


you installed gcc7 and not the gcc7-crosscompiler for arm(hf)

my ubuntu 18.4 workstation contains the following packages (for armhf)


i wonder where you read about changing the crosscompiler…imho you need not to do this…this was needed till the point where were problems with gcc7, but these should be fixed…so just install the meta-package gcc-arm-linux-gnueabihf and run build.sh

ok, this instructions are not from me and using official 4.14 repo…

for compiling kernel just use my repo:

and follow instructions in Readme there…you can compile with gcc 7 and you have an actual kernel (4.14.103 or maybe 4.19.25)

I am confused with these 4 lines of command R2-Head0k seems like zeros for 1M but what about the head1-512b?

Next the preloader is loaded onthe same boot partition from block 2 and then the Uboot is loaded.

But when I flashed the ubuntu image I didnt see the uboot in its boot partition.

What I am using for the Khadas Vim device is below commands

#partition with boot and root
        parted -s $LDEV mklabel msdos 1> /dev/null 2>&1
        #parted -s $LDEV mkpart primary fat32 0% 100M 1> /dev/null 2>&1
        parted -s $LDEV mkpart primary fat16 4M 128M 1> /dev/null 2>&1
        START=`cat /sys/block/$DEV/${DEV}p1/start`
        SIZE=`cat /sys/block/$DEV/${DEV}p1/size`
        END_SECTOR=$(expr $START + $SIZE)
        parted -s $LDEV mkpart primary ext4 "${END_SECTOR}s" 100% 1> /dev/null 2>&1
        partprobe $LDEV 1> /dev/null 2>&1
        mkfs.vfat "${LDEV}p1" -n BOOT 1> /dev/null 2>&1
        mkfs.ext4 -O ^metadata_csum,^64bit ${LDEV}p2 -L ROOTFS 1> /dev/null 2>&1
    #copy rootfs contents over to the FS
        mkdir -p $TMPDIR/root
        mkdir -p $TMPDIR/boot
        mount ${LDEV}p1 $TMPDIR/boot
        mount ${LDEV}p2 $TMPDIR/root
        cp -ra $ROOTFS_IMG/rootfs_$ARCH/* $TMPDIR/root/
        mv $TMPDIR/root/boot/* $TMPDIR/boot

    #clean up
        umount $TMPDIR/root
        umount $TMPDIR/boot
        losetup -d $LDEV 1> /dev/null 2>&1
        rm -r $TMPDIR/root $TMPDIR/boot
        partprobe $LDEV 1> /dev/null 2>&1

And if any device which needs a uboot with header and first block to be clear then We use the following.

#Clear first 8mb
        dd if=/dev/zero of=${LDEV} bs=1M count=8 1> /dev/null 2>&1
dd if=$TMPDIR/boot/u-boot.bin.sd.bin of=${LDEV} bs=1 count=444 1> /dev/null 2>&1
        dd if=$TMPDIR/boot/u-boot.bin.sd.bin of=${LDEV} bs=512 skip=1 seek=1 1> /dev/null 2>&1

@frank-w You advice will be helpful


1 Like
BPI-R2-HEAD440-0k.img.gz is 65 bytes
BPI-R2-HEAD1-512b.img.gz is 82 bytes


how do you get the information about 1M?

why do you want to clear the first 8M? you can also simply write new uboot over the old like my uboot/build.sh does

at offset 1M starts user-data-area where my uboot saves uboots environment (8k)

see partition-picture https://wiki.fw-web.de/doku.php?id=en/bpi-r2/storage

so you need only to clear first 1M for clearing boot-data and the following 8k to erase environment, remember that at the end of the first 512 byte your partitiontable is saved…so its better to clear beginning on 512byte

1 Like

Ok I will follow your instruction mentioned on your wiki page. So using that I understand that i need to dd 4 lines to the boot partition and those will be the following for my pkgbuild.

dd if=$TMPDIR/boot/BPI-R2-HEAD440-0k.img of=${LDEV} bs=1024 seek=0 

dd if=$TMPDIR/boot/BPI-R2-HEAD1-512b.img of=${LDEV} bs=512 seek=1

dd if=$TMPDIR/boot/BPI-R2-preloader-DDR1600-20190722-2k.img of=${LDEV} bs=1k seek=2

dd if=$TMPDIR/boot/u-boot.bin of=${LDEV} bs=1k seek=320

I hope this will work. I will copy these files in the boot directory of rootfs from where they will be dd’ed to the boot partition when the pkgbuild is run.

Next I will also use the uEnv.txt to be copied to the boot partition from the boot directory when i build the kernel and move the kernel img as uImage to boot partition. Is this second part of uEnv.txt and uImage correct? From what I have observed from the existing ubuntu img.

Third rootfs will be generated from manjaro linux along with the kernel from your branch.