# Indy IP24 PROM 5.3 Rev B10: structural map This document maps the newest known SGI Indy boot PROM, `ip24prom.070-9101-011.bin`. All addresses are PROM addresses as mapped by the hardware; file offsets are `address - 0x1FC00000`. Facts in this document come from three places: 1. byte-level inspection of the dump (padding, strings, jump vectors); 2. Ghidra 12.1.2 auto-analysis driven through REA 6.8.0; 3. MAME's IP22/IP24 machine definitions for the surrounding address map. Function names such as `FUN_1fc02000` are Ghidra auto-generated. The interpretations attached to them are inferences from their code, call sites and the strings they reference; they are labelled as such. ## Identity | Field | Value | | --- | --- | | File | `ip24prom.070-9101-011.bin` | | Size | 524,288 bytes (512 KiB) | | Part number | 070-9101-011 | | Version | `SGI Version 5.3 Rev B10 R4X00/R5000 IP24 Feb 12, 1996` | | SHA-1 | `ac663a4db68528e400487e98cbf901f620fb30ce` | | SHA-256 | `333a0b2e39bb828a28a758b6f633ae7290030129dc747297a1be68dc317b86ae` | | MAME set | `indy_4610` | | Load address | `0x1FC00000` | | Analyzed functions | 1,074 | | Analyzed strings | 1,418 | The SHA-1 matches MAME's `src/mame/sgi/indy_indigo2.cpp` ROM entry exactly. ### The other dumps in the set The `indy_4610` set contains four PROM revisions. SHA-1 values below are the ones MAME records. | File | Version | SHA-1 | | --- | --- | --- | | `ip24prom.070-9101-005.bin` | 5.1.2 Rev B4, 9 Dec 1993 | `28b769b28218a1fcd0400dceef9a284dcfbdda5b` | | `ip24prom.070-9101-007.bin` | 5.0 Rev B6, 28 Sep 1994 | `ade54cd2ecb7064957f8602894f05685e2f4e8fb` | | `ip24prom.070-9101-008.bin` | 5.3 Rev B7, 16 Feb 1995 | `a752a4aef7e2c6086b8b0244e9f064861a11870f` | | `ip24prom.070-9101-011.bin` | 5.3 Rev B10, 12 Feb 1996 | `ac663a4db68528e400487e98cbf901f620fb30ce` | `011` is the newest, and the only one whose version string claims `R4X00/R5000` support. MAME selects it as the default BIOS for both `indy_4610` (R4600) and `indy_5015` (R5000). ## How the image was opened The dump is headerless. REA's raw-binary lane has no MIPS architecture, so a minimal ELF wrapper is produced by `tools/make-elf-wrapper.py`: - ELF32, big-endian, `ET_EXEC`, `EM_MIPS`; - `e_flags = 0x50000000` (`EF_MIPS_ARCH_32`, no ABI field); - no `PT_MIPS_ABIFLAGS` record; - one `PT_LOAD` at file offset `0x1000`, virtual address `0x1FC00000`; - entry `0x1FC00000`. REA admits this through its recordless legacy MIPS lane and pins Ghidra to `MIPS:BE:32:default`. The import log confirms that language. The ROM bytes are not modified. The declared ABI is therefore absent by construction: o32 calling conventions are an analysis assumption of the lane, not a property recovered from the ROM. This affects parameter recovery in decompiled output, not instruction decoding or control flow. ## Physical placement MAME's IP24 map places the 512 KiB BIOS window at `0x1FC00000`. Through the MIPS segments the same bytes are also visible at: | View | Address | | --- | --- | | Physical / KUSEG | `0x1FC00000` | | KSEG0 (cached) | `0x9FC00000` | | KSEG1 (uncached) | `0xBFC00000` | Ghidra analyzed the physical mapping. The reset code itself uses KSEG0/KSEG1 addresses such as `0x9FC7DD18`, `0x9FC4AAC8` and `0xBFA0xxxx`; those appear in decompiled output as their unmapped KSEG forms. Relevant neighbours from MAME (`src/mame/sgi/indy_indigo2.cpp`): | Range | Device | | --- | --- | | `0x00000000` - `0x0007FFFF` | Alias for first 512 KiB of RAM | | `0x08000000` - `0x17FFFFFF` | Low system memory | | `0x1F000000` - `0x1F3FFFFF` | GIO64 graphics | | `0x1F400000` - `0x1F5FFFFF` | GIO64 EXP0 | | `0x1FA00000` - `0x1FA1FFFF` | Memory controller | | `0x1FB00000` - `0x1FB7FFFF` | HPC3 CHIP1 | | `0x1FB80000` - `0x1FBFFFFF` | HPC3 CHIP0 | | `0x1FC00000` - `0x1FFFFFFF` | BIOS (this ROM) | | `0x20000000` - `0x2FFFFFFF` | High system memory | | `0x80000000` - `0xFFFFFFFF` | EISA memory | The reset code touches `0xBFA00xxx`/`0xBFA01xxx` (KSEG1 alias of the memory controller) and `0xBFBD9xxx`/`0xBFBBxxxx` (KSEG1 alias inside HPC3 CHIP0). ## Image layout Page-level detail is in [rom-map.json](rom-map.json). The compressed view: | File offset | PROM address | Size | Content | | --- | --- | --- | --- | | `0x00000` - `0x003BF` | `0x1FC00000` - `0x1FC003BF` | 960 B | 60-entry, 16-byte exception jump table | | `0x003C0` - `0x4A89F` | `0x1FC003C0` - `0x1FC4A89F` | ~298 KiB | Reset handler, code and inline diagnostic strings; 1,074 functions, last one starting at `0x1FC4A7E0` | | `0x4A8A0` - `0x5568F` | `0x1FC4A8A0` - `0x1FC5568F` | ~44 KiB | Core message blob: boot sequence, diagnostics, monitor help, environment and ARCS strings | | `0x56000` - `0x6CFFF` | `0x1FC56000` - `0x1FC6CFFF` | 92 KiB | Binary tables; no Ghidra-defined strings | | `0x6D000` - `0x6EA4C` | `0x1FC6D000` - `0x1FC6EA4C` | ~6.5 KiB | Path-test strings and the environment variable/default table | | `0x6EA4D` - `0x7BF8F` | `0x1FC6EA4D` - `0x1FC7BF8F` | ~54 KiB | Mostly zero/binary pages plus two isolated strings | | `0x7BF90` - `0x7C16B` | `0x1FC7BF90` - `0x1FC7C16B` | ~480 B | SCSI tape drive name table | | `0x7C16C` - `0x7F9FF` | `0x1FC7C16C` - `0x1FC7F9FF` | ~14 KiB | Remaining data tables | | `0x7FA00` - `0x7FFFF` | `0x1FC7FA00` - `0x1FC7FFFF` | 1536 B | `0xFF` padding | Only four `0xFF` runs of 16 bytes or more exist; three are tiny alignment holes inside the string area, and the real padding is the final 1,536 bytes. The last non-`0xFF` byte is at file offset `0x7F9FF`. The code and message regions overlap at the page level: page `0x1FC4A000`-`0x1FC4AFFF` holds the last nine functions and 64 strings, so the "code ends" boundary above is the end of executable content, not the start of all strings. ## Exception vector table The first 960 bytes are 60 cells of 16 bytes: ``` 0x1FC00000 + (index * 0x10) ``` Each cell begins with an absolute MIPS `J` instruction followed by three `NOP`s. The reset handler lives at `0x1FC003C0`, immediately after the table, so the final four 16-byte slots are not table entries. Observed targets: | Cell address | Index | Target | Count | | --- | --- | --- | --- | | `0x1FC00000` | 0 | `0x1FC003C0` (reset handler) | 1 | | `0x1FC00200` | 32 | `0x1FC00F20` | 1 | | `0x1FC00280` | 40 | `0x1FC00F28` | 1 | | `0x1FC00300` | 48 | `0x1FC00F18` | 1 | | `0x1FC00380` | 56 | `0x1FC00E40` | 1 | | all other cells | - | `0x1FC007C4` (shared handler) | 55 | The architectural MIPS R4000 vector offsets (`0x080` TLB refill, `0x100` general exception, `0x180` XTLB refill, `0x200` cache error) fall inside this table. Of those, only `0x200` is special; the rest use the shared handler. The meaning of indices 32, 40, 48 and 56 is not established by this analysis. One plausible reading is an SGI dispatch table indexed by a cause-derived value rather than by the architectural vector offset, but that is an inference, not an observation. The four special targets all land inside the block at `0x1FC00E40`-`0x1FC00F28`. Ghidra did not split that block into separate functions; auto-analysis treats `0x1FC00DCC` as the nearest function start. The bytes there are worth a focused follow-up with manual function creation. ## Reset and boot flow ### Reset handler, `0x1FC003C0` Observed instruction sequence (REA/Ghidra): 1. `lui v0, 0x3041; mtc0 v0, Status` and clear `Cause`, `PageMask`, `WatchLo`, `WatchHi`. 2. `Config = 2`, then test `Config` bit 15. If clear, call `FUN_1fc0a810`, which reads and rewrites a CP0 configuration value and writes `0xBFA00000 = 0x10000`. 3. Write memory-controller registers at `0xBFA000EC`, `0xBFA000FC`, `0xBFA00004`, `0xBFA0000C`, `0xBFA00084`, `0xBFA000D4`, `0xBFA000DC` and `0xBFA0002C`. The value stored at `0xBFA00084` is loaded from `0x9FC7DD18`. 4. Poll `0xBFA01004` until the counter advances by at least `0x271`. 5. Call `0x1FC00A3C`, `0x1FC00BD0`, `0x1FC00DCC`, `0x1FC00D58`. 6. Set `s8 = 0x9FC03660`, then call `0x1FC039E0`, `0x1FC03DE0`, `0x1FC03EB0`, `0x1FC01070`. 7. Sample `0xBFA000C4`, test bits `0x2200`, optionally set low bits of `0xBFA00004`. 8. Call `0x1FC009D4`, then `j 0x1FC02000`. The Ghidra decompiler renders the end of this function as an infinite loop because it does not model the final `j` as a return. The instruction listing is the authority here. ### Cold-start main, `0x1FC02000` The function reached by the reset jump initializes HPC3/IOC registers around `0xBFBD9xxx`, calls a long sequence of hardware setup routines, and then reads the `diagmode` environment variable at `0x9FC4AAC8`. Two paths follow: - diagnostics/menu path (`FUN_1fc02a50`) when the diagnostic state is set; - autoboot path (`FUN_1fc0fb10`) otherwise. `FUN_1fc0fb10` reads `AutoLoad` (`0x1FC4D090`) and `NoAutoLoad` (`0x1FC4D09C`). If autoload is enabled it prints the maintenance prompt `To perform system maintenance instead, press ` and attempts a boot; a failure prints `Unable to boot; press any key to continue:` and falls back through `FUN_1fc00624`. `FUN_1fc02a50` is the menu/boot loop. It reads `VERBOSE` and `diskless`, touches IOC interrupt-enable bits, and loops through the maintenance menu whose strings sit at `0x1FC4D99C`: - `Install System Software` - `Recover System` - `Enter Command Monitor` - `Select Keyboard Layout` The same area contains `Autoboot failed`, `Diagnostics` and `Command Monitor. Type "exit" %sto return to the menu.` ### Boot-sequence messages The ordered diagnostic strings beginning at `0x1FC4A8A0` describe the intended power-on sequence: 1. `Power switch released.` 2. `Initializing primary/secondary caches.` 3. `Secondary cache size is 0x%x (%d Kbytes)` 4. `Initializing exception handlers.` 5. `Initializing Status Register.` 6. `Enabling PLP, ISDN B/A DMA channels.` 7. `Selecting Gen Control register bits.` 8. `Initialize Map Polarity.` 9. `Deasserting VIDEO Reset.` 10. `Playing startup tune.` 11. `Initializing environment.` 12. `Executing remainder of diagnostic.` 13. `Initializing software and devices.` 14. `All initialization and diagnostic completed.` ## Function anchors | Address | Auto name | Interpretation | Basis | | --- | --- | --- | --- | | `0x1FC00000` | `entry` | Table slot 0 thunk to the reset handler | Ghidra symbol, thunk target | | `0x1FC003C0` | `FUN_1fc003c0` | Reset handler | CP0 init, memory-controller setup, jump to main | | `0x1FC00624` | `FUN_1fc00624` | Shared exception/fallback path | Vector-table default target; cache and memory scrub code | | `0x1FC00DCC` | `FUN_1fc00dcc` | Short HPC3/cache stub | Writes `0xBFB91004`, `0xBFBC0003`, `0xBFBC0007`; nearest function for special vectors | | `0x1FC02000` | `FUN_1fc02000` | Cold-start main | Reached by reset jump; HPC3/IOC init; branches to diagnostics or autoboot | | `0x1FC02A50` | `FUN_1fc02a50` | Menu/boot loop | Reads `VERBOSE`/`diskless`; loops over maintenance menu | | `0x1FC0FB10` | `FUN_1fc0fb10` | Autoboot policy | Reads `AutoLoad`/`NoAutoLoad`; maintenance prompt and boot failure text | The full list of 1,074 function starts is in [procedure-index.csv](procedure-index.csv). The CSV has three columns: `address,name,thunk_target`. ## Firmware feature map ### Command monitor Help strings at `0x1FC4ACE8`-`0x1FC4B054` enumerate the monitor commands: `autoboot` (`auto`), `boot`, `checksum`, `date`, `dump`, `exit`, `fill`, `get`, `go`, `help`/`?`, `initialize` (`init`), `inventory`, `passwd`, `printenv`, `put`, `reboot`, `resetenv`, `resetpw`, `setenv`, `single`, `unsetenv`, `version`. The banner is `PROM Monitor %s (BE)` at `0x1FC4B090`, followed by the version string. ### Boot targets Strings at `0x1FC4AAF0`-`0x1FC4AB44` name the default load paths: - `bootp()ide.IP24` - `sashIP24` - `bootp()sashIP24` - `bootp()unix.IP24` Elsewhere the image references `sashARCS`, `part(8)sashARCS`, `/stand/ide`, IDE distribution CD boot, and ARCS-style boot. ### Diagnostics Diagnostic result strings name the tests the PROM can run: - memory probe, memory data test, memory address test, walking-bit test; - cache diagnostic; - graphics diagnostic; - interrupt controller diagnostic; - SCSI controller, SCSI device/cable, WD SCSI0 path test; - RTC path test, INT path test, data path test; - PC keyboard/mouse controller, keyboard, mouse; - CPU base board, graphics board, CPU module, SIMM replacement prompts. The display-side strings also expose the VDMA and DMA error registers (`DMA_CAUSE`, `DMA_MEMADR`, `CPU_MEMACC`, `GIO_MEMACC`, `CPU_ERR_ADDR`, `CPU_ERR_STAT`, `GIO_ERR_ADDR`, `GIO_ERR_STAT`) and the MIPS exception dump fields (`ErrEPC`, `BadVaddr`, `Cause`, `Status`, `CacheErr`, `CpuParityErr`, `GioParityErr`, `LIOstatus0..2`, `CpuErrorAddr`, `GioErrorAddr`). ### Environment variables The environment table around `0x1FC6E4F0`-`0x1FC6EA4C` and the help strings around `0x1FC4CD00`-`0x1FC4E000` cover: `AutoLoad`, `NoAutoLoad`, `diagmode`, `console`, `diskless`, `VERBOSE`, `eaddr`, `netaddr`, `srvaddr`, `gateaddr`, `netinsthost`, `netinstfile`, `tapedevice`, `scsihostid`, `scsiretries`, `pagecolor`, `TimeZone`, `autopower`, `SystemPartition`, `OSLoader`, `OSLoadPartition`, `OSLoadFilename`/`OSLoadFileName`, `OSLoadOptions`, `ConsoleIn`, `ConsoleOut`, `ConsoleWarning`, `passwd_key`, `netpasswd_key`. The NVRAM path has its own messages: `NVRAM checksum is incorrect: reinitializing.`, `nvchecksum:`, `Checksum mismatch: is 0x%x, s/b 0x%x`, `Enter new password:`, `Confirm new password:`, `Password cleared.` and the password-jumper warnings. ### ARCS and SCSI tables The string blob around `0x1FC5549C`-`0x1FC55524` contains the ARCS component names `ExceptionBlock`, `SystemParameterBlock`, `FreeContiguous`, `FreeMemory`, `BadMemory`, `LoadedProgram`, `FirmwareTemporary` and `FirmwarePermanent`. The cluster at `0x1FC7BF90`-`0x1FC7C16B` is a SCSI tape-drive name table: `CIPHER`, `TANDBERG TDC 3660`, `ARCHIVE VIPER 150`, `VIPER 60`, `Python`, `EXABYTE` and `KENNEDY`. The sentinel value `cafebabedeadbeef` appears at `0x1FC6EA4C`; it is likely an uninitialised-memory pattern used by the environment/NVRAM code, but that is an inference. ## Confidence and limitations - Function boundaries and names come from Ghidra auto-analysis. There are no symbols in the image beyond the imported `entry` label. - The ELF wrapper declares no ABI. o32 is an analysis assumption, so recovered parameter counts and types are weaker than the control flow. - The vector table's 60 x 16-byte structure and observed targets are facts. What each index means is not established. - The decompiler output is heuristic. Where it conflicts with the instruction listing (for example the reset handler's final jump), the instruction listing is authoritative. - String inventory covers Ghidra-defined string data only (1,418 entries). Undefined or packed text may be missing, and an absent string is not proof that the text is absent from the artifact. - No code was executed. Nothing here establishes runtime behaviour. - Ghidra 12.1.2 was used; REA's bridge is verified against 12.1.4 and accepts other 12.1.x builds. ## Reproduce ```bash ./tools/fetch-roms.sh ./tools/make-elf-wrapper.py \ roms/ip24prom.070-9101-011.bin -o roms/indy-ip24-5.3-b10.elf export GHIDRA_INSTALL_DIR=/opt/ghidra rea analyze roms/indy-ip24-5.3-b10.elf --provider ghidra --json ``` The checked-in artifacts were produced with the REA MCP session described in [rea-setup.md](rea-setup.md), then: ```bash ./tools/rom-map.py roms/ip24prom.070-9101-011.bin \ --evidence evidence/rea-bundle.json \ --output docs/rom-map.json \ --procedures-csv docs/procedure-index.csv ```