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}