Reboot-to-bootloader: enter <RealTek> prompt from Linux¶
Overview¶
A single command from Linux SSH reboots the gateway into the <RealTek>
bootloader prompt, ready for TFTP firmware updates — no need to press
ESC on the serial console.
The boothold binary writes the HOLD magic to DRAM via pwrite() + O_SYNC
(bypasses the CPU write-back cache). The flag is one-shot: the bootloader
clears it before entering download mode, so the next reboot boots Linux normally.
How it works¶
The mechanism uses a magic word in DRAM that survives the
watchdog reset triggered by reboot. No flash writes are involved.
- Linux writes
0x484F4C44("HOLD") to physical address0x003FFFFCvia/dev/mem. - Linux triggers
reboot, which causes a watchdog reset. - The CPU restarts at
BFC00000(flash reset vector). The btcode re-initialises the DDR controller, but DRAM cell contents survive because the DDR2 retention time (~64-256 ms) exceeds the re-init delay (~1-2 ms). - The stage-2 bootloader checks
0x803FFFFC(kseg0 cached alias of physical0x003FFFFC) for the magic word. - If it matches, the bootloader clears it and enters
download mode (
goToDownMode()). - If it doesn't match (normal boot, cold power-on), the bootloader proceeds to load and boot the kernel as usual.
A full power cycle (disconnect all cables) clears DRAM and restores normal boot.
Boot flow with boot-hold¶
setClkInitConsole()
initHeap()
initInterrupt()
initFlash()
showBoardInfo()
← check BOOTHOLD_RAM[0]
if match → clear, goToDownMode(), return
check_image()
doBooting()
DRAM address selection¶
Why 0x003FFFFC with reserved-memory¶
Physical 0x003FFFFC is safe from all btcode early-boot code (DDR
calibration, decompressor, TFTP). However, the kernel's page allocator
normally uses this page — writing through KSEG0 (cached) overwrites any
value written by devmem through KSEG1 (uncached), a classic
KSEG0/KSEG1 cache coherency conflict.
The fix: the device tree declares this page as reserved-memory with
no-map. The kernel never allocates it — no cache lines, no conflict.
devmem writes persist reliably.
Top of DRAM (0x01FFFFFC) is NOT safe: the btcode initialises the
stack pointer at the top of RAM (0x82000000) and pushes data starting
at 0x81FFFFFC during DDR calibration — producing false positives on
every boot, including cold power-on.
Address safety¶
| Region | Address range | Status |
|---|---|---|
| Exception vectors | 0x80000000 - 0x800001FF |
Avoid |
DDR calibration (DDR_cali_API7, Calc_TRxDly) |
0xA0080000, 0xA0100000 |
Avoid |
DDR size detection (Calc_Dram_Size) |
0xA0000000, power-of-2 offsets up to 0xA4000000 |
Avoid |
| Stage-1.5 (piggy) | 0x80100000+ |
Avoid |
| LZMA workspace | 0x80300000 |
Avoid |
| Boot-hold flag | 0x803FFFFC |
Used |
| Stage-2 code/data/BSS | 0x80400000 - 0x80422000 |
Avoid |
| TFTP load area | 0x80500000 - 0x81500000 |
Avoid |
| btcode stack (grows down) | 0x81FFFFFC and below |
Avoid |
Bootloader implementation¶
In boot/main.c, at file scope:
#define BOOTHOLD_MAGIC 0x484F4C44 /* "HOLD" */
#define BOOTHOLD_RAM ((volatile unsigned long *)0x803FFFFC)
In start_kernel(), after showBoardInfo():
if (BOOTHOLD_RAM[0] == BOOTHOLD_MAGIC) {
BOOTHOLD_RAM[0] = 0;
prom_printf("---Boot hold requested\n");
goToDownMode();
return;
}
Kernel device tree reservation¶
In rtl8196e.dts:
memory@0 {
device_type = "memory";
reg = <0x00000000 0x02000000>; /* 32MB */
};
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
boothold@3ff000 {
reg = <0x003FF000 0x1000>; /* 4KB reserved for boothold flag */
no-map;
};
};
The no-map property removes this page from memblock before the page
allocator starts — no runtime cost, no exception handling. On MIPS,
KSEG0/KSEG1 are hardware-mapped (no TLB), so /dev/mem access still
works. The 4 KB cost (0.01% of 32 MB) is negligible.
Linux-side usage¶
With boothold binary (recommended)¶
The boothold binary (installed in /userdata/usr/bin/) uses pwrite() with
O_SYNC on /dev/mem to write the HOLD magic directly to DRAM, bypassing the
CPU write-back cache.
Do not use devmem — it maps RAM through KSEG0 (cached, write-back) via
mmap(). The write may stay in the L1 D-cache and be lost on watchdog reset.
Experimental results¶
Tested on the Lidl Silvercrest gateway (RTL8196E, 32 MB DDR2):
| Test | Result |
|---|---|
| DRAM retention across watchdog reset | Survives — magic at 0x803FFFFC preserved after reset |
Boot-hold from Linux SSH (devmem + reboot) |
Works — bootloader prints ---Boot hold requested and enters <RealTek> prompt |
| Value persistence (kernel running) | Works — page excluded from allocator via reserved-memory no-map |
| One-shot behavior (subsequent reboot) | Works — flag is cleared, Linux boots normally |
| Full power cycle (disconnect all cables) | Flag cleared — DRAM lost, normal boot |
Design alternatives considered¶
Top of DRAM (0x01FFFFFC)¶
Attempted: reduce DT memory size by 4 KB to exclude the last page. Failed: the btcode stack starts at top of RAM and writes data there during DDR calibration on every boot (including cold power-on), producing false HOLD detections.
Flash-based flag¶
Write a 4-byte magic to flash offset 0x1FFF0 (last sector of mtd0).
The bootloader reads it, clears it via sector read-modify-write, and
enters download mode. This is guaranteed to work regardless of DRAM
retention but causes one flash erase+write cycle per use.
Not implemented — DRAM approach works reliably on this hardware and avoids flash wear entirely.