rk27xx: pass the NAND bootloader's handoff words to the OF

Before it jumps to an image, the rk27xx NAND bootloader writes three
words at the address in RKW header field 0x14: a magic, its version
and which copy of the image it loaded. The original firmware never
initialises them: it reports the version over USB and counts its own
reboots in the third word, and once the count reaches 5 it reboots
into a mode the NAND bootloader will not boot. When our bootloader
started the OF, nothing wrote them, so the OF ran with whatever was
left in DRAM.

Field 0x14 of our own images held a constant taken from some other
image. Point it at the last 12 bytes of DRAM, which nothing uses
before our bootloader runs. load_rkw() in the bootloader now reads
the words there before loading an image, and writes them at the
address in the loaded image's header when that lies between the
image and the bootloader. Started over USB, it passes version 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Ife1d63e361a780c28a9f0626d110552b98bb6b7f
This commit is contained in:
Marcin Bukat 2026-10-07 16:03:44 +02:00
parent e61aa0cd37
commit c3a62cee96
3 changed files with 98 additions and 2 deletions

View file

@ -30,7 +30,9 @@ struct rkw_header_t {
uint32_t image_base; /* Base address of the firmware image */
uint32_t load_address; /* Load address */
uint32_t load_limit; /* End of the firmware image */
uint32_t bss_start; /* This is the start of .bss section of the firmware I suppose */
uint32_t handoff; /* Where the loader leaves its three handoff
* words for the image (see rkw-loader.c)
*/
uint32_t reserved0; /* reserved - I've seen only zeros in this field so far */
uint32_t reserved1; /* reserved - I've seen only zeros in this field so far */
uint32_t entry_point; /* Entry point address */

View file

@ -19,6 +19,7 @@
****************************************************************************/
#include <stdio.h>
#include <stdbool.h>
#include "config.h"
#include "loader_strerror.h"
#include "rkw-loader.h"
@ -26,6 +27,82 @@
#include "file.h"
#include "panic.h"
#ifdef BOOTLOADER
/* Before the NAND bootloader jumps to an image it writes three words at
* the address in header field 0x14: a magic, its own version and which
* copy of the image it loaded. The OF never initialises them; it reports
* the version over USB and counts its reboots in the third word. When we
* start the OF ourselves we pass on what the NAND bootloader gave us, so
* the OF sees the same as when it is started directly.
*/
#define RKW_HANDOFF_MAGIC 0x03df479a
/* written over the magic once the words have been read */
#define RKW_HANDOFF_CONSUMED 0x123456ad
/* The last 12 bytes of DRAM. tools/rkw.c puts this address in every
* Rockbox RKW, so the NAND bootloader leaves our words here.
*/
#define RKW_HANDOFF_ADDR (0x60000000 + MEMORYSIZE * 0x100000 - 12)
#if MEMORYSIZE != 16
#error "tools/rkw.c assumes 16 MB of DRAM, update RKW_HANDOFF_ADDR there"
#endif
struct rkw_handoff_t {
uint32_t magic;
uint32_t version;
uint32_t source;
};
/* The NAND bootloader's words, read once: loading an image may overwrite
* them, and a reboot keeps DRAM, so they are marked consumed.
*/
static struct rkw_handoff_t *rkw_own_handoff(void)
{
static struct rkw_handoff_t own;
static bool read_done = false;
volatile struct rkw_handoff_t *h =
(volatile struct rkw_handoff_t *)RKW_HANDOFF_ADDR;
if (!read_done)
{
if (h->magic == RKW_HANDOFF_MAGIC)
{
own.version = h->version;
own.source = h->source;
}
else
{
/* not started by the NAND bootloader, e.g. over USB */
own.version = 0;
own.source = 0;
}
own.magic = RKW_HANDOFF_MAGIC;
h->magic = RKW_HANDOFF_CONSUMED;
read_done = true;
}
return &own;
}
/* Hand the words on to an image loaded at its own address, when its
* header points past the image and below us.
*/
static void rkw_pass_handoff(const struct rkw_header_t *hdr,
const unsigned char *buf, int len,
int buffer_size)
{
uintptr_t addr = hdr->handoff;
uintptr_t lo = (uintptr_t)buf + len;
uintptr_t hi = (uintptr_t)buf + buffer_size;
if ((uintptr_t)buf != hdr->load_address || (addr & 3) ||
addr < lo || addr > hi - sizeof(struct rkw_handoff_t))
return;
*(struct rkw_handoff_t *)addr = *rkw_own_handoff();
}
#endif /* BOOTLOADER */
/* loosely based on load_firmware()
* on success we return size of loaded image
* on error we return negative value which can be deciphered by means
@ -116,6 +193,11 @@ int load_rkw(unsigned char* buf, const char* firmware, int buffer_size)
goto end;
}
#ifdef BOOTLOADER
/* before the image can overwrite them */
rkw_own_handoff();
#endif
/* skip header */
lseek(fd, sizeof(rkw_info), SEEK_SET);
@ -141,6 +223,10 @@ int load_rkw(unsigned char* buf, const char* firmware, int buffer_size)
}
}
#ifdef BOOTLOADER
rkw_pass_handoff(&rkw_info, buf, len, buffer_size);
#endif
ret = len;
end:
close(fd);

View file

@ -26,6 +26,14 @@
#define RKLD_MAGIC 0x4c44524b
#define RKW_HEADER_SIZE 0x2c
/* The NAND bootloader writes three handoff words at the address in
* header field 0x14 before it jumps to the image. Point it at the last
* 12 bytes of DRAM, which nothing touches before our bootloader reads
* them. Every rk27xx target has 16 MB; keep in step with
* RKW_HANDOFF_ADDR in firmware/target/arm/rk27xx/rkw-loader.c.
*/
#define RKW_HANDOFF_ADDR (0x60000000 + 16 * 0x100000 - 12)
/* slightly modified version from crc32.c in rockbox */
static uint32_t rkw_crc32(const void *src, uint32_t len)
{
@ -114,7 +122,7 @@ int rkw_encode(char *iname, char *oname, unsigned long modelnum)
int2le(0x60000000, outbuf+0x08); /* base address */
int2le(0x60000000, outbuf+0x0c); /* load address */
int2le(0x60000000+length, outbuf+0x10); /* end address */
int2le(0x6035a5e4, outbuf+0x14); /* points to some unknown struct */
int2le(RKW_HANDOFF_ADDR, outbuf+0x14); /* loader handoff words */
int2le(modelnum, outbuf+0x18); /* reserved (we abuse the format
* to store modelnum here
*/