Repository navigation
kona: the InfiniteReality pipe's front end and a drawing back end, plus the Onyx2 plan - #454
Draft
jamesbraid wants to merge 190 commits into
Draft
jamesbraid wants to merge 190 commits into
jamesbraid wants to merge 190 commits into
Conversation
The pipe takes 32-bit words on a command page. A word opens a command, or is an argument of the one open, and how many arguments follow depends on a selector table per grammar: the textport after its sync word, the kernel and graphics-process tokens, the GL client's commands and the kernel FIFO's own. An unknown word between commands must be dropped without losing the alignment of the commands after it, or one stray word shifts every later command. Add the framer and the four tables, with a lookup that is a flat array indexed by selector, since every word between commands is looked up.
Host software finds the board by its widget id register, its one-wire record and the configuration block the pipe answers a configuration request with. A wrong word in the block shows up as a board set, a raster-manager count or a channel count the board report and the combination checker then trust. Model an Onyx2 deskside's InfiniteReality2 as QEMU's model does: a GE14-4 geometry board, one or two RM7 raster managers with a 64 MB TM7 each and an eight-channel DG5, with that model's part numbers, names and revisions. ROM serials and barcodes are synthetic and marked as such. The tests lay the deskside's configuration block out independently of the builder and compare it byte for byte, for one and two raster managers.
The graphics install tool programs the pipe's 512 KiB flash by sending command 0x1ec, an operation number and that operation's words, and reads the answer from a reply buffer whose first word must go non-zero last. The tool then reads everything back and compares it, so the flash has to return exactly what was written, including the header word the PROM checks. Add the flash and the six operations, with QEMU's framing: a count above 0x7ffb fails before any data word, an unknown type takes its data and stores none, and writes past the end are dropped. The identify word is the same on every call, as the tool assumes. The contents can be held in a file so they outlast the process as flash outlasts power-off. The tests drive each operation through its words and replies, including the read-back of an image group and of a header.
Most of the registers a processor touches while the pipe works are plain storage or small state machines: the interrupt destination and vectors, the cause register with its write-one-to-clear bits, the mailbox the firmware answers on, the one-wire slot register. They live in one register file behind a short lock that is never held across a call out of the board. The interrupt-enable register holds its twelve bits and reads them back, though software never reads it. The board reaches the machine only through three handles it is given: an interrupt packet sender, an inbound DMA handle and a FIFO-drained signal. Nothing in the module names a Hub, HEART or Bedrock type, so a machine's glue implements them over whatever its fabric is.
Everything that draws or changes display state is behind a typed seam, so a later back end can implement it without touching the front end. The front end hands it Jobs in stream order and gives it a BackPort for the few things it may do: answer on the return page, post a completion reason, queue mailbox replies, complete a retrace and move bytes through the pipe's map RAM or the host's physical memory. NullBackend stands in until one exists and completes retrace requests at once. The state behind the port is the Host Interface Processor's: the pages it answers on, the reason queue (one reason at a time, since a second would overwrite one the driver has not read), contexts with their own return pages, pool swaps, and the map RAM translation. The processor's state is taken out of a slot while someone works on it, so no board lock is held while the machine's DMA, interrupt and FIFO handles are called. The thread spins for the slot, which a processor holds for a few instructions. A monitor or reset sleeps on a condition variable.
The command page's words, the DMA push port, the bracket register, the firmware load port and the map RAM make up the stream the Host Interface Processor runs. Frame the textport, kernel and GL grammars that share the page, set a user command aside when the privileged sender breaks in and give it back whole, and answer what is about the board itself: the return and reason pages, the configuration request, contexts, pool swaps and the EEPROM operation. Everything that draws goes to the Backend as a Job. Accesses meet the stream three ways. A stream store is run in order. A register the driver polls while the pipe works (cause, mailbox, fill level, activity) is served from the register file at once. Any other register reads what the stream writes, so it waits for the stream. Here stores run in the store; the next change moves them behind a thread. The fill-level register counts 32-bit words, rounded up to two. DMA packets and display lists are only framed and consumed so far.
The processor stores commands and goes on; the board decodes them behind it, and a display list one store invokes can run for as long as its words take to decode. Put stream stores into a GFifo and run them in order on one thread standing for the Host Interface Processor. A full ring answers the bus busy instead of spinning on the processor's thread, as the VPro board does since 9ac76ff. A register the stream feeds waits for the ring to drain, and an 8-byte access is all or nothing so a retried store repeats no word. The fill level adds the ring's backlog to the words framers hold, and the published held count lags during a long run. stop runs what the thread had not taken. If the back end panics on the thread, the board is marked dead: the bus answers an error instead of busy forever, and failure() says why.
Host software copies a display list into pipe memory with a list-write record and a DMA packet, then invokes it from pipe memory or from a DMA pool by (offset, word count) ranges. A compiled list holds the words immediate mode writes to the command page, so the invocation feeds them to the same decoder, in order, across the break between ranges. A list that invokes a list fails and the outer one goes on, and offsets count from the region the kernel placed for the current context. A packet's high word with bit 28 clear is a device address shifted right twelve, translated through the map RAM; with bit 28 set it is an absolute host frame. Add the 15 MiB list memory, the packet's shape and tag checks, the completion word its last word asks for, the pool and its pull and move. A packet with no list record goes to the Backend as a DmaIn job. The widget DMA goes to is a field of the widget control register, which Kona::dma_widget exposes.
Record how the board attaches, which accesses wait for the command stream, the slot the processor's state is taken from, and each approximation or open question with its grade, so a back end or a machine does not have to rediscover them.
BackPort works only inside Backend::job, on the Host Interface Processor's thread. The display runs on a thread of its own at the frame rate and has to complete retrace requests and raise the video interrupt, so Backend::attach now hands the back end a DisplayHandle when the board starts. The handle holds the register file weakly. Host becomes its own Arc: the pipe slot owns the back end, so a handle holding the whole board would be a cycle that never drops. With the board gone every call is a no-op. Calls carry an epoch. A reset begins a new one, so a display thread that was inside a retrace completion when the reset happened cannot raise a cause the reset cleared. activity() sets the driver's watchdog latch for the drain loops and the raster thread; the display thread must not call it or the watchdog could never fire. video_frame() raises the video cause with no tag, and logs a missing interrupt destination once instead of every frame.
A back end with a display thread, a raster thread and tile workers has threads the board does not own, and Backend had only job and reset. Add defaulted stop, quiesce and resume. Device::stop calls Backend::stop once the Host Interface Processor's thread has joined and the jobs it had not taken have run, so nothing feeds the back end while it joins. It runs with the pipe slot taken and no lock held, since a thread of the back end may be inside a handle call that sends an interrupt packet. A board that never started does not call it. One whose back end panicked does, so the next start does not attach over live threads. Kona::quiesce lets the ring run out and then quiesces the back end, for a snapshot taken with the processors stopped; resume undoes it.
KonaBackend is the shell the drawing back end grows in. It takes every job as NullBackend does, counts it by kind, completes retrace requests at once, and carries what the pipe's later stages need to know about the board: raster manager count, texture memory per manager, board set and managed area. On firmware start it reports the managed area to the front end. It also holds the model-side bound on a pixel's record, from the host driver's own budget arithmetic: the managed area's pixels (width rounded up to a multiple of 160) divided into a per-board-set budget of words, mapped to the 64, 32, 16, 8 or 4 word class. The guest derives its own class from the raster manager count and the managed size, so the back end never reports one; the bound only lets a later layout that needs more words be refused. Backend::monitor lets the back end answer the kona command, which Kona routes to it: status and counters for now.
A word that opens no command was logged and forgotten. A token whose length the selector tables lack arrives as its own word and the words behind it, all of which the same path ate, so nothing showed which tokens a guest sends that the front end cannot frame, or how much traffic they carry. Hand each such word to the back end as Job::Dropped, in stream order with the commands around it. NullBackend ignores it; KonaBackend counts it with the other jobs. The padding word after a command in a doubleword store is one of these now, which the doubleword test says.
Which token families a stream really carries decides what each build step must learn about their layouts, and the front end alone cannot say: it frames only what its selector tables hold and hands the rest on as dropped words. Tap sits in front of NullBackend and tallies every job: commands by grammar and selector (with the stream position of the first), data runs, bitmaps, video commands, retrace tags, context changes, indirect stores and DMA packets. Dropped words are put down to the command before them, which is how a data run the tables cannot size shows up, and the census is snapshotted where the first run of 64 of them begins, since past that point a data word that equals a selector can open a command nobody sent. It also lists the selectors QEMU's decoder leaves with an unresolved argument layout against what the stream sent. Selector names come from the comments beside the front end's tables, so there is one list to keep. Test-only; the replay driver that feeds it recorded streams follows.
Replaying a stream another model of the board recorded checks the front end and the file format before anything draws. The KONAREC reader takes every record kind the recorder writes and refuses a bad magic, version, tag, count or cut-off record. The replay builds the board from the recording's board record, performs each store and load in order (retrying while the bus is busy, comparing the loads whose answers do not depend on timing), serves the board's DMA reads from the recorded ones and compares what it wrote to guest memory, the geometry-engine interrupts and the retrace and reset markers. Both recordings run through NullBackend and the census tap. The PROM stream is exact end to end. The IRIX stream is exact until the first run of data the front end cannot size (an inline pixel write: only a back end can answer Reply::Data for it), about 20,700 records into the graphics traffic: every compared load and every byte the board wrote match there, and the interrupts match. Past it the stream misframes, as it must, and the test says so instead of claiming a match. The recordings stay out of the tree; KONA_RECORDINGS names the directory and the tests skip without it. The measurements are in clanker/kona/token-census.md.
The drawing back end keeps one array of 32-bit words per word position of a pixel's record, one entry per pixel of the managed area, instead of whole records. A fill that touches one or two words then writes contiguous memory, and a window that uses 13 words of its 16-word class allocates 13 arrays. An array is allocated whole, zero-filled, at a draw's or clear's set-up and published through a fixed per-word AtomicPtr with release ordering, never moved or grown; reset zeroes in place. A kernel resolves its arrays' bases once, and the display can load a pointer once per row run and take null as a run of zeros, so the pixel path checks nothing and a racing display read sees the old or the new value. word_slot is the one definition of the address function, whose physical form is unknown, for cold paths. Tests: nothing allocates until a word is written; a reader racing a first write sees zero or the value; two threads allocating one word share one array; reset keeps the pointer.
From the buffer-configuration command's ten words (level, colour size, index size, double buffer, stereo, sample count, depth, stencil, accumulation and auxiliary buffers) work out where each plane starts in a pixel's record: colour buffers, depth, 8-bit stencil, multisample colour, accumulation, in that order, with the word counts the sheets give per plane and the window offset moving every base together. The layout does not depend on the raster manager count. Configurations the sheets do not cover are refused with a reason instead of guessed: an underlay level, a sample count other than 0, 1, 4 or 8, a depth that does not round up to 16, 24 or 32 bits, a stencil other than 0, 1 or 8, a colour size of 13 to 15 or above 16 bits, an auxiliary buffer, and accumulation on 16-bit colour (whose words depend on a firmware mode value nothing defines). A layout that needs more words than the host's budget gives a pixel is refused too. The sheet's worked check is the first test: RGB10, double buffered, 4 samples, 24-bit depth, 8-bit stencil and accumulation ends at word 12.
A transfer crosses two sets of formats: the host's packed image (RGBA4, bitmap, RGB5A1, R5G6B5, LA8, L16, I16, S16, Z16, RGBA8, RGB10A2, LA16, Z32, RGBA16, RGBA12) and the 64-bit words of the raster bus, picked by a code. The model holds a colour as four 16-bit components and a stencil or depth as an integer of its width; HostFormat converts between those and packed bytes, most significant component first and big-endian, the guest's memory order. Widths round to nearest, narrowing as (v * max + 32767) / 65535, so every value that fits survives a round trip. Only what the sheets give is modelled. Of the bus words they place the fields of RGBA12 alone (code 0), so only it packs; the other codes carry pixels-per-word and the published table, with the five codes that have no module refused. Host RGBA12 is refused: its field placement is not given. Luminance formats take red as the luminance, an approximation. Tests: every value of every component of every colour format round trips; byte layouts; bitmap rows; scalars; the code table; the RGBA12 bus word.
…ack end KonaBackend now owns a PixelStore sized to the managed area (nothing is allocated until a word is written) and lays out every GL buffer configuration it sees within the host's per-pixel budget, counting the ones it refuses by reason. The token layer will keep a layout per context; until then the last one shows in kona status. A reset zeroes the store in place. kona pix X Y lists the words of a pixel that have an array, so the store can be looked at the way the later stages will write it.
The replay parser took the whole file as a slice, which a recording of 71 million records cannot be. Stream reads the same format from any reader one item at a time (the board, a record, or the end with its count), and Recording::parse is now a loop over it, so there is one decoder for the format. Errors are as before (bad magic or version, unknown tag, cut-off record, wrong count, second or missing board record), plus data after the end record.
Counts which GL tokens the three demos in the GL recording send, per user mark, replayed into NullBackend behind the census tap: vertex, array, matrix, texture and query families, how many commands fall outside QEMU's decoder table or in its unresolved set, and the dropped words by the command before them. The 1 GB file does not fit memory, so census_spans reads the board's DMA reads in a first pass and replays in a second, cutting a census at every mark. GL commands are also tallied by family from the front end's table names, and the render says how many commands lack a decoder entry or sit in the unresolved set. Results and the grammar still needed are in clanker/kona/token-census.md. The test skips unless KONA_GL_RECORDING names the file.
The integer primitives the textport and the X server share: plot a point, fill a box and set a bitmap's bits into one word position of the store, clipped to the managed area. A draw's set-up allocates the array it writes. Coordinates are the board's (origin bottom left, y up) and the store keeps them as given; the display flips. A whole-pixel box lights columns x1+1..x2 and rows y1+1..y2: the left column and bottom row are left out, so boxes that share an edge meet with neither gap nor overlap. FillRule makes that a parameter, with a variant that lights every edge, so the pending ruling on the rule's grade changes a value and not the code. A reversed box draws nothing. Tests: the four-corner packet's columns and rows, shared edges, the rule as a parameter, clipping at the screen edges, off-screen plots and the bit order of a bitmap.
The PROM's console draws before any window exists. Command 2 sets the draw colour (a colour index, a byte), 4 the raster position, 5 fills a box by the fill rule, 6 plots a point, and a bitmap blit draws a glyph or icon at the raster position minus its origin and then advances the position. 1, 3, 7 and 8 are framed and counted: 3 (a colour-map load) belongs to the display tables, and nothing is known of the others' role. Pixels go to the colour word of a single-buffered level-0 window, word 1 of the record, since no buffer configuration precedes the console; that default, the colour-index reading of a textport pixel and the bitmap placement are the model's own and graded approximated in the module doc. Tests: colour and box, point and raster position, signed corners, bitmap placement and advance, and the commands that only count.
KonaBackend takes the textport's commands and bitmap blits and draws them into the store with the configured fill rule, shows the textport's state in kona status, and clears both on reset. kona hash prints a hash of the store (FNV-1a over each allocated word position and its entries), the way the other boards' replay checkpoints do. Replaying the PROM recording to its end draws the graphics menu: 5,528 draws, one word array, and a store whose hash is pinned in the replay test as a regression net (class U: the hash says the drawing did not change, not that the pixels are right). Rendering the store with the y axis flipped puts every label where the recorded screen capture has it, and the gradient, icons and logo are all there as colour indices, which the display's colour map will resolve; so the board's y-up coordinates and the placement rule hold for the textport. The test skips without KONA_RECORDINGS.
The X server loads its drawing state as thirteen words: a colour, a background, a write mask, a logic operation and a target. Decode them into a Gc, or say why not (bad fields, colour or mask, a mixed display-ID form, or a form whose meaning is not known) so a draw that uses an undecoded context is refused and counted, never guessed. The forms understood are the 8-bit and native 16-bit main-plane index, 24-bit and 30-bit RGB, the two overlay buffers, the popup planes, both overlay buffers at once and the display-ID plane, with the 16 logic operations and the write-mask arithmetic. Ported from the QEMU model's decoder with its unit cases, and with its grades: the field layouts are software expectations; that a repeated buffer selector means the same selection, that the config-0x28 selector-3 form is the overlay window drawn into both buffers, and that format 9 marks 30-bit colour are inferred. Taken from jamesbraid/qemu-sgi-origami at dd935284c624 (hw/display/sgi/x-gc.c and its unit test, tests/unit/test-sgi-x-gc.c).
The X server's state commands set what fills draw under: 0x129 the context, 0x12a the drawable's position (the origin of rectangle coordinates), 0x145 and 0x12b a clip list (a count and rectangles, the first words arriving as bdata4 runs ahead of the clip), 0x12d a clip box. XState keeps them and works out the bounds a fill clips to: the screen, narrowed to the clip rectangles' box and the scissor, with a membership test for a list of several rectangles. Grades are in the module doc: each rectangle absolute with an exclusive far corner, and drawing clipped to their union, are inferred; a list whose length disagrees with its count clips to the screen only; the scissor ends at the next clip load (a model choice, since how long it lasts is unknown). Any other kernel command ends a waiting clip run, as the other model does. The model keeps at most 64 rectangles. kona status shows the decoded context and the state.
A box (two words, absolute, far corner exclusive) and a rectangle (x, y, width, height, relative to the drawable's position) draw under the loaded graphics context into the colour word: clipped to the screen, the clip list and the scissor, the colour through the context's write mask and logic operation. When a request has more boxes than a token holds, the leading words arrive as data tokens (0x141 to 0x14b) that wait for the box or rectangle token ending them; any other kernel command drops them, as an odd-length run is dropped. X shapes draw the pixels the request names and do not go through the textport's fill rule (approximated). A request is checked whole before any pixel is written, so a refusal draws nothing, and is counted by cause: a context that did not decode or an opaque one with no stipple, a target held by the display step (overlays, display-ID plane), or a destination word wider than the target. The model carries no per-pixel tag for what a word means; which colour map resolves it is the display's to settle. Tests: box and rectangle geometry, data runs and their bounds, clipping to screen, clip list and scissor, write masks and the no-op operation, and each refusal.
Spans (a count, that many points, that many widths) draw one-pixel-high runs from their points, absolute, with no position added (approximated: no traced sequence tells that from a position-relative reading). A span token whose count disagrees with its length is dropped and counted. Points draw single pixels and, like boxes, take the words of a waiting data run first. Both go through the same checked-then-drawn fill as boxes, under the same context, clip and refusals. Background and relative point forms and segments are not in either recorded stream and are not read from the model's source yet.
Stippled fills are masked by a pattern the X server stores once and names. 0x13d stores an entry's width, height and rows (the rows follow the command, so the back end answers Reply::Data for them and takes them as a Job::Data), 0x13e sets the entry's origin and 0x13c loads an entry, or all ones to end the masking. Box, rectangle, span and point fills then paint a pixel only where the pattern's bit, measured from the entry's origin plus the drawable's position, is set; a clear bit leaves the pixel alone unless the context is opaque, in which case it takes the background. An opaque context with no stipple loaded is still refused. The cache's size (32 patterns of up to 32 by 32) and its refusals are the model's own: a store outside it consumes its rows and leaves the entry empty, a row count past 4096 is refused without asking for the rows. The bit order is inferred from a traced I-beam; what a clear bit does and how a store treats the origin are model choices, graded in the module doc. Tests: tiling, wrap and origin shifts, load and unload, refusals, and a checkerboard stored and loaded through Backend::job masking a box.
Replays the IRIX recording's graphics traffic up to the first run of data only a back end can size (record 1,415,538, about 20,700 records in) through the same path as the PROM test and pins the store's hash: 154 fills of the X server's state, boxes, rectangles, spans, points and stipples at the greeter's start. A regression net (class U), not evidence of the pixels: the pixel and image paths that fill the rest of the desktop are the next step's. Rendering the store of the whole replay with the X fill rows flipped puts the toolchest, the console icon and the four desktop icons where the recorded desktop capture has them, and the greeter's window, buttons and text field where the greeter was; without the flip they land at the bottom, so the X server's y runs down from the top of the screen while the textport's runs up. The earlier commits' X fills carry that flip. replay_drawing takes the number of records to run, so a test can stop at a known point. The test skips without KONA_RECORDINGS.
Relative points (one, six or twenty-four per token) were framed and dropped. Each word is a step from the previous point, the first from the drawable's origin, and the walk runs on through a waiting data run and the whole burst before starting again at the origin with the next token. Once the points are placed they draw like absolute points, through the clip, scissor, stipple, write mask and logic operation.
The X server's lines and arcs arrive as segment tokens (one or six independent pairs of points) and polyline tokens (two or seven points, absolute or as steps from the previous point). They were framed and dropped, so every outline the X server drew was missing. A line is cut into boxes before the request is planned: a run of pixels along a row or column is one box and pixels off the screen are not made, so the existing fill loops draw it, the destination check covers the whole request, and nothing is looked up per pixel. A segment draws both ends and a segment of no length one pixel; a polyline draws a shared vertex once and is not closed unless its last point is its first. No source gives the tie rule for a line that falls exactly between two pixels. The model takes the integer line through pixel centres and rounds an exact half toward the end point. That is an approximation and is marked as one, with what would settle it. Ported from the line shapes of hw/misc/sgi-kona.c in jamesbraid/qemu-sgi-origami.
… and vertex tokens The GL path counted these as unmodelled, so a client that submits vertices with glVertex3d, colours with glColor3d or coordinates with glTexCoord2d drew nothing. The texture-coordinate forms are established as two words to a double, and the colour, normal and vertex forms are inferred to be the same. A double token is narrowed once, as the command arrives, into the words of the matching float form, high word first as the depth clear's double is, and then runs the float form's path. The vertex path gains no branch and no second decode. The colour-index double stays unmodelled: the model has no colour-index mode. A word count that does not match the form is not narrowed and is counted under the float form.
The module comments and the back-end plan were written before the fragment operations, texturing, lines and points landed, and still told readers the stage has no stencil, blend, logic operation, write masks, alpha test, polygon offset, stipples, texturing, lines or points, that the GL path takes no texture commands, and that every draw runs one unspecialised path. They also said swaps and retraces both go by ticket, when only a retrace request carries one (the reset epoch it was made in); a swap loads the display's bank table when its command is reached in the stream. Describe what the code does and keep the gaps that are real: no fog, no texture on volume or detail binds, the model's own depth form, fragment-operation order and line and point shapes.
The pipe's identity was a set of constants for the Onyx2 deskside's InfiniteReality2, scattered through the configuration block, the geometry board's one-wire record, the texture pool and the class word the driver's attach reads. Adding another product would have meant another copy of each. Gather what differs by product into one row of a table: the board set, the class word, the geometry, raster and texture boards, the texture size and its code, three chip identity words, and the chassis the product shipped in. The pipe's configuration names its product and every reader takes its values from the row. The one row is the InfiniteReality2 and every byte of the block is unchanged.
Add one page for the chip: the parent and each child against the Linux cell it stands for, which parts are the IOC3 and which the external Super I/O, how the interrupt words and DMA work, what each lock covers, each board's row and wiring (including the Octane2's records and the Onyx2's MediaIO and RAD1), what is modelled and what is approximated, and which snapshot keys remain. Point the older notes that named src/dev/ioc3.rs and ioc3_eth.rs at the new paths, and retitle the PS/2 page, which is not Octane-only. The clanker notes keep only the traps (what the PROM insists on, the pacing rule, scan code set 3).
The R10000 family puts a two-bit uncached attribute beside the address of every uncached reference. An unmapped one takes it from XKPHYS bits 58:57, which already reach the board in those bits; a mapped one takes it from EntryLo bits 63:62, which the TLB stored and then dropped when it made the physical address. Every mapped uncached reference therefore went out with attribute 0. The Onyx2's Hub picks node memory, its own registers or a crossbar widget by that attribute. IRIX maps the InfiniteReality pipe's command page into the kernel and into the graphics start-up and X server as an uncached accelerated page with attribute 1, I/O. With attribute 0 the stores landed in the Hub-special view at node memory past the fitted 128 MiB and vanished: the kernel's pipe start never reached the pipe, and the driver gave up with "IR0: mopup timeout" and death reason 17. A TLB entry with cache algorithm 2 (uncached) or 7 (uncached accelerated) now carries its attribute in bits 58:57 of the physical address it gives, the same place an unmapped reference carries it. Cached entries are unchanged. The attribute is folded in when the entry is written, so translation does no extra work. The other processors cannot store those EntryLo bits, and the boards that do not decode the attribute cut it off with the executor's physical mask.
The Hub still asked the machine four things through a hooks trait: the node number, the cached alias, a processor soft reset and a restart. A trait that a board implements is the wrong direction for a chip, and each call hid ordering: the effect carried the new value, so two writers that raced could leave the machine's routing at the older one. The node number and the cached-alias size are now published values the owning unit stores under its own lock, as the register changes. The Hub's address decode reads them from there with no lock, as before and through one pointer. The machine's node-zero routing is handed to the Hub once and re-reads both under its own lock when a store, a neighbour's vector write or a reset has changed one, so the last value written wins. A soft reset is a line, not a call: the Hub sets bit 63 of the processor's interrupt word, which is what the processor's reset signal did, without a lock and without the machine. A restart goes out on the machine's event channel as a hard reset, wired the way the Indy IOC's power events are. Enable and NMI stay stored and logged. The node number stays 16 bits because the register field is 9 bits wide. No per-access cost is added: the claim decode loads the number and the alias through a pointer it already followed.
src/ip35.rs held the bounded MMIO windows, their sparse map and map_device, but the only code that uses them upstream is the SN0 node bus, so IP27 imported its bus library from a module named for the Fuel's software platform. The file becomes src/window_map.rs, named for what is in it, and takes the physical address masks that ip27.rs and a Fuel bus both define (PA_MASK and CPU_PHYS_MASK), so there is one definition. A bus that wraps the four views differently stays with the machine that has it. The move is a rename; a test now pins what the window map did without one: a window answers exactly its bounds, the rest of the page is refused, and a later map over a page replaces the earlier.
The X server sets bit 31 of the graphics context's fourth word for an opaque stipple or a tiled fill, and nothing else on the wire tells the two stipples apart. Whether the board honours the bit is unknown: the engine forwards the word whole and the chip probably masks it to 24 bits, which would leave the bit no effect. The model keys an opaque fill on it anyway, so call that an approximation that contradicts the mask, and say what would settle it.
The kernel puts the pointer in the video DAC when an X session owns a DAC-capable channel: the display request loads a 64 x 64 two-bit image and two colours, and writes to the DAC block's index and data registers set the control value and the position. The load was already decoded and kept; nothing drew it, because the composed frame had no stage after gamma. Follow the block's registers (index low then high byte, data to register 2) for the control value at 0x204 and the four position bytes at 0x200, and draw channel 0's cursor last, over the XMAP glyph and the frame. Its colours are the DAC's 10-bit values narrowed to eight and do not go through the gamma RAM, the DAC being after it. It shows in X11 mode with the enable bit set, which is what the kernel writes, 5. Pixel value 3 takes the foreground and 2 the background, 0 and 1 leave the frame. The host's pointer follows it the way it follows the glyph cursor. The kernel sends the index again before each position byte and the bytes go to register 2 of the owning channel's block like every other data write; no other address carries them. The index advances after each access in 0x200 to 0x204 and in the image's 0x400 to 0x7ff, as the published part does; whether it advances in 0x000 to 0x00f is unknown and the model leaves it there. The position is a signed 16-bit top-left in output pixels with no bias, and which active pixel is (0, 0) depends on the board's blanking edge, which is unknown. The colour assignment is the part's, inferred to be this board's DAC. The XGA mode, the interlace bit's effect and the load for channel 0xde are not drawn. The module header and the board page say which is which. The DAC state is saved in the snapshot. The recorded desktop and console frames do not move: the recording's pointer is the XMAP glyph and the stream never loads or enables a DAC cursor, which the replay test now asserts.
The display generator's ChannelBlanking and TempBlankAllChannels commands were kept as state and logged, and the screen ignored them. A guest that blanks a channel while it changes video format, or blanks everything around a mode switch, still saw the old picture. The board shows one channel's frame, channel 0's, so that is the one that matters: the frame is black while channel 0 is blanked or every channel is temporarily blanked. Other channels' blanking stays state. The composer takes the choice once per frame, from a flag in the baked tables, so an unblanked frame pays nothing per pixel. The pixel store is not touched; drawing carries on underneath and shows again when the blank ends. The recorded desktop and console frames do not move: channel 0 is unblanked in both.
On a two-node Onyx2 or Origin 2000 the PROM numbers one node 1. That
node's software addresses its own memory by its number (bit 32 up), and
node zero's memory beyond the cached alias belongs to the other node.
The processor's cache and the JIT reach RAM through the node's ppmem
window, but the window covered only node-relative offsets below 4 GiB,
and NodeZero cleared it entirely once the node held a nonzero number.
Every cached access of that node's processor then went through the
decode table, the Hub's node view and its bank lookup, and every
instruction fetch missed the decode cache's window pointer and re-read
its word over that path. A per-thread count in a throwaway build showed
it: during `runon 1 awk 'BEGIN{for(i=0;i<1000000;i++)s+=i}'` on the
Onyx2, all 912 million instruction fetches and 458 million data
accesses of that processor took the bus path, 86 percent of the
fetches to its own memory at its number and the rest to node zero's
memory. During runon 0 the other processor fetched 665 million of its
804 million instructions from node 1's memory the same way.
Windows now lays every node's window from the numbers and aliases the
Hubs publish, as the decode table routes them: node zero's window from
the node's own banks below the alias and the zero holder's above it,
and each node's memory at its number, the other node's included,
sharing its storage and generation counters. A two-node board gets an
8 GiB window, so numbers 0 and 1 fit. Any node's change relays both
windows, laying only the difference: a page that stays mapped is
replaced in one mapping call, and a range that goes leaves the region
bitmap before it is scrubbed, so a renumbering never clears a window
the other processor is executing from. The access path is unchanged.
With the fix the same count shows no cached access on the bus path on
either processor, from power-on to the end of the runs. On the Onyx2
at this branch's previous tip, the awk loop took 29.9 s on processor 0
and 57.9 s on processor 1 (means of five runs each, alternating in one
boot). With the fix it takes 27.9 s and 28.2 s. Boot to the login
prompt went from 293 s to 208 s. The two-node Origin 2000 runs the
loop in 35.2-36.5 s on processor 0 and 37.7-39.6 s on processor 1.
An 8 GiB window has 128 MiB regions in its bitmap, so on two nodes a
64 or 256 MiB bank, which fills no region whole, stays on the bus path
for both processors. A number above 1 is left to the bus path too.
The Origin 2000 guide said a two-node machine stops in IRIX with "Processor #1 did not start", the second node's processor taking a TLB exception in the PROM. It no longer does. At this branch's tip, with and without the node window change, a two-node Origin 2000 (180 MHz R10000, 128 MB per node, stock IP27 PROM 6.156 and I/O PROM) boots IRIX 6.5.30 from the BaseIO's SCSI disk to a root login on the serial console, and hinv lists two IP27 processors. runon 0 and runon 1 both complete.
The texture arena only grew. A level reloaded at an address in another size, a level a later load overlapped, and every level of a context whose slot was activated afresh all stayed in the arena, so it filled at the product's size (16.7 million 32-bit texels on an InfiniteReality2) and every later load was refused as past texture memory and every bind of a level that never loaded failed. A TyrQuake replay hit that wall partway through. The pipe has no allocator of its own: a load writes where its address names, a level reloaded at the same place overwrites it, and a later load that overlaps a level replaces it. The model now stores texels the same way. Texture memory is a plane of words for each bank of each context (and for its detail images), and a level at address a of a bank is the words from 16 a of that plane, a unit of 256 bits being room for at most sixteen texels. Nothing is allocated, so nothing fragments, and a load is refused and counted only when its address and extent reach past the product's bank, which is the model's own limit since the pipe has nothing to refuse with. A load of a new shape or an overlapping address retires the levels it overlaps and zeroes its own words. A fresh activation of a context slot drops that context's planes after draining the tiles. A first-fit allocator would have refused loads the pipe takes once the pool fragmented, which a random stress shows. The owner's block offset the kernel writes on a reserve is not modelled, so each context has planes of its own and contexts neither contend for the pool nor alias; that is stated as an approximation. A plane is as long as the highest word written. The sampler takes a level's plane once for a sample and indexes it, so a texel read costs what it did. Snapshots carry each plane that holds words. Tests: a long run of reloads at the same addresses, of alternating sizes at one address and of overlapping addresses each stays bounded; a load that fits no hole is not refused; 20000 random loads inside the bank are never refused and never overlap; a load past the bank is refused with the levels there untouched; two contexts at the same addresses do not alias; planes survive a snapshot and a bad one is refused. A gated replay of the Quake recording prints the texture counts (`KONA_QUAKE_RECORDING`, `KONA_PROGRESS`, `KONA_STOP_AT`).
The desktop frame test compared with a threshold of 8 levels, so a difference of one level on 97% of the pixels (1274736 of 1310720) passed unseen. Measured against the recorded capture, every pixel is within one level and none is further. The one-level differences come from two conventions that nothing known settles, and the recorder settles each the other way. This model widens a 10-bit colour-map entry to the 12-bit gamma index by replicating its top bits; the recorder rounds it to 8 bits first. This model rounds the 12-bit gamma output to 8 bits; the recorder drops the low four bits. With both switched to the recorder's way the frame matches exactly, so nothing else (stipple, text, cursor placement) differs. The widening is graded as inferred and nothing yet settles the output narrowing, so neither side is shown wrong and neither is changed. Assert no pixel is further than one level, and name the two conventions in the test's comment.
The gamma RAM's 12-bit output reached the DAC's 8-bit inputs rounded to nearest. The colour map's output chips drive the video-output chips from bits 11:4 of each colour alone and nothing on that link rounds or dithers (read from the board's wiring), so the top eight bits are the value. The model now truncates. That is established for that link and inferred end to end, since a chip between the video-output chips and the DAC is not read to add anything. Grade the other two choices in the display chain where they are made: the 10-bit colour-map entry reaches the 12-bit gamma index by replicating its top two bits (inferred: the only rule the board's gamma addressing is known to fit), the glyph cursor passes through gamma (inferred) and the DAC cursor does not (established by where it sits). The gamma address's colour field 3 is rewritten to green by a hardware test program, not to blue; what the board does with 3 is unknown.
The kernel writes 1 to the host interface's status register at 0x8003a8 once per video interrupt. The model treated every bit but 2 as an unimplemented write and logged a warning for each, about 85 a second while a guest ran. Nothing in the kernel or the firmware reads bit 0 back, so there is nothing to model beyond the write itself. Count the writes, expose the count, and leave the busy latch alone. Other bits still warn.
A GL client that pushes attributes waits for word 0 of its return page before it goes on, whatever the mask. The model did not claim the push token, so the word was never set and the cycles demo sat in its wait loop printing "GL:Wait Rdata timed out" after its text helper pushed with only the list bit set. The pipe opens a read-back before it looks at the mask and closes it on every path, a push onto a full 16-entry stack included, and the close writes the completion word. Post a bare completion (a zero count, then the word) for each push. Nothing is read back for the list bit, and the model leaves the data for the other tested bits empty and counts those pushes. A push onto a full stack sets the stack-overflow error (inferred from the handler calling its overflow routine) and still completes. A pop takes an entry back and posts nothing; the light-state step is not sent by the host and still posts nothing.
The two-node SN0 window was 8 GiB, so its one-word region bitmap counted 128 MiB regions. The bitmap only names a region that is RAM end to end, and a 64 MiB bank fills half of one, as does each 64 MiB run of a 256 MiB bank. On a two-node Origin 2000 or Onyx2 with those banks every cached access of both processors took the bus path again, a regression for the first node, which had used 64 MiB regions before. A node number above 1 fell outside the window altogether. The bitmap test runs on every load, store and fetch, so it stays as it is. A space now keeps a fine table beside it, one bit per 64 MiB region of the whole window, and the shadow cache consults it on the bitmap's miss branch before taking the bus. A space's bitmap region size can be set apart from its window size. A cabled SN0 board gets a window spanning the node numbers up to the next power of two above its node count, 8 GiB for two nodes and 16 GiB for three or four, with 64 bitmap regions of 128 MiB over numbers 0 and 1. One node keeps its 4 GiB window and 64 MiB regions. The window is reserved address space that commits nothing (mmap PROT_NONE and MAP_NORESERVE on Unix, a VirtualAlloc2 placeholder on Windows, read from the code and run only on Linux), and sizing it by the node count rather than for all 256 node numbers keeps the reservation at 8 GiB per node on a two-node board. A node a guest gives a number past the window still works: the bus path answers every number, at the speed it had before the window. The decode cache keeps window pointers for pages either test finds. It used to key them on the bitmap and start over when it moved, which misses a change only the fine table sees, so it now keys them on a value the space bumps whenever either changes (PpMemSpace::add_key_sink). Every machine that wires a shadow cache to a window registers it. With a throwaway per-thread counter, a two-node Onyx2 with two 64 MiB banks per node made no cached access on the bus path on either processor, from power-on through a runon 0 and a runon 1 awk loop. The iris-bench R10000 suite, deterministic clock, quick, A B B A: identical guest work (621448231 instructions) on every run, and 341.12e9, 337.38e9, 337.29e9, 341.19e9 host user instructions, so the shadow cache's hit path costs no more. On a one-node Origin 200 under IRIX 6.5.30, the processor thread's user instructions over one awk loop, base and branch alternating over six boots (eleven runs), were 243e9 to 279e9 for the base and 239e9 to 292e9 for the branch, means 264e9 and 251e9. That count is not fixed work, since the guest's timers run on the host clock, so it shows no regression beyond its own spread and nothing finer.
A 512 MiB bank 0 spans the whole 512 MiB stride, boot flash's reset
address included. The node memory mapped the flash over the bank in the
cached decode and listed both in the memory map, so MemoryMap::new
panicked ("bank 0 overlaps boot flash") for any machine built with one,
such as a two-node deskside with 512 MiB in bank 0.
Cached references now reach the bank, as the PROM's memory test expects
when it walks that address as RAM, and the reset fetch, which is
uncached, reaches the flash through the Hub-special space, where it
already is. With a smaller bank 0 the flash stays in the cached decode
as before. The QEMU sgi-origami node model makes the same split. That a
real machine does is inferred from the PROM's memory test, not measured.
A test builds every size bank 0 can be, and checks that a 512 MiB one
keeps its RAM at the reset address and every smaller one the flash.
The IP27 and IP35 platform work and the KONA board are spread over several stacked branches that join late. Write down the goal, the order, the decisions taken so far, the finish line and the rules every PR follows, so a later session can pick the port up from the repository.
A texture load sent in a packed type (the library's codes 8 to 12: 3-3-2, 4-4-4-4, 5-5-5-1, 8-8-8-8 and 10-10-10-2) was refused as a type the model does not decode, so the level never reached texture memory and every bind that listed its address found nothing to bind and was refused. A replay of the Onyx2 Quake session sent its 4-4-4-4 textures this way and lost 478 binds to 30 such loads. A packed pixel is the packed type's own size, 3-3-2 with RGB and the others with RGBA (the library refuses any other pairing, and so does the model). The sizes are established. The first component is taken from the most significant bits and a field narrower than eight bits is scaled to 255, as OpenGL defines its packed types; the pipe's unpacker produces an unsigned byte for each, but its bit order and scaling are not read, so those are inferred. The stream is big-endian unless the unpack state swaps bytes, like the plain types. Tests: each packed type decodes to the texels expected, the wrong format for a type is no load, and swapped bytes read the word little-endian.
Both notes still said no machine fits the board and that lines, points and cursors were undone. The Onyx2 deskside now takes the pipe, the 4Dwm desktop runs on it, and the back end draws lines and points, fog, volume textures and the DAC cursor. The notes say so, and name what is still open: stereo, the crosshair, the cursor on the other channels and any comparison with hardware pixels. KONA now lands stacked on the SN0 machine branches it joins, not after a single Onyx2 machine PR, and only the IP27 machines refuse a snapshot.
jamesbraid
force-pushed
the
onyx2-kona-draft
branch
from
November 10, 2026 21:31
8a59b57 to
dcaf735
Compare
jamesbraid
force-pushed
the
ip27-fixes
branch
4 times, most recently
from
November 11, 2026 02:31
3fac6c4 to
ca677fd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #481 (the IP27 cleanup), which builds on #546 (IOC3) and #512 (PCI interrupts). Only the commits above
ip27-fixesbelong to this PR.This is the InfiniteReality graphics pipe (the Onyx2's KONA board) as an Iris board model, fitted in the Onyx2's graphics slot, plus the plan notes for the Onyx2 and Onyx 3000 work.
What it does
[graphics] board = "ir2"/"ir2e", with one or two raster managers). The InfiniteReality3's product row is in the table for when a machine fits it.src/dev/gl, user clip planes, texgen and the double-precision forms;How it's built
GFifo, DMA, display lists) is ported from the sgi-origami model; each ported file says so.defsmodule. Where a hardware detail isn't known, the code says so and makes a plain, labelled choice.Testing
KONA_RECORDINGSandKONA_GL_RECORDINGpoint at them.cargo test --workspacepasses.[graphics] board = "ir2".