Installing genric Linux distros to boxes with GXBB (S905/S905-H) SoC
Background
As you may or may not know, during the development of their GXBB family of SoCs Amlogic made a big mistake: they defined that the bootloader should be stored at the first sectors on the eMMC/SD card, starting at sector 0. This ends up conflicting with the popular MS-DOS/MBR partition table which should also be stored at sector 0. (And of couse GPT partition table which should be stored at sector 0+1)
What made this even worse is that only the user partition of eMMC (/dev/mmcblk2
) is used, not the boot partitions (/dev/mmcblk2boot0
or /dev/mmcblk2boot1
), so it’s not even possible to store bootloaders there and have a sane partition table stored in user partition.
In their future SoC familied they fixed this by re-defining the bootloader to be stored starting at sector 1, and on either user partition (/dev/mmcblk2
) or boot partitions (/dev/mmcblk2boot
or /dev/mmcblk2boot1
), so both MBR and GPT partition tables are now possible, although the latter is not often used.
They also have some proprietary workarounds for certain partneres, e.g. Hardkernel’s Odroid C2, which moved the bootloader to sector 96, but that won’t work for generic devices
For their own Android implementations and some OSS distros (e.g. CoreELEC, EmuELEC) using their kernel, this could be worked around by using their proprietary EPT partition table stored at 36MiB offset (check ampart if you want a partition tool for that)
But what if we want to install a generic distro (e.g. ArchLinuxARM, Armbian, etc) to the eMMC? Are there any ways we could store some partition infos without breaking the bootloader?
Command line partition
Upstreamed by Huawei in 2013, embeded device command line partition parsing is a great feature in mainline Linux kernel that you could store the partitions info in pre-boot environment (e.g. u-boot env) and pass them to the kernel via command line (if you play around with custom boot argument, you would be very familiar with it).
The kernel just needs CONFIG_CMDLINE_PARTITION
to be set to y
to use it.
Kernel & Distro
If you want a kerel that has the above option enabled, you would need to use the images available in my ArchLinuxARM project which has the option CONFIG_CMDLINE_PARTITION
enabled since last month in its kernel package. Check the releases to get ArchLinuxARM-aarch64-Amlogic-*-pkgs.tar.xz
to extract the kernel package if you want to use it on other existing installation (either another ArchLinuxARM instalaltion or something else like Armbian)
Identify the eMMC layout (optional)
Use my tool ampart to get the existing layout of your eMMC if you have stock Android on there, the tool has static aarch64 releases so you could run it without dependency (If you have mainline u-boot as bootloader, skip these two parts about eMMC layout)
For download instruction, refer to README.md
To get a report of the existing eMMC layout, run the following command to get a URL you can open in browser:
ampart /dev/mmcblk2 --mode webreport
Change the eMMC layout (optional)
For an eMMC layout like this, you would want to re-partition it with ampart’s dclone
mode for maximum utilization:
ampart /dev/mmcblk2 --mode dclone
This should give you a layout like this
For these existing partitions, I highly recommend including them in your blkdevparts definition so you won’t accidentally overwrite them (since no partitions could overlap with each other). The above layout can be carried to kernel with the following argument:
blkdevparts=mmcblk2:4M(bootloader),64M@36M(reserved),8M@116M(env),-@132M(data)
Do not create 0-size partitions, kernel won’t accept 0-size partitions and will consider the whole blkdevparts definition broken! cache here is omitted to avoid breaking the partitions definition
Decide on the partitions
You would need several areas on eMMC to store the following stuffs:
- kernel as a byte stream
- initramfs as a byte stream
- dtb as a byte stream
- rootfs
These can be stored in the following ways:
- Either 4 partitions (kernel, initramfs, dtb, rootfs)
- Or 2 partitions (kernel+initramfs+dtb, rootfs)
- Or 1 partition (rootfs, all others I/O go directly to eMMC)
For the sanity of future updating, I would recommend go with 4 partitions and use udev rules to give them symlinks, and all future writes go to the symlinked block devices instead of raw /dev/mmcblk2pN
devices
For each area, I recommend allocating space 2x the size of the file that needs to be stored there (e.g. if the kernel image needs 25M now, then give it 50M), and round up the size to nearest MiB boundary for easy calculation.
The following command line is an example I’m using as my mibox3’s bootargs
:
Note this box has mainline u-boot as the BL33 inside bootloader so there’s no Android partitions. I’ve released it in my u-boot fork. If you’re using the stock bootloader, you would need to have those non-touchable partitions pre-identified with ampart first
blkdevparts=mmcblk2:880K(bootloader),80K(dtb),64K(env),15M(initramfs),50M(kernel),-(data) zswap.enabled=0 root=/dev/mmcblk2p6 rw
In the above example, mmcblk2 (the user partition of eMMC) would be parsed as 6 partitions:
kernel name | partition name | offset | size |
---|---|---|---|
mmcblk2p1 | bootloader | 0 | 880KiB |
mmcblk2p2 | dtb | 880KiB | 80KiB |
mmcblk2p3 | env | 960KiB | 64KiB |
mmcblk2p4 | initramfs | 1MiB | 15MiB |
mmcblk2p5 | kernel | 16MiB | 50MiB |
mmcblk2p6 | data | 56MiB | auto-fill |
output of lsblk
:
# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
mmcblk2 179:0 0 3.7G 0 disk
├─mmcblk2p1 179:1 0 880K 0 part
├─mmcblk2p2 179:2 0 80K 0 part
├─mmcblk2p3 179:3 0 64K 0 part
├─mmcblk2p4 179:4 0 15M 0 part
├─mmcblk2p5 179:5 0 50M 0 part
└─mmcblk2p6 179:6 0 3.6G 0 part /
Udev rules
Write the following content in /etc/udev/rules.d/emmc-links.rules
and you should be able to access these partitions by /dev/block/[name]
upon next reboot:
SUBSYSTEM=="block", KERNEL=="mmcblk2p*", ENV{DEVTYPE}=="partition", SYMLINK+="block/$env{PARTNAME}"
Testing with external boot
Don’t hurry to write things to eMMC yet, be sure your blkdevparts
definition will work as intended on an external system first. Adding it to /boot/extlinux/extlinux.conf
or /boot/uEnv.txt
(depending on your setup)
Change the APPEND
line from like this:
APPEND root=UUID=49b9c681-20e8-4b66-919e-fa8e65057496 rw
to like this
APPEND root=UUID=49b9c681-20e8-4b66-919e-fa8e65057496 rw blkdevparts=mmcblk2:880K(bootloader),80K(dtb),64K(env),15M(initramfs),50M(kernel),-(data)
And boot to check if /dev/block/bootloader
, /dev/block/kernel
, etc exist.
Writing to eMMC
Write initramfs, kernel and dtb to their corresponding partitions, with simple dd
command (these are from my ArchLinuxARM installation, change them according to your actual setup):
dd if=/boot/initramfs-linux-aarch64-flippy.uimg of=/dev/block/initramfs
dd if=/boot/vmlinuz-linux-aarch64-flippy of=/dev/block/kernel
dd if=/boot/dtbs/linux-aarch64-flippy/amlogic/meson-gxbb-p201.dtb of=/dev/block/dtb
And create the rootfs on /dev/block/data
or /dev/block/rootfs
or whatever name you gave it, and populate it
U-boot env
eMMC booting argument should be updated to prepare the kernel, initramfs and dtb with direct MMC reads. On a device with mainline u-boot, the following u-boot envs are important (each of them is listed in the fw_printenv
output format [varname]=[content]
, to set them, use fw_setenv [varname] [content]
)
These’re envs used with mainline u-boot and distro_bootcmd enabled and ArchLinuxARM inallation on my mibox3, adapt them to your actual layout
# Bootcmd of MMC device 1 (eMMC) should be overwritten to run a custom command
bootcmd_mmc1=run emmc_bootcmd
# The kernel command line that'll be used for eMMC booting, as a single variable, for easier edit
emmc_bootargs=blkdevparts=mmcblk2:880K(bootloader),80K(dtb),64K(env),15M(initramfs),50M(kernel),-(data) zswap.enabled=0 root=/dev/mmcblk2p6 rw
# The offset and block count of each "area/partition", hexdecimal, 1 unit = 512B, so be sure you calculate them right
emmc_dtb_offset=0x6e0
emmc_dtb_count=0xa0
emmc_initramfs_offset=0x800
emmc_initramfs_count=0x7800
emmc_kernel_offset=0x8000
emmc_kernel_count=0x19000
# The custom boot command
emmc_bootcmd=mmc dev 1; mmc read ${fdt_addr_r} ${emmc_dtb_offset} ${emmc_dtb_count}; mmc read ${ramdisk_addr_r} ${emmc_initramfs_offset} ${emmc_initramfs_count}; mmc read ${kernel_addr_r} ${emmc_kernel_offset} ${emmc_kernel_count}; setenv bootargs "${emmc_bootargs}"; booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}