SWI 0x0B — CpuSet¶
- Entry:
0x00000B4C(THUMB; jump-table value0x00000B4D) - Status: verified (hardware-checked 2026-07-08: cycle counts and return values measured on real GBA via the worker ROM)
Summary¶
Copies or fills a block of memory in 16-bit or 32-bit units. The transfer mode
and length are packed into a single control word in r2. Unlike CpuFastSet,
CpuSet works one unit at a time (simple LDRH/STRH or LDR/STR loop), so it
can move any number of units and is used for small or odd-length transfers.
Parameters¶
| Reg | In | Meaning |
|---|---|---|
| r0 | u32 | Source address. For fill mode, points to the single fill value. |
| r1 | u32 | Destination address. |
| r2 | u32 | Control word (see bit layout below). |
Control word (r2) bit layout¶
| Bits | Meaning |
|---|---|
| 0–20 | Unit count (number of 16-bit or 32-bit units to write). Max 0x1FFFFF. |
| 24 | 0 = copy (source advances), 1 = fill (source address fixed). |
| 26 | 0 = 16-bit units, 1 = 32-bit units. |
Bits 21–23, 25 and 27–31 are ignored (masked off when the byte length is computed).
Returns¶
None. Register outputs are not defined; memory at r1 is modified.
Clobbered registers¶
r3, r4, r5 are used internally (r4/r5 restored via the pushed frame). r0 and r1 are advanced in the 32-bit path. Caller-saved r3 is destroyed.
Hardware audit (2026-07-08, canary r4–r12 + CPSR snapshot): caller-visible clobbers: 16-bit copy returned r0/r1/r2 all unchanged; 32-bit fill advanced r0 by 4 and r1 past the written region; r3 = 0x170 leftover in both. r2, r4–r12, r13, and CPSR (flags and mode) came back bit-identical on every tested path. r11/r12 are explained by the SWI dispatcher (it pushes {r11, r12, lr} — see 10_irq_boot_and_iwram.md) and CPSR by the SPSR restore on return; r2 and r4–r10 must be preserved or restored by the routine itself — where a static note above claims r2 is destroyed, the hardware disagrees at the caller level. Raw data: results/clobber_audit_pass2.csv.
Algorithm¶
- Extract the count: mask bits 0–20 and multiply by 4, giving the 32-bit byte
length. (Implemented as
(r2 << 11) >> 9.) - Call the shared guard routine (see below). If the count is zero the whole transfer is skipped.
- Decode bit 26 (unit size):
- 32-bit units: compute end pointer
r1 + bytelen. If bit 24 is set (fill) load one word from the source and repeatedlySTMIA r1!it untilr1reaches the end. OtherwiseLDMIA r0!/STMIA r1!one word per iteration. - 16-bit units: halve the byte length (so it equals count×2). Iterate an
index in steps of 2. Fill mode loads one halfword once and stores it at
each index; copy mode does
LDRH [r0+i]/STRH [r1+i].
Shared guard routine (0x00000BA4)¶
Common to CpuSet, CpuFastSet and BitUnPack. With ip = byte length and r0 =
source:
- If length == 0, return with Z set → caller aborts (nothing is transferred).
- Otherwise compute end = source + length and test the source region:
tst source, #0x0E000000 then tstne end, #0x0E000000. The transfer only
proceeds when both the source start and source end have at least one of
address bits 25–27 set, i.e. lie at or above 0x02000000. A source inside the
0x00000000–0x01FFFFFF range (BIOS / unused) yields Z set and the transfer is
aborted. This acts as a guard preventing the copy routines from reading out of
the BIOS ROM.
Edge cases & known bugs¶
- Count 0: transfer skipped entirely.
- Source in BIOS region (< 0x02000000): transfer silently skipped by the guard (see above).
- Alignment: 16-bit mode needs halfword-aligned addresses, 32-bit mode needs word-aligned addresses. No explicit check — the ARM7TDMI forces alignment on misaligned accesses, so misuse produces rotated/wrong data rather than a fault.
- Count is measured in units, not bytes.
Cycle count¶
Hardware-measured net CPU cycles (worker-ROM harness, TM0/TM1 cascade at F/1,
13-cycle baseline subtracted; identical across 3 runs — see
02_hardware_verification_checklist.md § Measurement setup).
| Operation | Buffers | Cycles |
|---|---|---|
| 16-bit copy, n=4 | EWRAM→EWRAM | 156 |
| 16-bit copy, n=64 | EWRAM→EWRAM | 1056 |
| 16-bit copy, n=256 | EWRAM→EWRAM | 3936 |
| 32-bit copy, n=64 | EWRAM→EWRAM | 1374 |
| 32-bit copy, n=256 | EWRAM→EWRAM | 5214 |
| 16-bit fill, n=64 | EWRAM | 739 |
| 32-bit fill, n=64 | EWRAM | 868 |
| 16-bit copy, n=64 | IWRAM→IWRAM | 800 |
| 32-bit copy, n=64 | IWRAM→IWRAM | 734 |
| BIOS-source guard trip (src=0x100, n=4) | — | 84 |
EWRAM slopes ≈15 cycles/halfword (16-bit copy) and ≈20 cycles/word (32-bit copy) with ≈96 cycles fixed overhead. The BIOS-source guard returns in 84 cycles without writing anything (hardware-confirmed).
Open questions (need hardware verification)¶
- Exact intent/semantics of the
0x0E000000source-region guard (confirmed to abort BIOS-region sources in static analysis; verify on hardware). - Behavior when destination overlaps source in copy mode.
GBATEK cross-reference¶
Matches GBATEK's CpuSet: bit 0–20 count, bit 24 fixed-source (fill), bit 26
datasize. Adds the observation that a 0x00000000–0x01FFFFFF source is rejected
by the shared guard routine.