Fetch and verify the four MAME indy_4610 PROM dumps, wrap the newest dump in an ELF32 BE MIPS image for Ghidra/REA, and record the resulting structural map: image layout, exception vector table, boot flow, function anchors and firmware feature clusters.
369 lines
15 KiB
Markdown
369 lines
15 KiB
Markdown
# 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 <Esc>` 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
|
|
```
|