Kernel flashing in detail

Now that the newly-released Biffboot 2.1 supports both ethernet flashing (with bb_eth_upload.py) and serial flashing (with bb_upload.py), I've spent a couple of days investigating how they work, with an eye towards merging them into one script. The scripts can be found in http://bifferboard.svn.sourceforge.net/viewvc/bifferboard/utils/

Biff's been kind enough to answer loads of my (Lurch) questions via email as well, so this isn't entirely guesswork :-) and I've learnt a lot in the process!

In the explanations below "biffboot2.1" and "the bootloader" are the same thing.

Although the text below is mostly correct, it should be noted that where it mentions "physical address" not the actual physical address in the processor's address space is meant. The actual memory layout for an (8 Mb. flash) bifferboard is as follows:

┌ RAM

│ 0000 0000 start of RAM

│ 0040 0000 default loadaddress of kernel image (can be changed by biffboot)

│ 005F FFFF end of copy of kernel bzImage from flash if kernelmax = 0x20 (2 Mb.)

│ 00C0 0000 end of copy of kernel bzImage from flash if kernelmax = 0x7F (8 Mb.)

│ 0100 0000 (expanded) copy of biffboot (wiped out after biffboot exits)

│ 01FF FFFF end of RAM (always 32 Mb.)

│ 0200 0000

│ . not used

│ FF7F FFFF

┌ FLASH

│ FF80 0000 start of flash (if 8 Mb.) - start of the last 16 kb. of kernel bzImage (see note)

│ FF80 3FFF end of the last 16 Kb. of kernel bzImage

│ FF80 4000 config block

│ FF80 5FF0 MD5 checksum of config block

│ FF80 6000 start of kernel bzImage

│ FF9F FFFF end of kernel bzImage if kernelmax = 0x20 (2 Mb. - 8 kb.)

│ FFA0 0000 start of ubifs flash disk

│ FFFE FFFF end of ubifs flash disk if kernelmax = 0x20 (6079 Kb. - 6.224.896 bytes)

│ end of kernel bzImage if kernelmax = 0x7F (7E9FFF or 8.298.495 bytes max)

│ FFFF F000 start of biffboot (always 64 Kb.)

│ FFFF FF00 entry point for processor

Note: this extra 16 Kb. is only used when the kernel is larger than space permits.

(For clarity there is a space in the memory addresses, this is not denoting segment:offset; these are protected mode physical addresses).

Loading the kernel

To understand how the flashing works, you first need to understand how the bifferboard's flash memory is arranged, and how the bootloader remaps it. The bifferboard has a 1MB flash chip (ENLV800B - 8Mbit) arranged into a 16K sector, 2 * 8K sectors, a 32K sector, and 15 * 64K sectors. The picture below shows how they're arranged with biffboot 2.1.

Update Note: For 4MB bifferboards the flash chip (ENLV320B - 32Mbit) is arranged into 8 * 8K sectors and 63 * 64K sectors. For 8MB bifferboards the flash chip (ENLV640B - 64Mbit) is arranged into 8 * 8K sectors and 127 * 64K sectors. For all bifferboards the (optional) config block is always in the same place with the same size (8K starting at 0x4000), the kernel always starts in the same place (0x6000), and the bootloader (biffboot) always occupies the last 64K sector.

The numbers in grey indicate the end-address of a block, whereas the numbers in black are the start-address.

When changing the bootloader options, it saves them into the config block (yellow area above).

The 'physical layout' is mapped into the 'logical layout' by the bootloader. When biffboot starts up (and assuming you haven't changed the boot type to MMC), it copies the kernel memory (blue and yellow areas above) into main RAM in a 'special way'. It starts at logical block 0 (physical address 0x00006000) and copies it to RAM, continuing all the way to the end of logical block 116 (physical address 0x000EFFFF) and then 'wraps round' to logical block 117 (physical address 0x00000000), and continues to copy to RAM, all the way to the end of logical block 119 (physical address 0x00005FFF). So the bootloader has now 'unwrapped' the kernel, and copied it into memory as a continuous section of 120 * 8k blocks which, after taking off the config block, givies a maximum kernel size of 974848 bytes (bb_upload.py and bb_eth_upload.py can be modified if you need to reclaim this extra 8KB as kernel space). Biffboot then starts executing the kernel from the start of this 'unwrapped' RAM.

It's likely that your kernel is less than 952KB, but handily the kernel simply ignores any data 'after the end'. So even if your kernel (or kernel image with embedded rootfs) is 952KB, it still ignores the 8KB config block.

Update Note: For the 4MB & 8MB bifferboards, this logical-block mapping (4MB: 0 -> 503, 8MB: 0 -> 1015) and flash 'unwrapping' works in a very similar manner. However if you want to use a kernel larger than 2MB, the config block is non-optional.

If the config-block is missing (or if its MD5 hash is incorrect), biffboot will load the 'first' 2MB (the first 256 logical blocks), and start executing that as the kernel (as explained above, the kernel will reclaim any unused memory, so it doesn't mater if your kernel is less than 2MB).

If the config-block is present, biffboot reads the (new) kernel-size setting, and then loads that much from flash into memory, before executing it as a kernel. Which means that for an 8MB bifferboard, the maximum kernel size is 8MB (flash size) - 64K (bootloader) - 8K (config-block) = 8314880 bytes. (that sounds pretty big, but don't forget that may include an embeddeed rootfs)

Erasing the Flash

Before 'new data' can be written to the flash memory, the 'old data' has to be wiped. Flash memory can only be wiped one (physical) sector at a time.

Biffboot doesn't allow itself (sector 0x000F0000) to be erased!

bb_upload.py (serial flashing) sends a single "erase" command to the bootloader, this then erases sector 0x00000000, skips sector 0x00004000, and erases sectors 0x00006000, 0x00008000, 0x00010000, .... 0x000E0000

bb_eth_upload.py (ethernet flashing) sends multiple "erase sector" commands, and instructs the bootloader to erase sections 0x00000000, 0x00006000, 0x00008000, 0x00010000, .... 0x000E0000

(it skips 0x00004000)

Update Note: When the 4MB & 8MB bifferboards (and 1MB bifferboards with 'recent' versions of biffboot) are being flashed by ethernet, these erase sector commands are simply ignored, and a sector is erased just before trying to write data to it for the first time. All the 8k chunks (explained below) within a sector need to be written sequentially.

Flashing the kernel

After erasing the flash, bb_upload.py and bb_eth_upload.py work in more or less the same way:

    1. They check the kernel to be flashed is less than 974848 bytes

    2. They split up the kernel into 8k chunks (padding the last chunk up to a full 8k if necessary)

    3. Then for each 8k chunk:

      1. They send the chunk to biffboot (bb_upload.py transmits 256byte sub-packets, bb_eth_upload.py trasnmits 512byte sub-packets)

      2. They verify the (full 8k of) chunk data with md5 (bb_upload.py sends the md5 of the transmitted data to the bifferboard, bb_eth_upload.py requests the md5 of the recieved data from the bifferboard)

      3. They tell biffboot which logical block the (verified) data should be written to. It's the responsibility of biffboot to remap these logical blocks back into physical addresses.

    4. After all the 8k chunks have been succesfully transmitted (which may be many less than the maximum of 119) and written to flash by biffboot - Finished!