Skip to content

tezro: add the SGI Tezro, with the V12 and up to four processors - #409

Draft
jamesbraid wants to merge 67 commits into
techomancer:mainfrom
jamesbraid:tezro
Draft

jamesbraid wants to merge 67 commits into
techomancer:mainfrom
jamesbraid:tezro

Conversation

@jamesbraid

@jamesbraid jamesbraid commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Sits on #368 (Fuel), which sits on #448 and #422.

Iris runs the Octane and the Fuel. I wanted it to run the Fuel's big brother, the Tezro, so this adds it.

It's a port of my QEMU work. jamesbraid/qemu-sgi-origami already runs the Tezro with up to four processors. Each ported file says which QEMU file it came from. The Tezro shares the Fuel's Bedrock node, so most of it reuses the Fuel's code. You get one, two or four R16000s, 4 GiB by default, the IO9 devices (serial, clock, PS/2, SCSI, gigabit Ethernet) and an optional V12. IRIX 6.5.30 boots to the login prompt on every processor count.

01-hinv 02-gltest 04-quake-over-hinv

(The screenshots are from earlier heads of this branch.)

What it adds

  • Four R16000 parts: 700 MHz rev 2.1, 800 MHz rev 2.2, and 900 MHz and 1 GHz rev 3.0, with 4, 4, 8 and 16 MB of secondary cache. A bare cpu = "r16000", or no cpu key, selects the 700 MHz part.
  • The Tezro's interface board: a PIC PCI host bridge, a PXBOW crossbar widget behind it, the IOC4, a DS1744 timekeeper and a Broadcom BCM5701 gigabit controller. The two- and four-processor board adds a second PIC at widget 11.
  • The L1 answers the Tezro PROM's port-speed query and the router brick request.
  • [machine] cpus takes 1, 2 or 4, with 4 the default for a Tezro.
  • Each fitted processor runs on its own host thread. The IP35 PROM expects them to leave reset in step and they don't, so four holds keep an early processor at the PROM rendezvous until the others catch up. Each is labelled an approximation and has a test.
  • A fresh two-processor flash carries the DisableB and DisableD records. Without them the PROM power-cycles the module six times over the empty positions.
  • board = "v12" fits a VPro V12 at widget 12, behind the PXBOW.

Worth a close look

Shared code:

  • The crossbar. PXBOW is the existing Crossbow model with part 0xd100 and manufacturer 0, not a second copy. It connects through main's XtalkHost; an interrupt whose target is not the host goes to the PXBOW packet sink, and the PIC forwards it to its peer. PROM 6.210's crossbar check writes the PXBOW's read-only error address registers, which iris turned into a Data Bus Error; the PXBOW now completes those writes, since an uncached store can't take an error response (NEC U10278EJ4V0UM p.104, table 6-1). A standalone Crossbow, such as the Octane's, still refuses them, and a test covers it.
  • R16000 parts beside R12000_270. The secondary cache size is a parameter of the shadow cache type, picked once from the part at machine construction; nothing is read or branched on at run time. The R16000 shares the R10000 family's programming model and the R12000's FIR, as a model choice: no R16000 manual was available.
  • PciFunction::config_write. The BCM5701's indirect register and memory windows are configuration dwords where writing the same value twice is two register writes, but the header only reports a write when the stored value changes. config_write is offered a whole-dword write first; the default declines, so every other function is unchanged.
  • FETCH_ALIAS, R16000 caches only. The IP35 PROM fetches its own code uncached at 0x1fcxxxxx. Once a RAM bank covers that window, the compile pool's cached view answered RAM there, which the PROM's memory test zeroes, so the JIT compiled NOPs in place of PROM routines. An uncached fetch of the reset window now goes to the bus at an address of its own. Ungated, the check cost the R14000 interpreter benchmark about half a percent of its host instructions, so only the R16000 model takes it.
  • SC on IP35 boards. The cached view now passes a compare-and-swap to the memory controller's DMA view. Before, an SC to a bank above 4 GiB or to a checked page became an ordinary store. The Fuel runs one processor and is unchanged.

Approximations the commits state:

  • The reset-window split rests on what PROM 6.210's memory test needs; which space attribute 0 reaches at 0x1fc00000 is not established.
  • The PIC returns a master timeout for a configuration read of an unclaimed slot, because all-ones headers made the PROM record phantom components and, with a second PIC, exceed its 66-component limit. The fault and its enable gating are inferred.
  • The byte order of a 64-bit access to a conventional adapter is not known; it takes word values.
  • Bedrock PI CPU enable is stored only; every fitted slot is granted at reset.
  • The V12 placement on the one-processor board is inferred.
  • The 800 MHz part takes four processors only; a config with 2 is refused with that message.
  • V12 credit accounting is not modelled. The Tezro reaches the desktop without it.

Testing

On main 79bd1ce, on the Fuel branch at 1c16d9c; head f4687f4, 22 commits.

  • Per commit: all 22 pass cargo test --workspace --no-run --features jitv2 and cargo check --bins --tests.
  • Tip, cargo test --lib, release and debug: default features 3433 passed, jitv2 3953 passed. The only failure is exit_persistence_tests::no_process_exit_bypasses_the_flush, which fails on main too. cargo check -p iris-gui is clean.
  • --validate: the default profile is ok; r16000_800 with 4 CPUs is ok and with 2 is refused; r16000_1000 is ok; an unknown part is refused with the list of valid ones.
  • Boots to login, release builds, fresh flash and NVRAM, single runs. Every guest exits by itself on halt -p, holds no cache lines at login, and logs no media errors.
Machine CPUs Engine Seconds to login
Tezro 4 interpreter 216
Tezro 2 interpreter ~229
Tezro 1 interpreter 264
Tezro 2 jitv2 177
Fuel 1 release build 169
Octane 1 release build 156
O2 (default R5000) 1 release build 179
Origin 200 1 release build to login

hinv shows 1, 2 and 4 "700 MHZ IP35 Processors": R16000 rev 2.1, 4 MB secondary cache, 4096 MB of memory.

Networking: the BCM5701 (tg0) resolves ARP, receives with no errors, and a telnet through the port forward returns the IRIX banner. An earlier version of the BCM5701 commit swapped the halves of its host-address register pairs, so the first transmit fetched its send ring from the wrong address and tg0 sent nothing.

IRIX's olperi FPU diagnostic does not pass yet on the Tezro or the Fuel: it depends on a separate fix for the upper word of FP registers after single-precision results.

Not run on this head: an iris-bench cell, the OpenGL tests and ol* diagnostics, boots of the 800 and 900 MHz parts, jitv2 with one processor or the 1 GHz part, macOS and Windows.

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.
The flash seeded its configuration block with the Fuel's values: boot
mode word 0x549aa985, 500 MHz and machine type 2, and the machine kept the
matching CP0 Config as a separate constant. A Tezro needs the word of
its own chip and a Config whose mode bits agree, or the PROM stops in
its error routine before it sets up GP.

The board passes a FirmwareRecord: the PROM's boot-mode word, the reset
mode bits the CPU latches into Config, the clock and the machine type. The
record yields the Config the CPU resets to, so the flash and the CPU
read one source. A reopened image gets the opening board's record and
the block's byte sum is still preserved. The record also carries a
header policy: the Fuel keeps its refreshed, sum-preserving fields,
while the Tezro writes a whole 0x50-byte block when a store is formatted
and keeps a stored block on open, so a flash update the PROM wrote
survives.

There is a record for each Tezro chip, 700, 800, 900 and 1000 MHz. The
first three mode words are rows of the stock IP35 PROM's CPU speed table
(0x589bad85, 0x549baf85 and 0x569cb185). The table stops at 900 MHz, so
the 1 GHz word 0x4aedb385 is built from the formulas the PROM's own
configuration editor uses for the other rows and from the cache clock and
tap its inventories print; it is approximated, and the doc comment says
how each field was derived. The block's layout and its checksum rule are
the editor's too: it sets the compensation byte so the new block sums to
what the block it replaces did, which keeps the whole image summing to
zero. The seed computes that byte instead of carrying a constant that
only reproduced for machine type 0.

The word at +0x30 is a machine class, not a CPU type. The PROM's
start-up check derives its own from the board word's top nibble (0x4
gives 2, 0x8 gives 1, any other 0) and prints "CPU configuration values
mismatch" when the flash disagrees. The speed-table rows carry 2, the
class of the Fuel's 0x4000 board words; the Tezro's board word 0x2001
gives 0, so the Tezro records author 0, as QEMU's Tezro does. Neither a
real Tezro's flash nor its board word has been read, so this keeps the
model consistent rather than reproducing a capture.

A fresh store can be seeded with environment records at given offsets.
The stock PROM treats every empty processor position as a processor that
failed to start and power-cycles the module three times for each before
writing a Disable record for it. The seed builds those records for the
positions a board names, B and D byte for byte as QEMU's two-processor
boot tests write them, so the first boot runs through. That the board
ships so is a model policy, not a recovered factory image. A store that
already exists keeps what it holds, and the Fuel seeds nothing.

Ported from tests/functional/mips64/lib_origin300.py in
jamesbraid/qemu-sgi-origami.
The Tezro tower's PROM asks the L1 for its two port-speed integers and
prints a complaint about the link running at 400 MHz instead of 600 when
the first reads zero. The L1 answered [0, 0] unconditionally. The first
integer selects a PROM display branch rather than naming a speed, so make
it a setting (zero or one, as the model it is ported from allows) with
the default at zero, which keeps the Fuel's reply unchanged.

Add the router command interpreter's brick request for the Chimera brick
types, off by default. It arrives as 0x3001, 0x3002 and 0x3003 chunks on
one of 32 subchannels; each subchannel reassembles its own message and a
new start replaces a pending one. A single-fragment request whose
descriptor fields match the capture is answered with the rack, slot,
partition, brick type name and serial, in 113-byte chunks; a malformed
request, any other interpreter command, a brick type with no name and a
message of more than one fragment are logged and get no reply, because the
descriptor layout for the longer messages is not known. The model's second
UART transport is not present here, so only transport 0 exists. The tests
port the port-speed case and the single-transport router cases from
sn1-l1-test.c, including the Chimera WS identity the Tezro uses.

Ported from hw/misc/sgi-l1.c in jamesbraid/qemu-sgi-origami.
The Tezro's IO9 board carries an IOC4 (PCI revision 0x53) where the Fuel
has an IOC3. Its serial console is one of four byte-wide 16550 ports
behind it, so the PROM and IRIX cannot reach a prompt without it.

The model has the four ports on the shared 16550 core, each with the
serial ring engine the IOC3 uses, a 66.67 or 33.33 MHz UART clock
selected by the board, and the local interrupt words: eight serial bits
per port, a second word for DMA errors, and one PCI pin for both. A
failed ring transfer is captured with its address and requester and halts
the port until a channel reset. The ATA DMA readback word the PROM
diagnostic probes and the PCI status the IP53 policy asks for are latched
only for a board that requests them. The DS1744 clock is mapped at
0x80000 to 0x87fff with the calendar at 0x87ff8: byte accesses only, wider
ones refused and logged, a chip reset leaves it alone, and an IOC4 with no
clock fitted reads zero there. The PS/2 controller sits at 0x200.

The chip reads its 32-bit registers in the processor's byte order. PROM
6.210's serial DMA diagnostic timed out on UART A (diag_rc 75) because its
writes arrived byte-reversed: port 0 DMA control as 0xff010210 and the TX
producer as 0xf8030000 where QEMU's IOC4 on the same PROM records
0x100201ff and 0x3f8. The IO9 PIC carries the processor's bytes in address
order and Iris hands a PCI function its BAR value as those bytes read
little-endian, while the IOC4 reads them big-endian as the QEMU model's
register region does. The UART and clock regions are byte-wide and
unaffected.

What the guest does that the model takes but cannot make sense of is
logged as QEMU logs it: DMA enabled over a misaligned ring base or over
PIO data in the FIFOs, DMA disabled with ring work pending, a pair base
changed while busy, a PIO transmit while DMA is on, index writes outside
the pointer field, receive-timer bits above the reload and control bits
stored without effect. Behavior is unchanged by those. The tests port the
QEMU model's qtests: the ring registers (4 KiB wrap, retained control
bits, index pointer fields, empty-slot entries, timer arm, reload and
expiry), receive-ring-full as a level held while ring DMA is on and an
entry waits, loopback and overrun cases, the idle status sequence, and
the absent clock, calendar window and wide-access refusal. The timer
cases check the same transitions at host-time deadlines, since the QEMU
file steps virtual time.
The Tezro runs on the same Bedrock node as the Fuel: the same processor
views, PIs, MD, L1, boot flash, I/O and local-bus blocks, Hub-special page
and CPU and reset wiring. Only the board inputs differ: memory banks,
processor population, firmware record, board-id word and L1 identity. All
of it lived inside FuelBoard and FuelMachine with the Fuel's values
written in, so a second board would have copied several hundred lines that
must keep agreeing.

Move the node into ip35::node. Node::build takes the board inputs in a
NodeConfig. The Fuel's machine file is renamed ip34_machine.rs after its
board, as the Octane and O2 are named, since ip35_machine.rs is the name of
the software platform the Tezro's IP53 board shares. The widget DMA adapter
that turns a board's bus accesses into Bedrock inbound traffic from a
widget moves in beside it, so a V12 on the Tezro reaches memory as the
Fuel's VPro does. The node also owns the buses a processor reads memory
and code through, and wires a processor to them.

The PI answered as a node with one processor, with slot A of PI0 present
and every other slot absent, and the Fuel machine set CPU 0's enable bit
itself. A Tezro tower can hold one, two or four processors, and the PROM
reads the present, enable and CPU-number registers to find them. The board
now passes a Population: which of the four slots hold a processor, in the
board's order PI0/A, PI1/A, PI0/B, PI1/B, so two processors sit one per
interface. Every fitted slot reads present; only CPU 0 starts, so only its
enable bit is set at power-on and the others are held disabled, which
should lead the PROM to report one enabled processor.

PROM 6.210 asks the L1 for a power cycle (command 0x2006) after printing
"Starting PROM Boot process", to retry the other processor slots at
another PLL setting. The L1 reports it as power down, power up and a
restart, and the node turned the power down into a machine power-off, so
the Tezro stopped at its first line. Only the shutdown is a power-off now
and the restart is a reset, as in QEMU.
The IOC4 has the same keyboard and mouse controller at another place in
its BAR, and QEMU shares one model between the two chips (sgi-ioc3-kbc).
Here it lived inside ioc3.rs as part of the chip's state and could not be
reused. Move the register view, the per-port holdings, the clamp rules and
the overflow flag to ioc3_kbc.rs. Each call that can bring in a character
now says so and the chip latches its own source bit (the IOC3's status bit
22; the IOC4's bit 6 of its other word). The host-input line that pokes
the chip moves with it, as KbcOwner and KbcLine. The IOC3 behaves as
before and logs what QEMU logs.

The IOC4 maps the controller at 0x200 to 0x210 behind an adapter that
takes aligned 32-bit accesses only, hides the IOC3-only receive flags, and
latches bit 6 of the other interrupt word for every character that
arrives, under that word's own enable and write-one acknowledge. Every IO9
brings the keyboard and mouse out; without it the PROM and the OS find no
keyboard and the console falls back to the serial line.
The Tezro's IO9 carries a 32 KiB timekeeping SRAM with a BCD calendar in
its last eight bytes. The Fuel's M48T35 model has the same geometry and
clock offsets but not the same registers: run against the DS1744 contract
it reads zero from the control register where the part shows the century,
0x40 where the part shows 0x60 after a read request, has no weekday, and
takes a month of 0x13 as a wrapped date where the part refuses the commit.
That is a different calendar, so the DS1744 is its own model rather than a
flag on the Timekeeper.

The control register carries the century beside the write and read bits,
the weekday advances with the date, the oscillator-stop bit rides in the
seconds, a commit that is not a date is refused, and test mode is
modelled. The clock is ported from QEMU's ds1742 and the two have to keep
in step, so the tests replay the QEMU model's register sequences as one
table.
The Tezros whose inventories have been seen are two- and four-processor
machines, and their interface board differs from the one-processor one.
The tower guide's slot layout gives it four PCI buses (its overview says
three) and a second XIO connector, which the QEMU composition models as a
second PIC at widget 11 and a V12 slot at widget 12.

Accept 1, 2 or 4 in [machine] cpus, with 4 the default for a Tezro: the
Tezro used to read its count from [tezro] cpus while the Octane reads it
from [machine] cpus, two keys for one setting. Unset, the key is the
profile's default (four on a Tezro, one elsewhere), and it is written back
only when set so a saved config keeps following its profile's default.
Two or four processors fit the second PIC and number the PICs' PI level
sources in QEMU's host order. The second PIC is wired to Bedrock IO at
widget 11 with nothing on its buses, and its empty devices fail as master
timeouts, as on the IO9 PIC.
The Tezro takes an optional VPro V12, but the board fitted no graphics,
so [graphics] could only be none.

Add board = "v12", on the VPro model the Octane and the Fuel already use.
The V12 is attached at widget 12 behind the PXBOW of the PIC that owns
widget 0, and Bedrock IO reaches it through that route with widget
offsets, as the Fuel's graphics slot does. Each PI keeps its graphics
flow-control registers, stored as written as on the Fuel. The board's
interrupt packets reach Bedrock IO as the PIC's do, and its DMA enters
Bedrock as widget 12's inbound traffic.
On the two- and four-processor board widget 12 is QEMU's graphics slot.
On the one-processor board the tower guide gives one XIO connector for a
VPro board but names neither its widget nor its route, so the same
placement there is inferred, and the code says so.

PROM 6.210 and the IP35 kernel learn which XIO widgets exist only from
widget 0's crossbar: link n is usable when its status word has bit 31
(alive) and its auxiliary status has bit 5 (present). The second PIC was
wired straight to Bedrock IO as widget 11, so the PXBOW reported link 11
absent; the PROM never addressed it and IRIX listed only widgets 12 and
15. It is now an endpoint of the IO9 PIC's PXBOW, beside the hub's link 10
and link 15, reached through the switch.

Register the V12 with the monitor, as the Fuel does, so the `vpro`
commands resolve on a Tezro that has one.
…the board

The Bedrock memory model hard-wired the Fuel's two 256 MiB banks: the
bank size was a constant in the decode, in the ECC, directory and
protection keys, in the self-test and in the processor bus's test for
where RAM can be. A Tezro fits up to six banks of 1024, 1024 and four of
512 MiB, the last two at 4 to 6 GiB, outside ppmem's 4 GiB direct window.

The board now passes the fitted bank sizes and they are checked: at most
eight banks, each a whole number of direct-window regions up to the 1 GiB
the largest size code names. Decode, self-test and the sparse ECC,
directory and protection state read the size of the bank they touch and
key it by where the bank starts, so banks of different sizes never share a
key. The processor bus asks a RamExtent where RAM can answer, allowing for
the bank-select swizzle; generation counters cover every slot that can
hold RAM. Banks above 4 GiB answer through Physical's decode table with no
direct fast path. MemoryPopulation is the typed form a Tezro board uses:
512 or 1024 MiB DIMMs, one per bank, at least 512 MB for one processor and
1 GB for two or four. The default is the 4 GiB layout (1024, 1024 and four
512 MiB), which matches an observed four-processor Tezro; the 1 GiB
population stays constructible, and nothing claims the PROM or IRIX boot
at it. The Fuel passes [256, 256] and behaves as before.

A checked (ECC or directory marked) page no longer rebuilds the whole
memory map. The PROM's memory test marks and lifts a few hundred
doublewords thousands of times, which the QEMU model counted as 6630
placements. A mark now takes one decode-table entry, and the first mark in
a 64 MiB region withdraws that region from the direct bitmap without
unmapping its host pages, so a processor that read the old bitmap an
instant earlier still reaches the bank's bytes. Each CPU's cached view of
memory follows the withdrawal: its bitmap loses the region, its host pages
stay, and a page it holds there is laid from the bank again when released.
A lifted mark changes nothing; lifted pages return to their banks together, when 1024 have piled
up, after a second of controller time, on a zero self-test, or when the
board calls retire_lifted_marks. Per added page the Fuel went from 15
mappings, 3 table rewrites and 393,216 counters to one mapping at most, one
rewrite and 2 counters (32,768 for a region's first mark), and the Tezro
from 95, 7 and 2,621,440 likewise. Whole-map rebuilds still scrub, so they
run inside a guard the board supplies that parks the other processors.

Bulk writes (reset_memory, load_bank, restore_bank) advance every logical
counter, since code pages follow the logical counters and a bulk write
through Physical bumped only the banks' own. Tests port the qtest cases
for memory banks, size boundaries, bank select, directory, self-test and
lifted marks (ecc-lifted-mark-keeps-overlay, ecc-lifted-mark-over-marked-
line), and cover cached and uncached stores of every width to banks above
4 GiB, a store after a bank swap, and the reset PROM at 0x1fc00000 inside
fitted RAM.
The Bedrock network-interface port status word read 0 on a node with no
cable. The IRIX 6.5.30 bus-error handler starts with a network-interface
check that prints "Detected a link reset due to other partition reset"
when reset enable is 0 and port status bits 1:0 are 0, so every faulting
widget probe printed it.

Read bits 1:0 = 1 instead: link up (bit 1) clear and bit 0 set. The value
is inferred, not measured: bit 1 follows a real Tezro PROM reporting its
NUMAlink down, and bit 0 follows a real one-node Fuel taking a PIO read
bus error with no link message. Nothing is recorded in the
processor-interface error records. The offset-0 register is renamed from
NODE_ID to PORT_STATUS, which is what it reports. This block is shared
with the Fuel board, so QEMU needs the matching change.
…e and timers between IOC chips

The IOC4 puts the same ring-DMA engine in front of its four 16550s that
the IOC3 has. Copying it would leave two walks and two copies of the
timer code to keep in step, and the two chips are meant to track the QEMU
models, so the engine moves out of ioc3.rs into ioc_serial_dma.rs under
QEMU's names (SerialDma and friends, sgi-ioc-serial-dma there) and
reaches the chip around it through a host trait: the 16550s, the
interrupt bits, the bus master and the error report. The IOC3 keeps its
behavior; two parameters cover what the chips do differently, and a chip
can halt a port on a failed transfer until a channel reset and receive
the failed address. The host-time timer slots (one pending one-shot per
deadline kind, replaced when its deadline moves, removed under the lock
that keeps a late arm from outliving a stop) are one DeadlineTimers both
chips use.

The engine follows QEMU's timing. It writes a character only when the
16550 can take it and resumes when transmit-ready rises; the 16550 holds a
byte its backend refuses, reads busy, retries as the backend drains and
after two seconds gives up and treats the backend as disconnected until it
takes a byte again (SerialBackend::try_send_byte defaults to sending, so
other backends are unchanged). Before, a full socket lost bytes or stalled
the whole emulator. A looped-back character takes its character time on
the line, about a millisecond at the 9600 baud reset rate, and the engine
wakes from a timer if nothing touches the chip meanwhile; the IOC3 ring
tests that read straight after the doorbell wait out the line. Arbiter-idle
(IOC3) and serial-idle (IOC4) read busy only while one of the engine's own
bus transfers is under way. Engine tests drive a stand-in host.
A Bedrock node can hold four processors, and the PI only knew how to run
the one in PI0 slot A. The board now names its running slots beside the
fitted ones. A running slot starts with its CPU-enable bit set, its
interrupts, timers and enable writes are no longer refused, and the
CPU-number register answers the reading processor's own slice, slot in
bit 0 and interface in bit 1, as QEMU's PI does from the issuing
processor. A reader that is no processor reads slot A, QEMU's behavior for
an access with no requester. A fitted slot that does not run is held
disabled; a guest write that enables it is stored and reaches the node
hooks, but no processor starts.

CPU enable stays stored only: QEMU grants every fitted slot at reset and
wires the register to nothing, an approximation since no cold-reset value
is known. Clearing a running slot's bit is recorded as a refusal, and an
NMI is refused unless the node reports it taken, so an NMI to a running
processor, which has no NMI entry, is logged instead of dropped. The PI
therefore records a stop or an NMI it cannot carry out, and the Tezro's
node hooks need no refusal counters of their own.

The graphics slot stops below its bias, as the Bedrock does: the store
that crosses below raises the interrupt and the slot's next counted store
waits until the processor takes the interrupt, software writes the
counter, bias or interrupt count, or returned credits lift the counter
above the bias. QEMU follows the SN1 header's credit-counter comment here;
its Hub only raises the interrupt once, so a writer the board cannot keep
up with drove the counter without bound.

Fuel and the one-processor Tezro run PI0/A alone. Tests follow
sn1-cpu-identity-test four-processors-know-their-slots and
sn1-bedrock-pi-test cpu-number-fallback, cpu-enable-lines and nmi.

Ported from hw/misc/sgi-bedrock-pi.c in jamesbraid/qemu-sgi-origami.
The two- and four-processor boards fitted their processors but ran only
CPU 0. Each fitted slot now gets its own R16000, numbered in the board's
slot order (CPU 1 in PI1 slot A, CPUs 2 and 3 in the B slots), all
starting together from reset as in QEMU's IP35 node. Each has its CPU
number, its share of one compile pool and page set, its direct window
bitmaps, its slot's interrupt lines from its own PI, its own status bar
counters and a place in the monitor. The board runs the PROM's boot
rendezvous across them, fed from the LB scratch registers, II scratch 0
and each PI's real-time registers.

A fresh flash carries a Disable environment record for each empty
position, B and D on the two-processor board and B, C and D on the
one-processor board, so the PROM's first boot goes straight through
instead of power-cycling the module three times per empty position to
give up on them. On the one-processor board that recovery was nine power
cycles, about 30 s of every boot from a fresh flash. The four-processor
board has no empty position and seeds nothing. A flash file that already
exists keeps what it holds. The node passes the board's records to its
flash; the Fuel passes none.

Board composition ported from hw/mips/sgi/sn1-node.c in
jamesbraid/qemu-sgi-origami.
…the guide

The one-node network reads and PROM 6.210's crossbar check were answered as
the PROM expects but no test ran them on the Tezro board. Add them, and a
test that flushed copy-on-write overlay writes survive a power-off.

The guide now describes the second PIC on link 11 of the IO9 PIC's PXBOW,
the PROM disabling every processor but CPU 0, the R16000 policies left out,
and the limits of the machine.
The IP35 PROM brings a node's processors up assuming they left reset on
one clock edge and have run in step since: four of its early handshakes
order one processor's stores before another's reads only because both run
at hardware speed. Each processor here is a host thread that can fall far
behind, as under QEMU's multi-threaded TCG, so a processor that reaches
the rendezvous early is held until the others catch up. This ports QEMU's
four holds: the reset barrier on LB scratch 1, the pair liveness post in
the processor's own real-time compare register, the presence publisher at
its release of the hub lock, and the partner at its real-time counter
read. The hold has its own reason in the processor control so that its
release does not end a world stop or HEART's disable, and the monitor can
tell the holds apart. The LB takes optional hooks for scratch 1 and 3 and
Bedrock IO reports each store to scratch 0 once it has landed, with every
hook running with the block's lock dropped; nothing connects them on the
Fuel.

A processor on PI1 reads its CPU number and programs its own interrupts
through the XIO local register form at offset 0, as QEMU's IP35 node
overlays PI1 there for every processor sitting on it (OpenBSD expects the
local form to select the requester's interface). That the chip answers PI1
there is an approximation pending a hardware read. A processor running
with others makes SC atomic through its cache's compare-and-swap, which
the shadow cache does itself for RAM in its direct window and hands
anything else to the bus; on the IP35 boards that bus had no swap, so an
SC to a bank above 4 GiB or to a checked page became an ordinary store.
The cached view now passes the swap to MD's DMA view, which swaps in the
bank and advances the page's logical counter. A node restart from the LB
soft reset, the NI local reset or an L1 event is logged. Fuel is unchanged.

Ported from hw/misc/sgi-bedrock.c, hw/misc/sgi-bedrock-lb.c,
hw/misc/sgi-bedrock-io.c and hw/mips/sgi/sn1-node.c in
jamesbraid/qemu-sgi-origami.
None of iris's PCI functions has an expansion ROM, but dword 12 of the
header stored whatever was written to it. A size probe of all ones then
read back as a 2 KiB ROM, and IRIX 6.5.30 on the Octane assigns it an
address and warns "pciba: ROM is 0x...". A device without a ROM leaves the
register unimplemented, and an unimplemented register reads zero and
drops writes (PCI Local Bus Specification 2.2, sections 6.1 and 6.2.5.2),
which is how firmware learns there is no ROM.
When a guest never finds or never programs a function, the question is
which configuration cycles it sent and which of them anything answered.
The bus is where every host's cycles meet, so log them there: bus,
device, function, register, width, the value, and "no answer" when no
function sits at the address. The check is the devlog module's enabled
flag, and configuration cycles are firmware and probe traffic, never a
fast path.
A driver that gives up between two register accesses on the Octane leaves
no trace of what it did in between: it may have programmed the Bridge's
direct map or translation table, connected an interrupt through the
Bridge and HEART, or tried a DMA that went nowhere. Two log modules show
that. bridge logs register reads and writes by name, translation-table
writes, a function's transfer that reached no memory (failures only, so
successful DMA costs nothing), and each interrupt pin's edges and the
HEART vector it sets or clears. heart logs processor-interface and widget
register accesses and every interrupt vector raised or lowered. The
kernel reads the counter too often to log every read, so heart logs one
in 256 with the host clock beside it, which shows the counter's rate.

Each check is the module's enabled flag on a register access or an
interrupt edge, not on memory or instruction paths.
A bus behind a bridge needs three things the root bus does not: its
functions' windows reach the CPU only through the bridge's forwarding
windows, their interrupts and DMA go through the bridge, and firmware
numbers the bus. PciToPciBridge is a function with a type 1 header (new
PciConfig::type1: bus numbers, I/O, memory and prefetchable window
registers at their real granularity, no BARs, zero at reset) that owns a
secondary PciBus. The caller names its PCI identity; the header is the
only part of a bridge chip modelled, and the upper halves of the
prefetchable and I/O windows (dwords 10 to 12) read zero, since the
lower registers advertise 32-bit prefetchable memory and 16-bit I/O.
The type 1 header's expansion ROM base (dword 14) reads zero, as dword
12 does in a type 0 header: no bridge has a ROM.

The secondary has no host and no outputs. A pin of device d behind the
bridge reaches pin (pin + d) mod 4 of the bridge's device; its DMA is
the bridge's, so a function behind a bridge masters as the bridge's root
slot. The bus in front merges the BAR regions of the functions behind
its bridges whose range lies wholly inside an enabled window (a BAR
straddling a window's edge is not forwarded; hardware leaves that
undefined), into the same table and the same mappings, inheriting the
host window the mapping lies in. A configuration write updates the
mappings when it changes the command register, a BAR, or a bridge's bus
numbers or windows (dwords 1 and 4 to 9), and a change behind a bridge
updates the buses in front of it. Parents hold the bridge function,
which holds its bus, and the bus holds its parent weakly. find_bus(n)
finds the bus a guest bus number reaches from the bridges' secondary and
subordinate numbers, and MACE's CONFIG_ADDR now sends a non-zero bus
field there.

Raising bridge control bit 6 resets the buses behind the bridge and
nothing else, through a new PciFunction::config_written hook that the
bus calls when a configuration write changes a dword. The hook sees the
write, not the register after the header reset, which clears bit 6. RST#
on the primary bus reaches behind the bridge too, as in PCI, so a cold
reset resets the functions behind a bridge. Saving a bus saves the
headers on the buses behind its bridges.

A configuration cycle that reaches the bus behind a bridge and finds no
function ends in master abort at the bridge: a read returns all ones
(masked to its width), a write is dropped, and the bridge's secondary
status records received master abort, write one to clear. Only the root
bus leaves an unclaimed cycle to its host. Master-Abort Mode is not
modelled; it resets to clear, which is the all-ones behaviour.

Nothing machine-level builds a PciToPciBridge yet; it is reachable
through the tests and becomes a device type with the device registry.
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.
@techomancer

Copy link
Copy Markdown
Owner

On the "JIT refuses memory above 4 GiB" point in the description: main already handles RAM past 4 GiB in jitv2, so this looks like a property of the Bedrock memory layout in this branch rather than a JIT limit. The PR's fork point includes the Octane 8 GB work (73b589c, b8fca3a, 2026-10-31), where:

  • The ppmem window is per machine. PpMemSpace::over_window / Physical::with_window take a window size, and ppmem::window_shift gives the region size for the one-u64 bitmap. HeartRam::try_new picks the 16 GB window (256 MB per bit) when RAM base + fitted memory passes 4 GB; everything else keeps 4 GB and shift 26. The shift reaches the shadow cache and decode cache through set_tcache_shift, and the JIT bakes it in from JitDcGeometry::window_shift (it is in the persistent cache fingerprint).
  • The PCP pool is keyed by physical frame with a u32 PFN (28-bit frame for a 40-bit address). PfnMap is sized from the machine's MemoryMap (frames_for), and pfn_of returns None above it, so a fetch past the map runs in the interpreter instead of panicking. Page bases and the compile snapshot's bus reads are u64.
  • The inline load/store gate (emit_tc_mapped) bounds the region index to < 64 before testing the bitmap, so a page past the window cannot wrap onto a low region; it takes the helper path through the 64-bit bus.
  • SC to RAM outside the window goes through the bus-level cas32/cas64, so SMP atomics in high memory stay atomic (test: compare_and_swap_works_above_the_window in heart_mem.rs).

Notes: clanker/devices/ip30-memory-past-4gb-takes-a-16gb-window.md and clanker/cpu/mp-llsc-is-a-host-compare-exchange.md.

Your own 69261e9 already says a jitv2 build no longer needs memory below 4 GiB, so the PR body is behind the branch there. Two requests:

  1. Update the description so it does not read as a jitv2 limitation (the 1024x4 banks workaround and "its uncached code views can't address it").
  2. If you want the Tezro's banks at 4 to 6 GiB on the fast path, let bedrock_mem ask for the 16 GB window the way HeartRam does instead of sending those banks through the decode table only. Your 512 MiB banks are already whole 256 MB regions, and withdrawing a region on the first checked-page mark works the same at either window size. Not a blocker for the PR, but it is the difference between those banks reaching the JIT's inline path or not.

@jamesbraid

Copy link
Copy Markdown
Collaborator Author

ty, will rework it, this PR started out before the work you did as well for >4GB

The Tezro ships with R16000s at 700, 800, 900 and 1000 MHz, and the part
table carried no R16000. Add the family and one Part per shipped clock:
r16000_700 (rev 2.1, which the _EARLY const marks so the Fuel's later
rev 2.2 700 MHz part keeps the plain name), r16000_800 (2.2), r16000_900
and r16000_1000 (both 3.0), with 4, 4, 8 and 16 MB of secondary cache.

hinv prints an R16000's major revision two below the PRId register's
major, as the Linux and OpenBSD CPU probes do. Part::prid undoes that in
one family match, so 2.1, 2.2 and 3.0 read 0x0f41, 0x0f42 and 0x0f50.

The secondary-cache size is a parameter of the shadow cache type, so the
executor, the translator and the cache know it when they are built.
The three sizes are three aliases of one shadow cache (Config SS codes
3, 4 and 5), and with_cpu! picks the alias from the part's l2_kb once at
machine construction, so nothing is read or branched on at run time. The
R16000 shares the R10000 family's programming model, as a model choice,
since no manual for it was retained; its FIR is the R12000's 0x900 for
the same reason.

No machine lists these parts yet. Tests pin the PRIds, run each alias
through the interpreter, the Config, TLB-write and LWR checks and the
jitv2 equivalence check, and pin the type with_cpu! builds per part.

The cached-view line tests run on the three R16000 types too: a primary
data line and a secondary line hold their page in the CPU view, Index
Store Data fills it, and dropping the lines ends it.
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.
Tezro's interface board puts its PCI buses behind the PIC, not the
XBridge Fuel uses. One chip fills one XIO widget with two heads, at offset
0 and 0x800000, and the two- and four-processor board fits a second PIC at
widget 11 beside the one at widget 15, so a Pic is a self-contained device
the machine builds once per widget.

Each head owns a shared PciBus and is its PciHost, as the XBridge heads
are: it supplies a slot's DMA and takes the level of its eight interrupt
inputs, device d's INTA reaching input d - 1, and it publishes its CPU
windows (the four short device-I/O windows and the 1 GiB PCI memory window)
so a whole BAR page is served from the head's own table. The model is the
revision 3 profile the QEMU model selects for Tezro: identity with bit 0
set, the control, Device and DIR_MAP masks, the CPU word view of the
eight-byte registers, direct-map and direct64 DMA, and the invalid address
interrupt. Translated DMA and PCI-X errors stay unsupported and
observable. PCI slots are hooks only; the machine attaches its functions.

A conventional adapter sees a numeric value at the processor's address with
the low two bits corrected, as on MACE; a slot marked self-ordered (IOC4)
gets the processor's byte sequence. Which order the silicon gives a 64-bit
access to a conventional adapter is not known; it takes word values. A
configuration read of an unclaimed slot fails as a master timeout instead
of returning all ones: PROM 6.210 reads interrupt status bit 11 after each
probe, and an all-ones header recorded two phantom components per empty
device, enough with a second PIC to exceed the PROM's 66-component limit
and lose its CPU records. The error view at 0x170 records the timeout, the
status only while enable bit 11 is set, and the error interrupt follows.
That fault and its enable gating are inferred.

The shared bus decides which of two functions that decode the same range
answers, as a probing PROM leaves them: the lower device. Real silicon with
two matching comparators is undefined, and the QEMU model's newest-wins rule
is not carried over.

XBridge's whole-page BAR table is shared with the PIC.
Behind the PIC that carries IO9, widget 0 is a PXBOW and the processor
reaches the other widgets of the board through it. The Crossbow model
already has the register map, so PXBOW is that model with part 0xd100 and
manufacturer 0 instead of a second copy. It selects revision 5 behavior,
which adds the reset fences and the resource lock the Octane's revision 4
leaves reserved, but reports revision 3 in its identification word
(0x3d100001), as the Tezro, Origin 350 and Onyx 4 PROM inventories print
it; part and manufacturer remain inferred, and a 2009 OpenBSD dmesg that
names the part Bedrock is an open conflict. The Crossbow also reports a
source widget in its status reads and sends its own interrupt to a packet
sink when the target is not HEART; PXBOW's target is the host, so the PIC
forwards it to its peer. Pxbow::route gives the machine a widget as the
processor sees it through the switch.

Link resets drop the link for the two core cycles QEMU models (20 ns on
the machine clock, ended by the next register read or widget query) and a
warm reset takes the first reset in 1024 core cycles and ignores its
echoes, each destination's fence deciding whether it admits the source.
The Octane, with no clock, restores at once as before.

PROM 6.210's crossbar check writes 0xdeadbeef and 0 to the PXBOW's
read-only error address registers. Iris turned the refusal into a Data Bus
Error and the PROM reported an exception in its xbow_sanity diagnostic.
The Crossbow specification makes such a write an error condition reported
in widget 0's status bit 5 and its interrupt, and an uncached store cannot
be answered with an error response (the R10000 accepts no completion
response for it, NEC U10278EJ4V0UM p.104, table 6-1), so the PXBOW
completes both writes, as QEMU's complete-posted-error-address-writes
policy does. A standalone Crossbow, such as the Octane's, keeps refusing
both; a test covers the upper register and both revisions.
A NIC's indirect register and memory windows are configuration dwords the
chip computes: a write to the data dword is a register write, and writing
the same value twice is two register writes. The header cannot do that,
because it stores a value and tells a function about it only when the stored
value changed (config_written), and the window dwords have no writable bits
to change.

config_write is offered a whole-dword write before the header. A function
that takes it returns true and the header stores nothing; the default leaves
every other function as it was. Narrower writes keep the header's path. The
read side already works: a function overrides config_dword.
The Tezro's IO9 board carries a Broadcom BCM5701 gigabit Ethernet
controller (PCI 14e4:1645) next to its IOC4 and SCSI controller, and
nothing in Iris implemented one. The PROM and the IRIX driver both bring
the chip up through its registers and then move frames through its rings.

BAR0 is 64 KiB: the register file, whose first 256 bytes alias the PCI
configuration registers, and a window onto 32 KiB of on-chip SRAM. A
driver reaches the same state three ways: the BAR, an indirect register
window and an indirect memory window in configuration space. The last two
are registers the chip computes, which a plain configuration header cannot:
the function answers them from config_dword and config_write, so every bus
that attaches it works the same. A slot that drives REQ64# is part of the
board's config; the chip samples it as reset ends. The model has the chip id in MISC_CTL, the global reset with the
bootcode's firmware-ready handshake, the self-clearing mode resets, the
buffer manager's delayed enable, the station address the bootcode leaves in
SRAM and filter slot 0, the interrupt mailbox and global mask with
PCISTATE.INTR_NOT_ACTIVE, the MII management path to the copper PHY, and
the serial EEPROM controller over a backing the caller supplies. Nothing
synthesises an EEPROM image: no complete board image is established.
Anything unmodelled (a write to an unmodelled register, an undefined MII
opcode, direct EEPROM pin control, a BAR access of a width the chip
refuses) is stored or dropped but counted in Bcm5701::refused and logged
on first sight.

The send ring is walked from the chip's consumer to the producer the
driver writes to the doorbell mailbox. A frame's descriptors are gathered,
source-address replacement and checksum offload are applied if asked, and
a large TCP buffer with a segment size is cut in software. A doorbell that
ends inside a frame, or a descriptor or buffer the bus cannot reach, rolls
back to the start of that frame so a later doorbell retries it. The
standard, jumbo and return rings carry received frames: the ring is chosen
by size, a frame with no posted buffer is discarded and counted when the
other ring could still take traffic and otherwise left at the head of the
backend's queue. The status block and coalescing follow the register
contract: update criteria are a descriptor count or a microsecond budget
per direction, an expired budget reports the next event at once, zero is
off, and an update the bus refused is retried. A completion is held for a
200 microsecond minimum arrival latency so a reply never overtakes the
transmit that provoked it. Descriptors, the status block and frame data go
through the chip's byte-swap controls as lane permutations; the bridge's
own byte order sits behind PciDma. Frames use the shared network link as
the IOC3 Ethernet does.

Timers fire at their own deadlines, earliest first with the clock set to
each one's deadline, so a timer that re-arms another counts from its
expiry. The PHY settles after 100 ms and the buffer manager enables after
8 ms. A chip or machine reset purges the frames the backend still holds,
so one queued before the reset cannot land in rings set up after it.

The tests port the plain-memory cases from bcm570x-test.c: registers,
descriptors, status block, coalescing, interrupts, receive, the receive
queue reset and the checksum report for every malformed header. Cases
needing virtio-net headers, QEMU's user-mode network, a TAP, the dual-port
BCM5704 or migration have no counterpart; the bridge byte-order cases
belong with the PIC.

Ported from hw/net/bcm570x.c and hw/net/bcm570x_phy.c in
jamesbraid/qemu-sgi-origami.
@jamesbraid
jamesbraid force-pushed the tezro branch 3 times, most recently from 934952b to f4687f4 Compare November 10, 2026 01:13
At every start the IP35 PROM checks the configuration block in its boot
flash against the node board's CPU configuration. It reads that from the
board area of the L1's component 0 record. When the area carries none,
it checks against a row of its own table chosen by the Bedrock board
word. A block that matches neither draws "CPU configuration values
mismatch ... reflash the PROM image" before the maintenance menu. The
Tezro's board word 0x2001 picks a 400 MHz row, so its 700 MHz flash
block failed the check on every boot.

Let a board give the L1 a mode word and CPU and Hub clocks. The board
area then takes format 2 and the CPU fields after its identity, in the
layout QEMU's L1 gives the Origin 350 and Tezro boards and the stock
PROM 6.210 accepts. Only Config0 and the two clocks are known, so the
other fields stay empty rather than zero. No factory record has been
read: the bytes are a software input, not a capture. A board that sets
nothing keeps its record byte for byte, so the Fuel is unchanged.
Iris had every Tezro device and the Fuel's Bedrock node but no machine
that put them together. Add the tezro profile, configured by the shared
keys and a [tezro] section, with the board the QEMU one-processor
composition describes.

The node is the Fuel's, given the Tezro's inputs: an R16000 with reset
Config 0x6c9bad85, the Tezro flash record, Bedrock board word 0x2001,
memory from 512 and 1024 MiB banks with the 4 GiB layout as the default,
and an L1 that reports brick type 0x39, revision 1.48.1, with the
flash record's mode word and clocks as the node board's CPU
configuration. Without that configuration the PROM checks the flash
against the 400 MHz table row board word 0x2001 selects and prints "CPU
configuration values mismatch" on every boot. The PIC carries
IO9's IOC4, the ISP12160 SCSI controller and the BCM5701 behind a PXBOW,
with the DS1744 clock and the shared IOC4 console. Graphics is not
attached.

The profile lists the four R16000 chips the Tezro shipped with, the 700
MHz one first, so a bare cpu = "r16000" and a config with no cpu both
select it. A chip the Tezro lacks is refused by the generic message that
names the machine and its chips. The chips differ in what the guest and
the PROM read: revision 2.1 at 700 MHz, 2.2 at 800 MHz and 3.0 at 900 MHz
and 1 GHz, with a secondary cache of 4, 4, 8 and 16 MB. The board keeps
what is its own beside the chip: the flash record whose mode word gives
the Config the processor resets with, and the processor counts the chip
is fitted with (the 700 MHz chip one, two or four; the others four only).
The comment on that table grades each value as established, inferred or
approximated and names the evidence. The cache type comes from with_cpu!,
so the board adds no run-time size field or branch.

The poll on the CPU thread asks the memory controller to return lifted
checked pages to the direct window every 100 ms of board time, so a lifted
page does not keep its 64 MiB region on the slower decode path.
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.

2 participants