Zynq BootROM Secrets: UART loader
Recently I acquired (md5: ADF639AFE9855EE86C8FAAD216C970D9) the Zynq bootrom, and during the reversing process uncovered some interesting secrets, one of which is an as-of-yet undocumented UART loader. As documented the Zynq bootrom will load from NOR/NAND/SPI flashes, eMMC/SDIO-based storage (unfortunately) not USB, or anything else more complex.
Not sure why Xilinx didn't document this. In my brief testing it is super unreliable if you just spit everything at once - they reset the RX/TX paths during the process, so timing is critical, but that might be the janky meter-long ftdi cable. You can change the baudrate during the process, but I was too lazy to do the math.
Here's the disassembly that made me look twice (that, and checks for the MIO
boot_mode[2:0] that weren't specified in the docs :)):
ROM:0000A220 BL uart_init ROM:0000A224 MOV r_buff, #0x1030 ROM:0000A228 MOV R1, #'X' ; data ROM:0000A22C MOVT r_buff, #0xE000 ; void * ROM:0000A230 MOV R4, #0x488 ROM:0000A234 BL dsb_write ROM:0000A238 MOV r_buff, #FIFO ; void * ROM:0000A240 MOV R1, #'L' ; data ROM:0000A244 BL dsb_write ROM:0000A248 MOV r_buff, #FIFO ; void * ROM:0000A250 MOV R1, #'N' ; data ROM:0000A254 BL dsb_write ROM:0000A258 MOV r_buff, #FIFO ; void * ROM:0000A260 MOV R1, #'X' ; data ROM:0000A264 BL dsb_write`
Sure enough, if we set the correct MIO straps (
boot_mode[2:0] = 0b011, if you'd like to try), the bootloader prompts us! Settings are 115200 8N1, port is MIO49 (RxD) and MIO48 (TxD). My Zybo had these broken out to the usb-uart interface, my Cora did not. The miner board I had sitting around had them broken out to the UART header. The latter was the only one to sufficiently break out the
boot_mode strapping pins, fwiw.
Protocol is simple, all ints are LE encoded and 4 bytes:
BAUD[BAUDGEN value][baud_divider_reg0 value][image length][checksum][image data]
The checksum may be set to 0 to skip - it's just an additive 32 bit sum of the image data, but often enough some random corruption will cause this to fail
I don't understand the whole BAUD bit - since the ROM loops and tries this a couple times, perhaps there was an attempt to autobaud at one point that didn't make it into the rev I have.
The really handy part is the bootrom actually will print error messages over UART too (even when not using this mode :))!
|0x200A||probably a checksum error, the logic is a bit fucky and tries to find another multiboot header, which flows to this error|
|0x2501||didn't handshake within 128 attempts|
|0x2502||Size error (hilariously, there's a signing bug here, and you can pass this check with a size < 0. unfortunately this shortcuts the actual copy loop. no cool 0day here, sorry).|
|0x2503||UART checksum error (set to 0 to bypass)|
Attached is a super simple bit of python you can use to experiment with this! Just give it a Zynq image as the first arg (the same thing you build with bootgen, or place on an sdcard, etc).