Skip to content

fuel: add the SGI Fuel (IP34 board, IP35 platform) - #368

Draft
jamesbraid wants to merge 44 commits into
techomancer:mainfrom
jamesbraid:fuel-ip35
Draft

jamesbraid wants to merge 44 commits into
techomancer:mainfrom
jamesbraid:fuel-ip35

Conversation

@jamesbraid

@jamesbraid jamesbraid commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

This adds the SGI Fuel: a 500 MHz R14000 on a Bedrock node with 512 MiB, the dual-head XBridge, the IOC3 (Ethernet, serial, parallel, PS/2), the two-channel ISP12160, the L1 system controller, AMD boot flash and OHCI USB. PROM 6.210 passes its diagnostics and boots IRIX 6.5.30 to the 4Dwm desktop, and with a V12 board SGI Quake, SGI Doom and Performer draw. Most devices are ported from my sgi-origami QEMU branch.

This is based on #422 (the shared PCI layer) and carries #448's five commits (each CPU's cached view of memory) until it lands; both drop out on rebase. The XBridge uses the shared PCI layer as the Octane's Bridge does: each of its two heads owns a PciBus, implements PciHost, publishes its device and alias windows as CPU windows with the byte order chosen when they are published, and takes its interrupts through the shared combiner (IntxRouting, with the IOC3's SuperIO interrupt as a BoardIrq), so it has no slot table, BAR-mapping code or interrupt routing of its own.

fuel-final-desktop fuel-hinv-quake fuel-final-doom

Processor

  • The Fuel has its own chip, r14000_500: R14000 rev 2.3 at 500 MHz with a 2 MB secondary cache, as the IP35 hinv dumps show. It is the only entry in the Fuel's CPUS.
  • Config change for a saved Fuel config: cpu = "r14000" now means r14000_500, and cpu = "r10000" is refused. Leaving cpu out takes the default. fuel.toml.example is updated.
  • The flash configuration block's CPU type word is 1 (R14000), as the IP35 PROM's own configuration editor lists it.

Fuel-only CPU behaviour

  • Uncached SB and SH reach the bus with the whole source register. The PROM's ECC memory test reads the other byte lanes.
  • R10000-family CPUs keep the uncached attribute from EntryLo 63:62 on mapped pages, which Bedrock decodes as the address space. Other machines drop those bits before the bus.
  • The PROM builds its stack in cache lines it stages before DRAM works. The CPU holds them in its cached view of memory from cpu: keep valid R10000-family cache lines in each CPU's own view of memory #448; one commit here lets a machine declare places where lines may be held with no memory mapped yet, and the Fuel declares its four bank slots. The memory controller models nothing for it.
  • The executor keeps one bus for cacheable memory, cached_bus, which the JIT's page lookup, its inline compile, a flush's restart and the compile pool all use. It is sysad on every other machine. The Fuel's sysad is the uncached connection, which decodes a plain address as Hub-special space, so the Fuel gives the executor the view its cache fills from and starts the pool on that same object. This replaces the earlier restart fix.

PS/2 through the IOC3 is the default keyboard and mouse; [fuel] usb_input adds USB HID. There is no audio: the Fuel had none built in.

Testing

  • Rebased onto main 79bd1ce, which already carries six of this branch's earlier commits (header write policies, the DP83843 PHY, the M48T35 timekeeper, the IOC3 Fuel population, the AMD flash command set, uncached byte and halfword stores); they are dropped here, leaving 22 commits. cargo test --workspace --no-run --features jitv2 builds at every commit; cargo test --features jitv2 --lib passes at the tip (3620 tests) apart from no_process_exit_bypasses_the_flush, which fails on main itself.
  • At the tip the Fuel (release build, jitv2) boots to login in 106 s with ALL DIAGS PASSED, network, DNS and rsh, and no lines held at login. On the same build an O2 with its default R5000 (77 s), an Octane (84 s) and an Origin 200 (84 s) boot to login and halt. At the previous head it booted in 122 s, and in 180 s with all compiled code thrown away every 25 s (five stop; j2 flush; start cycles from the monitor), which checks that the JIT's page tracking and compile pool rebuild correctly mid-boot on the Fuel's split cached and uncached buses. Both runs print ALL DIAGS PASSED. hinv shows the R14000 rev 2.3 at 500 MHz, 2 MB secondary, 512 MB, both QL12160s, the V12, ef0 and the OHCI controller. The network answers ping, DNS and rsh, halt -p exits 0, and the cached view holds no lines at login (l2 probe 0: 0 valid lines).
  • The V12 draws the 4Dwm login screen with its full background at the tip. I did not rerun Quake, Doom or Performer at this head; the screenshots above are from the previous base.
  • The desktop, Quake, Doom and Performer draw on the V12 Fuel on this branch with the odyssey index-copy fix applied locally (a separate PR). Without that fix the desktop's root background paints only in part, an upstream VPro bug that is not specific to the Fuel.

Performance

No cost to other machines: iris-bench ab (from bench-ab-pr) against #448 (r10k-cache-line-staging), release features lightning,jitv2,gpujit,idle-pause,hostgl, concurrent pairs on a busy host, 20 rounds, Indy R4400. The suite is -1.2% [-2.7%, +0.7%] (99.9% interval, B over A, negative is faster), and no kernel is flagged. Host cycles per guest instruction are -3.9%. 25 noisy kernels are inconclusive, so the claim is no slowdown beyond +0.7% on the suite, not zero.

I/O (bedrock_io):
Fuel needs Bedrock I/O to route programmed PIO windows to PCI widgets,
check inbound DMA permissions and run its two block-transfer engines.
Fault reporting and interrupt delivery stay with the block that owns the
state, and unsupported accesses are reported.

Register map and behaviour are ported from the QEMU model.

Node routing (bedrock_ni):
Fuel runs as one node with no NUMAlink peer, but the guest still reads
and programs these local control words during machine setup. Retain the
writes and keep the network fabric out of the model.

Crossbar (bedrock_xb):
Record the fabric error queues and let software service them, with the
outputs available to the Bedrock processor interface. Unsupported queue
reads return zero and writes are logged and ignored.

Local bus (bedrock_lb):
Add the node and region registers, atomic scratch aliases, error and
vector status, and the MicroLAN master that board identity parts sit on.
The tests drive it together with the I/O, node-interface and crossbar
blocks it reports through, which land in this same commit.
The Fuel CPU receives interrupts and timing events through Bedrock PI.
Model the fitted processor slot and its node callbacks while keeping
unconnected graphics control explicit.
Fuel reaches its AMD flash through executable PROM aliases and
programming windows. Share the array bytes and persistent store while
preserving the command and erase state of each flash part.

The configuration block's CPU type word is 1, the R14000, as the IP35
PROM's own configuration editor lists it (0 for the R10000 and R12000, 2
for the R16000); a test pins it.
Fuel routes PCI configuration, DMA and interrupt packets through two
XBridge heads. Each head owns a PCI bus, as the Octane's Bridge does: the
bus holds the head's functions, decodes their BARs and combines their
interrupt pins, and the head supplies only what is its own. That is how a
processor access becomes configuration cycles and byte order (from a
device window's register, or from Control for an alias), the translation
table and DMA, and where an interrupt pin goes. Whole-page BAR runs of
the device and alias windows are served from the head's own table, so the
bridge shares the machine memory map without allocating more Physical
tables.

A board attached to one of the crossbar's external ports raises its
interrupts as crosstalk packets, the same way the PCI heads do. Route
them through the same sender, so the Fuel's graphics board reaches the
Bedrock's processor interface with no path of its own, and say whether
the destination took the packet.

The crossbar answers with the Crossbow's widget 0 register map, so it
takes its offsets from the Octane's Crossbow model instead of restating
them. What stays separate is behavior that differs between the two: the
XBridge crossbar's status register carries the requesting port, wide
and malformed accesses set the access-error status and answer zero, its
interrupts go out as crosstalk packets to any destination, and it
resets the heads and ports its fences select.
The Fuel console first passes through the L1 system controller. Reuse
the shared 16550 core and keep controller framing, power commands and
identity records in its protocol model.
Fuel enumerates a Lucent OHCI controller on PCI. Provide its registers
and descriptor scheduling through the USB function interface while
leaving devices unattached in the machine configuration.

The frame counter follows the machine clock the board hands the
controller, and every register read and write first delivers the frame
boundaries that clock has passed. Advancing it only from the board's
poll, which runs between CPU batches, left HcFmNumber at zero after the
guest's own 1 ms wait had expired, and the Fuel PROM reported that SOF
did not start within 2 ms. The sync holds one lock from reading the
clock to applying the frames, so a timer thread cannot leave a
concurrent reader behind it.

Entering UsbOperational from Reset or Suspend re-anchors the frame:
it loads HcFmRemaining from HcFmInterval, toggle included, and
increments HcFmNumber, with the first SOF one frame later (OpenHCI 1.0a
6.2.1, 7.3.2, 7.3.3). The tests cover that entry with frame numbers at
and around the overflow flag.

Add two low-speed USB functions for the OHCI root ports: a HID boot
keyboard and a HID boot mouse, each with one configuration, one boot
interface and one interrupt-IN endpoint. They answer the standard
requests (descriptors, SET_ADDRESS taking effect after its status
stage, SET_CONFIGURATION, status and interface queries) and the HID
class requests (GET/SET_REPORT, GET/SET_IDLE, GET/SET_PROTOCOL), and
report host keys and pointer moves as boot reports. Reports queue one
per change so a key tapped between two polls still shows both edges.

Written from the USB 1.1 and HID 1.11 specifications (boot descriptors
from appendix B) and the HID Usage Tables, with no code from another
emulator. The wheel, remote wakeup and the host layout translation that
the PS/2 path has are left out. Ohci::attach loses its test-only gate so
a machine can plug the devices in.
The JIT reads code pages, and asks for their generation counters, at plain
physical addresses, through the executor's sysad. Everywhere else sysad is
also what the cache fills from, so a plain address there is memory. The
Fuel's sysad is the uncached connection: it takes bits 58:57 of the address
as the uncached attribute and decodes a plain one as Hub-special space,
which holds RAM only in its first 256 MiB. Its compile pool was started on
the cache's own view of memory instead, so the pool and the CPU's inline
compile and page lookup read code through two different objects, and a
flush restarted the pool on the CPU's.

The executor now keeps one bus for cacheable memory, defaulting to sysad,
and the page lookup, the inline compile, a flush's restart and `j2 inline
off` all use it. A board whose uncached connection decodes differently
gives it the cache's view with set_cached_bus and starts the pool on the
same object. No other machine changes: the two are the same object there.
Build the Fuel IP34 board around its R14000, Bedrock, XBridge, IOC3 and
dual-channel ISP12160. Use one Physical memory table with sparse
processor and peripheral views.

Uncached byte and halfword stores reach a device through
BusDevice::write_narrow_from, which each window forwards at the
device-local address; only the memory controller's ECC view reads more
than the low bits of the register.

With the jitv2 feature, start the compile pool after the memory windows
are published, as the Octane and O2 do. Each worker copies the cache
geometry once as it starts, and without the windows that geometry has no
inline load or store path. Workers read code through the CPU's cached bus, the
view the cache fills from, which serves plain physical addresses; the
uncached views would decode them as node registers.

The machine describes where its memory can answer to the CPU: the four
1 GiB slots Bedrock's controller places its two 256 MiB banks in, and the
reset PROM. Its disks on both ISP12160 channels are flushed at power-off,
quit and exit like the other machines', so a copy-on-write overlay keeps
its record of held sectors after a guest `halt -p`.

The processor is the R14000 at revision 2.3 (PRId 0x0f23), the revision a
public software inventory of a Fuel prints, where the Octane's part
reports 2.4. The raw CP0 word, the FIR and the 128-byte lines are taken
from the Octane part, not read from a Fuel. Like the Octane's R14000 it
reports "R12000" as its name. The machine loads its PROM through
prom_image.

Feed the IOC3 PS/2 controller from the window, VNC and CI input, and let
a USB keyboard on root port 0 and a USB mouse on port 1 share it. The
Fuel's manual shows PS/2 as the standard keyboard and mouse, so
ps2_input under [fuel] defaults on and usb_input defaults off; setting
usb_input fits the USB pair, and ps2_input = false leaves the PS/2 pair
out. The monitor's ps2 command reaches the keyboard and mouse too.

PROM loader (prom_image):
Add a device-independent loader for PROM images. It accepts a raw
payload or a version-six stored or gzip container, decodes the
recognized containers explicitly and reports malformed inputs with their
path before any bytes reach guest memory.

Config profile (config):
The Fuel is a new machine profile with two memory banks, wide SCSI buses,
no Ethernet adapter choice beyond its built-in one, and no graphics board
by default. The [fuel] section selects the serial console (the L1
controller or either IOC3 port), the L1 TCP port and log, and the
persistent boot flash store. l1_revision and brick_serial replace
the L1's reported firmware revision (default 1.48.1) and brick serial
(default EMUN00). ps2_input defaults on and usb_input off, as
the Fuel's manual shows PS/2 as the standard keyboard and mouse; setting
usb_input fits a USB keyboard and mouse, and ps2_input = false leaves the
PS/2 pair out.

The key names the part: machine.cpu = "r14000" selects the Fuel's
r14000_500, and a Fuel config cannot claim another processor. The
Octane's R14000 is revision 2.4; the Fuel's is 2.3.
The test slept 150 ms and required the pushing thread to still be
unfinished. The board thread's hold was a fixed 4 s sleep, which a
loaded host could outlast before the test looked, so the refused store
could complete and the assertion fail.

The hold now lasts until the test releases it, and the test waits for
the push to be refused before it asserts.
The board assumed the Octane: its interrupt packets went only to HEART
at widget 8, and it took the processor's addresses in the IP30 widget
windows. A Fuel's processor reaches widget 13 through Bedrock, which
hands the board widget offsets, and the board's packets cross the
XBridge's crossbar to Bedrock like any widget's.

Add a constructor for such a machine. It takes a packet sender for the
interrupts and decodes every address as an aperture offset. The two
choices were hard-wired into the board; they are now one packet sender
(the Octane's checks for HEART's widget and register and drives its
line, and a packet to anywhere else is refused as before) and an
explicit aperture mode chosen at construction. The Octane's constructor
and behaviour are unchanged, and nothing on its store path is touched.
The debug register dump no longer lists the heart widget, which is the
Octane sender's business now.

A test routes a packet to a widget other than HEART and requires the
sender to get it, not HEART's line.
Every Fuel has a VPro graphics slot: a Fuel IRIX inventory lists its
board under xtalk/13 and OpenBSD finds Odyssey at crossbar widget 13.
Build the board there when the machine is given a V12 or a V10, through
the widget-offset constructor, and keep none, the serial machine, as the
default.

The XBridge now reports link 13 alive, the board's DMA enters Bedrock's
inbound path as widget 13, and a link reset of port 13 resets it. Its
interrupt packets cross the crossbar to Bedrock like the PCI heads'. The
status bar's activity (SCSI, IOC3 and Ethernet) and the CPU's Compare
ticks reach its LEDs as on the Octane. IRIX's VPro driver writes each
processor interface's graphics flow-control registers at attach and tears
the board down when the credit counter reads zero, so each
interface keeps those registers as plain storage. Nothing counts
stores or returns credits.

With PROM 6.210 the board passes the graphics diagnostic, and IRIX
6.5.30 with the VPro driver lists Graphics board: V12 and draws the X
login screen on it. Input comes from the PS/2 pair the Fuel already has.
Configuration accepts the board on the Fuel too, see the config note below.

Tests build a Fuel board with each board and check that the processor
reaches the board at widget 13 and finds its link alive, that its
interrupt packets set and clear a processor interface's pending bit, that
a link reset of its port resets it, that a headless Fuel has nothing
there, and that the processor interfaces keep their graphics registers.

Config (config):
A Fuel is reported with both the V12 and the V10, so take either with
[graphics] board; the other boards stay refused, with a message that
names the Fuel's choices, and none, the serial machine, stays the
default. The doc comments for the two boards now say they fit the Fuel as
well as the Octane.

The Fuel's validation case now accepts both boards and refuses the V6.
Document the supported serial configuration, image inputs, the host
input options and the explicit limits for graphics, USB devices and
snapshots, in the feature list, hacking notes, debug notes, the sample
config and a Fuel page. The page also says how the PROM's stack in
primary data-cache lines is served: by the CPU's staged-line support,
with nothing special in the memory controller.
…sses

The R10000 drives a two-bit uncached attribute on the system bus. For an
unmapped xkphys access it is address bits 58:57; for a mapped reference
it comes from the TLB's uncached-attribute field, which EntryLo0/1 bits
63:62 load (R10000 User's Manual 007-2490-001). EntryLo already keeps
those bits on the family, and a TLB entry holds them, but the entry's
physical base dropped them, so a mapped uncached page always reached
attribute zero.

Carry them to physical bits 58:57 only for pages the model's C table
decodes as uncached, so cached mappings are unchanged. The TLB already
holds that table for its shadow entries; reading it there, and
recomputing each entry's base when a table is installed, avoids a second
decode of C.

An entry with no attribute bits set, the usual case, skips the C table.

The attribute reaches the bus only on a board that decodes physical
bits 58:57. The Octane, Indigo2 and O2 mask the physical address to 40,
32 and 32 bits before the bus, which folds it away, and a test shows
that at the executor. The R4400 and R5000 never store the bits.

Tests cover the attribute and the board masks, and the C table (a value
the model decodes as cached carries nothing; a table installed after the
entry was written decides).

The uncached-attribute comments cite NEC U10278EJ4V0UM, the fourth edition
of the VR10000/VR12000 user's manual, section 11.3, by document number;
the manual itself is not added to the repository.
Bedrock memory configuration changes the RAM decode. Keep ordinary CPU
and DMA memory on Physical directly, divert diagnostic pages through MD,
and update their generation state when memory configuration or ECC
changes.

The ECC view overrides write_narrow_from to take an uncached SH's check
word from the whole source register, at the position Fuel PROM 6.210's
memory test uses. Every other view stores the architectural low bits.
@jamesbraid
jamesbraid marked this pull request as draft October 31, 2026 05:28
@jamesbraid

Copy link
Copy Markdown
Collaborator Author

wip, doing some rework and cleanup

@jamesbraid
jamesbraid force-pushed the fuel-ip35 branch 14 times, most recently from 24e0013 to e49c3b0 Compare November 2, 2026 03:10
Each widget's first 2 GB of XIO space also appears at physical
0x8_0000_0000 + w * 2 GB. For the Bridge that window holds the 16 MB small
window and, from offset 0x4000_0000, the 32-bit alias of PCI memory space
from PCI address 0 (Bridge ASIC specification, p. 52), with the bytes
placed by Control's memory swap bit rather than a Device(x) register
(p. 55). iris mapped neither, so any PIO there was an unmapped physical
access.

IRIX 6.5.30 reaches a BAR this way. After a warm reboot it numbered a
PCI-to-PCI bridge in slot 5 again, this time with a memory window of
0x80_0000 to 0xff_ffff, and read the NVMe controller behind it at
0xF_C080_0008 (PCI memory 0x80_0008). The read bus-errored and the kernel
panicked.

The Bridge now publishes the alias as a ninth CPU window, so a BAR it
decodes whole becomes a run the processor reaches directly, as the
device windows' runs are. Ip30Bus maps the 2 GB window to the Bridge and
puts alias runs there instead of in the small window; a widget with no 2
GB window maps no such run. The specification calls 16 MB to 1 GB
reserved without saying what an access does, and iris answers it with a
bus error. An alias cycle nothing claims is a master time-out whose
capture names device 0; the specification does not say which device it
names. The address map's section on XIO addressing put widget 15's
window at 0xF_0000_0000, against its own physical map and the address
IRIX uses; it now says 0xF_8000_0000. The 64-bit alias at 0x8000_0000
and PCI I/O at 4 GB, which only the 64 GB window reaches, are still not
modelled.
The PCI layer now has an Octane windows section in docs/pci.md (the
Bridge's 2 GB window, what its reserved range answers, and what is not
modelled), and the open-items note records what is deferred and what
is approximated: restore not being a reset, the AIC-7880 chip reset,
the missing external ATE RAM and the per-device reset pins.

clanker/plans/pci.md lists what follows this change: the XBridge and
PIC moving onto the shared layer with their machines, the XIO PCI
carrier, card cage and NVMe in real slots, and the reset work that
waits for the machine rework.
@jamesbraid jamesbraid changed the title fuel: the SGI Fuel (IP35) machine fuel: add the SGI Fuel (IP34 board, IP35 platform) Nov 7, 2026
@jamesbraid
jamesbraid force-pushed the fuel-ip35 branch 5 times, most recently from 5467215 to 7571c3a Compare November 8, 2026 00:28
@jamesbraid

Copy link
Copy Markdown
Collaborator Author

good to go

A CPU's cache, its decode cache and its compiled code reach RAM through
ppmem's window, and every CPU and Physical's direct paths share that one
window. The R10000-family PROMs use the caches as working memory before
memory is set up: lines made valid with Index Store Tag and Index Store
Data hold a stack or code while uncached accesses, which bypass the
caches (NEC VR10000 user's manual p. 75), see memory. The Tezro's
processors hold lines for the same addresses with different contents at
once. One shared window has no room for any of that.

PpMemSpace::add_cached_view gives a CPU a window of its own. Every
mapping laid in the shared window is laid in each view as well, and a
view publishes its bitmap to that CPU's inline fields, so the CPU reads
its view through the same base pointer and bitmap test it used before.
hold_line_page turns a page of a view into private memory, starting as
a copy of memory, and release_line_page lays memory back when the last
line goes, dropping what the page held. A remap saves and restores the
held pages around it. A page with no memory behind it is refused. One
in a region the window maps only in part takes the whole region in
that view, reading zeros where nothing is mapped, because the region
bitmap is all a CPU tests: the O2's POST holds lines for page 0 before
CRIME maps the rest of its region. Nothing holds a page yet. The views
are not coherent with each other or with DMA.

The generic machine, the Octane, the O2 and the IP27 machine give each
CPU a view now. Physical and the devices that read the window directly
keep the shared one. Nothing on a load, store or fetch changes. The CPU
no longer takes over the shared window's bitmap, so set_bitmap_sink and
add_bitmap_sink lose their last callers and go.
The Fuel's and the Tezro's PROMs run code from primary instruction lines
they make valid with Index Store Tag and Index Store Data, and on iris
those words differ from memory once memory is set up there (measured on
the model, not on hardware). Compiles read memory and compiled code is
shared by every CPU, so code compiled from memory would run in place of
the lines. Upstream iris has no machine that runs code from a held page
yet: the O2 keeps only a stack in lines and runs POST uncached, and the
Octane's lines name no memory. This is for the Fuel and the Tezro.

The first CPU to hold a page now puts GEN_HELD on the page's generation
counter, which retires what was compiled there, and the last release
takes it off with an ordinary bump. A compile that finds the mark
publishes no code for that generation and denies every offset, so
arrivals interpret the page from the CPU's own view without asking
again. The first compile after the release allows every offset once
more. The release lowers the counter, so publish treats any change of
generation as new bytes, not only a rise. ppmem owns the mark, so it is
one mark however many CPUs hold the page, and a remap moves it to the
counter that backs the page afterwards.

All of it is at compile time. Dispatch and compiled code are unchanged,
and every other page stays compiled. Without the denial, four Tezro
processors asking for the same held PROM page took 65-77 s to the
BASEIO monitor against 38-42 s with it (an earlier version of this
change, jitv2 loop-profile builds).
…view

The R10000-family PROMs keep stacks and data in cache lines they make
valid with Index Store Tag and Index Store Data. The O2's POST tests
page 0 uncached while its stack lives in secondary and primary data
lines for the same page, the Fuel's and Tezro's stacks sit in primary
data lines for 0x100000-0x107fff, and on the Tezro every processor holds
those lines at once with its own contents. The shadow cache kept the
tags and data arrays for diagnostics only, so a cached access read
memory.

A tag change that makes a primary data or secondary line valid now holds
its page in the CPU's cached view and writes the line's data array into
it, since Index Store Tag writes the tag array alone (NEC VR5000/VR10000
instruction manual pp. 24-25). Loads, stores, fetches, the decode cache
and compiled code read the view as before and change not at all.
Uncached accesses bypass the caches (NEC VR10000 user's manual p. 75)
and keep seeing memory, as do other masters, since the views are not
coherent. A tag that names another line, or none, ends the line, and the
page goes with its last line. Index Store Data into a held line writes
through. The tags stay the only line state, plus a held bit.

The O2's R5000 POST keeps its stack the same way, in primary data lines
for page 0 made valid by Index Store Tag (D) with TagLo 0xc0, and tests
page 0 uncached meanwhile. An R5000 primary data tag has the R10000's
PTag and PState fields (NEC VR5000 user's manual U11761EJ6V0UM p. 133),
and a store hit on a write-back line makes no bus request (p. 243), so
a valid one holds its page too. The R5000 has no Index Store Data, so
the page starts as memory. Its instruction lines hold nothing: with no
Index Store Data to fill one, it could hold only what memory holds.

Every line the shipped PROMs stage ends by Index Store Tag, none is
written back, and none is read back with Index Load Data while it holds
a page, so the writeback and invalidate operations stay as they were.
The R12000 zeroing a primary data tag at Index Writeback Invalidate ends
a line like any other tag change.

A secondary doubleword puts TagLo at the lower address. The manuals fix
only that TagHi is the doubleword's high word and TagLo its low word
(MIPS R10000 user's manual p. 201, NEC VR10000 user's manual p. 289);
which address each pairs with is inferred, not established, from shipped
PROMs. PROM 6.210 on the Fuel and the Tezro stages code with the word at
A in TagLo and the word at A + 4 in TagHi, 990 pairs in a Tezro boot,
and its staged code equals its image only this way. The IP35 boot-flash
PROM's staging pairs them the same way: simulated with the other order,
378 of its 562 staged fetches break, every trampoline fetch among them.
With the other order the Tezro's PROM falls back to running its cache
test uncached, which took twice as long to reach the BASEIO monitor.

Limits: a page, not a line, is private, so the rest of a held page shows
memory as it was when the page was taken, and nothing is coherent with
other CPUs or DMA. A snapshot taken while lines hold pages is refused at
restore. Lines that name no memory, like the Octane's tag test patterns,
hold nothing. `l2 probe 0` lists the pages held.
Upstream iris has no machine that needs this yet. The Fuel's and the
Tezro's PROM 6.210 copies itself to memory from code it stages in
primary instruction lines with Index Store Tag and Index Store Data (I)
and runs through cached xkphys, and once memory is configured, memory
at those addresses holds something else. Without the lines the PROM
hangs on iris at "Copying PROM code to memory ..." (measured on the
model).

A valid primary instruction line now holds its page in the CPU's view
like a data line, with each word written there as the instruction it
predecodes. A primary instruction word is 36 bits of predecoded
instruction (NEC VR10000 user's manual p. 288, established). Neither
manual gives the layout, so it is inferred from the words PROM 6.210
stores, and only its eleven sampled classes decode. A word of any other
class leaves memory's word in the page, where hardware would run it.
Index Store Data (I) into a held line writes through.

The PROM gets the predecoded form of other code from the hardware: it
runs a secondary line it staged, which refills a primary line, and
reads the words back with Index Load Data (I) to stage them itself
(measured on the model). The model does not refill on a fetch, so Index
Load Data (I) in a set a held secondary line maps to now answers with
that line's words predecoded, as if it had been refilled, until Index
Store Data (I) writes the set. A guest's own pattern in the set reads
back as written (MIPS R10000 user's manual pp. 201-202). An instruction
outside the eleven classes comes back in a form only this model reads,
bits 35:32 set over the instruction, which takes predecode classes
0x78-0x7f.

Nothing on a fetch, load or store, and nothing in the decode cache,
changes. A page held only for code also answers cached loads with the
code's bytes, where hardware would read memory.
The O2 kept POST's stack in bank 0 and, while a primary data tag for
page 0 stood, pointed the decode table at a page of its own for the
uncached path, switched by a hook on every Index Store Tag (D). The
CPU's cached view holds those lines now, with the bank, the decode
table and the rendering engine on memory meanwhile, so the side page,
its hook in the shadow cache, the slot-1 mapping it needed at reset
and MipsCache::set_pd_tag_store_hook go.

A snapshot taken during POST by an older iris carries the side page but
no cache lines to rebuild the stack from, and is refused.
Fuel uses two independent SCSI buses behind one ISP12160 function. Reuse
the ISP1040 command engine and let the part trait own the firmware,
register and channel differences.

A bus reset reaches every attached target as unit attention rather than
a media change. Keep the reset attention on the shared target interface
so a controller can notify each device through the same bus. The
ISP12160 is its only caller: the WD33C93A, AIC-7880 and ISP1040 models do
not call it, so their targets behave as before. A bus reset restores each
disk's write cache bit per channel: a reset of one channel leaves the other's
disks alone, and a chip or soft reset restores both.

The engine changes reach the Octane's ISP1040: mailbox commands now run
with dispatch held, and the target interface gains the bus reset. The
Octane's IRIX 6.5.30 SCSI disk (Octane2, R10000, 256 MiB, V12, jitv2,
release build, headless, NAT) boots to the login prompt and runs commands
on this branch's tip and on main c6dea8c: login after 126 s on the
branch and 109 s on main in one run each (host load makes the times
indicative only), the same `hinv` listing of both integral controllers
and the disk, and the same command output.
The Fuel's and Tezro's PROMs keep their stack in primary data lines for
0x100000-0x107fff and run code from primary instruction lines at the
PROM's own addresses before they have configured memory. The cached
view refuses a page with no memory behind it, so those lines would hold
nothing and the PROM would read the bus.

allow_unbacked_lines names ranges where a page may be held with nothing
mapped behind it. Such a page starts as what the cache's bus shows
there, and its region is the view's while it stands, as for any held
page. A remap keeps the page, so memory arriving mid-phase does not
lose the stack.

jitv2 stays off a held page by the mark ppmem puts on the page's
generation counter, but only on ppmem's own counters. The IP35 boards'
compiles check other ones: Bedrock's for RAM, and for the reset PROM
that the bus maps over memory at 0x1fc00000 the flash's, which is
neither ppmem's nor Bedrock's. A page held there carried no mark, so jitv2
could compile the image's bytes for a page whose lines hold other words,
as a PROM that stages code in instruction lines at its own address makes
it. The cache now puts the
same mark on whatever counter its bus gives the page when that is not
ppmem's, and takes it off with the page's last line. That runs at CACHE
instructions only; loads, stores and fetches are unchanged.

Test branch only.
The Fuel's PROM keeps its stack in primary data cache lines for
0x100000-0x107fff before it has configured any memory, so those lines
have nothing behind them when they are made valid. The cached view that
the CPU reads memory through can hold such lines only in places declared
for it.

Declare all four bank slots as places lines may be held before memory is
mapped. Without the declaration the PROM's first stack access takes a
data bus error.
@jamesbraid
jamesbraid force-pushed the fuel-ip35 branch 5 times, most recently from 4a67fd2 to 1c16d9c Compare November 9, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant