summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/pardes/app.zig46
-rw-r--r--src/soc.zig45
2 files changed, 46 insertions, 45 deletions
diff --git a/src/pardes/app.zig b/src/pardes/app.zig
index 9fbe19e..a83485a 100644
--- a/src/pardes/app.zig
+++ b/src/pardes/app.zig
@@ -163,56 +163,12 @@ fn nowMs() u64 {
return us / 1000;
}
-// ------------------------------------------------------------------------- the cache, flushed
-
-/// Evict every flash-backed cache line, by reading more flash than the caches can hold.
-///
-/// This is a workaround for a real defect in the hand-over, and it is worth writing down exactly
-/// what was measured, because everything cheaper was tried first and every one of them said the
-/// hardware was fine:
-///
-/// * The MMU table is correct. Entries 0..9 read 0x1001..0x100a - the valid bit plus physical
-/// page N+1 - which is precisely what the image builder's single flash-to-vaddr anchor requires,
-/// and entries 10..11 are unmapped as they should be.
-/// * The flash is correct. `zig build flash` verifies an MD5 of what the ROM stored, and the image
-/// matches the ELF byte for byte at the addresses that misread.
-/// * The page size is not in question: it is hardwired to 64 KiB on this chip
-/// (hal/esp32p4/mmu_ll.h:126-130 returns MMU_PAGE_64KB and the setter asserts it).
-///
-/// And yet a load at 0x40035a1c returned `93 85 85 0f`, which is this image's own `.text`. Reading
-/// 512 KiB to force capacity eviction made the same load return `3c ee 08 40`, which is what the
-/// image holds there. So the second-stage bootloader hands over with cache lines that do not match
-/// the mapping it finally installed. It is perfectly deterministic - the same lines every boot,
-/// because the bootloader does the same thing every boot - which is exactly why it looked like
-/// anything other than a cache for so long.
-///
-/// The ROM's own `Cache_Invalidate_All` (0x4fc00404, same address in both esp32p4.rom.ld and the
-/// eco5 table) would be the right instrument and is NOT used: called from here it faults inside ROM
-/// code with the argument stranded in a2, so it wants a precondition this image does not know about.
-/// A capacity flush needs no such knowledge. It costs one pass over 512 KiB of already-mapped flash,
-/// once, at boot.
-///
-/// 512 KiB is four times the 128 KiB the L2 measured at (examples/memprobe.zig found real RAM
-/// stopping at 0x4FFA0000, the cache taking the rest), with the L1s smaller still. The stride is one
-/// 64-byte line. `volatile` and a summed sink so nothing here can be optimised away.
-fn flushFlashCache() void {
- var sink: u32 = 0;
- var p: u32 = 0x4000_0000;
- while (p < 0x4008_0000) : (p += 64) {
- sink +%= @as(*volatile u32, @ptrFromInt(p)).*;
- }
- // Consumed through a volatile store so the whole loop cannot be discarded as dead.
- @as(*volatile u32, &cache_flush_sink).* = sink;
-}
-
-var cache_flush_sink: u32 = 0;
-
// ------------------------------------------------------------------------------------- the loop
export fn zig_main() noreturn {
// FIRST, before a single byte of `.rodata` is touched - which means before the marker below,
// because that marker IS a string literal in flash and would read as machine code without this.
- flushFlashCache();
+ soc.flushFlashCache();
const heap = heapSpan();
soc.rom.print("\r\nMARK B3 rom.print heap 0x%08x..0x%08x %u KiB\r\n", .{
@as(u32, @intFromPtr(heap.ptr)),
diff --git a/src/soc.zig b/src/soc.zig
index 172966e..350948a 100644
--- a/src/soc.zig
+++ b/src/soc.zig
@@ -114,6 +114,51 @@ pub const rom = struct {
}
};
+/// Evict every flash-mapped cache line the bootloader left behind, by reading more flash than the
+/// caches can hold. Call it before the first byte of `.rodata` is touched.
+///
+/// This is a workaround for a real defect in the hand-over, not a tidiness measure. The second-stage
+/// bootloader leaves lines cached against a mapping it then replaces, so an application reads its
+/// own `.rodata` and gets its own `.text` back - **deterministically**, which is exactly what makes
+/// it look like anything other than a cache. Measured on this die: a load at `0x40035A1C` returned
+/// `93 85 85 0f` before eviction and `3c ee 08 40` after, and the second is what the image holds
+/// there. A string literal read before this runs is machine code, so a firmware whose first act is
+/// to print a marker prints garbage and looks like it never booted at all.
+///
+/// Everything cheaper was tried first and every one of them said the hardware was fine, which is
+/// why the list is here rather than being rediscovered:
+///
+/// * The MMU table is correct. Entries 0..9 read `0x1001`..`0x100a` - the valid bit plus physical
+/// page N+1 - which is exactly what the image builder's single flash-to-vaddr anchor requires,
+/// and 10..11 are unmapped as they should be. Read off the die through
+/// `SPI_MEM_C_MMU_ITEM_INDEX_REG`, not inferred.
+/// * The flash is correct. `zig build flash` verifies an MD5 of what the ROM stored, and the
+/// image matches the ELF byte for byte at the addresses that misread.
+/// * The page size is not in question: hardwired to 64 KiB on this chip
+/// (`hal/esp32p4/mmu_ll.h:126-130` returns `MMU_PAGE_64KB` and the setter asserts it).
+/// * Not fragmentation of the mapping either: the bad bytes arrive in one contiguous run of
+/// >= 192 B, not in 64-byte lines, and they are identical across three resets and two
+/// reflashes - determinism is what kept this looking like anything but a cache.
+///
+/// 512 KiB is four times the 128 KiB the L2 measured at (`examples/memprobe.zig` found real RAM
+/// stopping at `0x4FFA0000`, the cache taking the rest), with the L1s smaller still.
+///
+/// `rom.Cache_Invalidate_All` is the instrument that ought to do this and does not: called from an
+/// image the ROM did not launch, it faults inside the ROM with its argument stranded in `a2`.
+/// Capacity eviction needs no preconditions, which is the whole reason it is what ships. 512 KiB at
+/// a 64-byte stride is 8,192 loads, once per boot.
+pub fn flushFlashCache() void {
+ var sink: u32 = 0;
+ var p: u32 = 0x4000_0000;
+ while (p < 0x4008_0000) : (p += 64) {
+ sink +%= @as(*volatile u32, @ptrFromInt(p)).*;
+ }
+ // Consumed through a volatile store, or the optimiser drops the whole loop as dead.
+ @as(*volatile u32, &flush_sink).* = sink;
+}
+
+var flush_sink: u32 = 0;
+
/// Busy-wait for a number of CPU cycles, using the cycle counter rather than the mask ROM. Useful
/// when an image must not depend on ROM entry points at all, and for delays shorter than the ROM's
/// microsecond granularity.