4fd337fb24
The launcher committed in b438960 would have hung the console, and reviewing
it against Dennis's suggestion to test with Swiss's boot.dol is what surfaced
it -- Swiss is known-good, so a hang could only have been the launcher, but I
would not have been able to tell that from "the transfer is broken".
Two distinct defects, both from the same oversight -- a DOL's sections load
from 0x80003100 upward, which is exactly where this program is running:
1. The copier was an ordinary C function in .text, so it overwrote its OWN
instructions partway through the copy.
2. The image was staged in a memalign'd buffer, which the destination sections
can overwrite while it is still being read from.
Fixes:
- netboot_stub.S: the copy/zero/cache-flush/jump sequence in position
independent assembly, copied into reserved memory and executed THERE. It
has no globals, no literal pool, no TOC references and no calls, because
any of those would read from the address it was LINKED at rather than the
one it is RUNNING at. It also does the dcbf/icbi pass the C version was
doing through DCFlushRange -- without it the CPU executes stale icache
lines at the entry point.
- netboot_reserve(): carves the top of MEM1 and hands it to
SYS_SetArena1Hi() so neither libogc nor malloc allocates there. Called
FIRST in main(), before the framebuffer.
- netboot_launch(): verifies every text/data section AND bss against the
reserved region and refuses (-3) if any would land on it, instead of
corrupting the staged image or the copier mid-flight. Also hands the new
image a stack inside the reserved tail.
The app now tells you on screen to send Swiss's boot.dol first, and the README
explains why: the launcher and the transfer can each be broken independently
and a hang looks identical, so the first send should be an image whose success
is unmistakable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
53 lines
2.3 KiB
C
53 lines
2.3 KiB
C
#ifndef NETBOOT_H
|
|
#define NETBOOT_H
|
|
|
|
#include <gccore.h>
|
|
#include <stddef.h>
|
|
|
|
/* Self-update over the BBA: receive a .dol via raw ethernet and launch it.
|
|
*
|
|
* Deliberately NOT built on an IP stack. Frames carry a private ethertype
|
|
* and a 12-byte header, so there is no ARP, no IP, no checksums and no
|
|
* libogc network driver competing with us for the EXI channel -- it reuses
|
|
* exactly the two paths the test suite already proves on hardware:
|
|
* bba_tx_bytes() for TX and the SPRAM ring at RRP for RX.
|
|
*
|
|
* The transfer is STOP-AND-WAIT (one block in flight, ACKed before the next).
|
|
* That is not just simplicity: the gateware does not consume RRP, so
|
|
* RXFrameAssembler can overwrite ring pages the GC has not read yet. One
|
|
* block in flight means the ring never has more than one frame outstanding,
|
|
* so the missing overflow protection cannot bite. Do not "optimise" this
|
|
* into a sliding window without adding RBFI-style protection first.
|
|
*/
|
|
|
|
#define NB_ETHERTYPE 0x88B5u /* IEEE 802 local experimental ethertype 1 */
|
|
#define NB_MAGIC 0x47435550u /* "GCUP" */
|
|
|
|
#define NB_OP_DATA 0x01u /* host -> GC: one block */
|
|
#define NB_OP_ACK 0x02u /* GC -> host: block received */
|
|
#define NB_OP_DONE 0x03u /* host -> GC: transfer complete, launch */
|
|
|
|
#define NB_BLOCK_MAX 1024u /* payload bytes per frame */
|
|
|
|
/* Reserve the staging region at the top of MEM1. MUST be called early in
|
|
* main(), before anything else allocates, and the pointer it returns is where
|
|
* netboot_receive() should stage the image. Returns NULL if MEM1 is too
|
|
* small (it never is on real hardware).
|
|
*
|
|
* The image CANNOT live in an ordinary malloc'd buffer: DOL sections load from
|
|
* 0x80003100 upward and would overwrite it partway through the copy. This
|
|
* carves the top of MEM1 and hands it to SYS_SetArena1Hi so libogc will not
|
|
* allocate there either. */
|
|
void *netboot_reserve(u32 *out_max);
|
|
|
|
/* Receive a .dol into `dst`. Returns its length, or <0 on error.
|
|
* `status` (optional) is called with a human-readable progress line. */
|
|
int netboot_receive(void *dst, u32 maxlen,
|
|
void (*status)(const char *msg));
|
|
|
|
/* Launch a .dol image already in memory. Does not return on success.
|
|
* Returns <0 if the image is not a plausible DOL. */
|
|
int netboot_launch(const void *dol);
|
|
|
|
#endif
|