The BL602 is a new WiFi and Bluetooth 5 capable SoC from Bouffalo Lab based on SiFive's E24 core which is a 32-bit RISC-V processor with the IMAFC extension set.

I've challenged myself to write my own RISC-V OS from scratch in Rust - and one of the first things I require is a tool to convert an elf file to the specific firmware image format that is read by the programming tool.

Since I couldn't find a resource that described the format, I went through the source files and wrote this article in the hopes of making it easier to understand.

I've made some tables describing the different structures and their fields, with some comments from the source, and some from myself.

Firmware image

The file starts with a a boot header which contains structures that describe different boot parameters, clock parameters and flash parameters.

Immediately following the boot header is the image data, which also includes an address that the image should be written to.

Boot header (aka Boot_Header_Config)

OffsetSize (bytes)FieldPurpose
0x000x04magicMagic number - can be either 'BFNP' or 'BFAP' for CPU1 or CPU2 firmware, respectively
0x040x04rivision[sic]Boot header revision number?
0x080x5CflashCfgStructure containing necessary information on how to communicate with the external flash
0x640x10clkCfgStructure containing clock information
0x740x04bootCfgBoot config flags
0x780x04imgSegmentInfosegmentCnt or imgLen
0x7c0x04bootEntryEntry point of the image
0x800x04imgStartramAddr or flashOffset1
0x840x20hashSHA-256 hash of the whole image
0xa40x04rsv1
0xa80x04rsv2
0xac0x04crc32

Clock config (aka Boot_Clk_Config and Boot_Sys_Clk_Config)

OffsetSize (bytes)FieldPurpose
0x000x04magicMagic number - always 'PCFG'
0x040x01xtalType
0x050x01pllClk
0x060x01hclkDiv
0x070x01bclkDiv
0x080x01flashClkType
0x090x01flashClkDiv
0x0a0x02rsvd[2]
0x0c0x04crc32CRC32 checksum of the cfg struct

Flash config (aka Boot_Flash_Config and SPI_Flash_Cfg_Type)

OffsetSize (bytes)FieldPurpose
0x000x04magicMagic number - always 'FCFG'
0x040x01ioModeSerail flash interface mode
0x050x01cReadSupportSupport continuous read mode
0x060x01clkDelaySPI clock delay
0x070x01clkInvertSPI clock phase invert
0x080x01resetEnCmdFlash enable reset command
0x090x01resetCmdFlash reset command
0x0a0x01resetCreadCmdFlash reset continuous read command
0x0b0x01resetCreadCmdSizeFlash reset continuous read command size
0x0c0x01jedecIdCmdJEDEC ID command
0x0d0x01jedecIdCmdDmyClkJEDEC ID command dummy clock
0x0e0x01qpiJedecIdCmdQPI JEDEC ID comamnd
0x0f0x01qpiJedecIdCmdDmyClkQPI JEDEC ID command dummy clock
0x100x01sectorSize*1024bytes
0x110x01midManufacturer ID
0x120x02pageSizePage size
0x140x01chipEraseCmdChip erase cmd
0x150x01sectorEraseCmdSector erase command
0x160x01blk32EraseCmdBlock 32K erase command
0x170x01blk64EraseCmdBlock 64K erase command
0x180x01writeEnableCmdNeed before every erase or program
0x190x01pageProgramCmdPage program cmd
0x1a0x01qpageProgramCmdQIO page program cmd
0x1b0x01qppAddrModeQIO page program address mode
0x1c0x01fastReadCmdFast read command
0x1d0x01frDmyClkFast read command dummy clock
0x1e0x01qpiFastReadCmdQPI fast read command
0x1f0x01qpiFrDmyClkQPI fast read command dummy clock
0x200x01fastReadDoCmdFast read dual output command
0x210x01frDoDmyClkFast read dual output command dummy clock
0x220x01fastReadDioCmdFast read dual io comamnd
0x230x01frDioDmyClkFast read dual io command dummy clock
0x240x01fastReadQoCmdFast read quad output comamnd
0x250x01frQoDmyClkFast read quad output comamnd dummy clock
0x260x01fastReadQioCmdFast read quad io comamnd
0x270x01frQioDmyClkFast read quad io comamnd dummy clock
0x280x01qpiFastReadQioCmdQPI fast read quad io comamnd
0x290x01qpiFrQioDmyClkQPI fast read QIO dummy clock
0x2a0x01qpiPageProgramCmdQPI program command
0x2b0x01writeVregEnableCmdEnable write reg
0x2c0x01wrEnableIndexWrite enable register index
0x2d0x01qeIndexQuad mode enable register index
0x2e0x01busyIndexBusy status register index
0x2f0x01wrEnableBitWrite enable bit pos
0x300x01qeBitQuad enable bit pos
0x310x01busyBitBusy status bit pos
0x320x01wrEnableWriteRegLenRegister length of write enable
0x330x01wrEnableReadRegLenRegister length of write enable status
0x340x01qeWriteRegLenRegister length of contain quad enable
0x350x01qeReadRegLenRegister length of contain quad enable status
0x360x01releasePowerDownRelease power down command
0x370x01busyReadRegLenRegister length of contain busy status
0x380x04readRegCmd[4]Read register command buffer
0x3c0x04writeRegCmd[4]Write register command buffer
0x400x01enterQpiEnter qpi command
0x410x01exitQpiExit qpi command
0x420x01cReadModeConfig data for continuous read mode
0x430x01cRExitConfig data for exit continuous read mode
0x440x01burstWrapCmdEnable burst wrap command
0x450x01burstWrapCmdDmyClkEnable burst wrap command dummy clock
0x460x01burstWrapDataModeData and address mode for this command
0x470x01burstWrapDataData to enable burst wrap
0x480x01deBurstWrapCmdDisable burst wrap command
0x490x01deBurstWrapCmdDmyClkDisable burst wrap command dummy clock
0x4a0x01deBurstWrapDataModeData and address mode for this command
0x4b0x01deBurstWrapDataData to disable burst wrap
0x4c0x02timeEsector4K erase time
0x4e0x02timeE32k32K erase time
0x500x02timeE64k64K erase time
0x520x02timePagePgmPage program time
0x540x02timeCeChip erase time in ms
0x560x01pdDelayRelease power down command delay time for wake up
0x570x01qeDataQE set data
0x580x04crc32CRC32 checksum of the cfg struct

Immediately after these headers follows the actual image to write:

Image data

OffsetSize (bytes)FieldPurpose
0x000x04AddressWhere to write the image
0x040x04Image sizeThe size of the following image
0x08<Image size>Image dataThe image data to be written to Address

Sources: