Repository navigation
rad1: play the Octane boot tune in full and stop audio replaying a ring lap - #373
Open
jamesbraid wants to merge 6 commits into
Open
jamesbraid wants to merge 6 commits into
jamesbraid wants to merge 6 commits into
Conversation
The output was tied to clock generator 1 and 0x58 accepted only 0x2000000x. The Octane PROM writes 0x4000000x and loads generator 2 for 32 kHz, so its write was refused and the output kept the 44.1 kHz default. Bit 29 now selects generator 1 and bit 30 generator 2; other select values stay unsupported and leave the register unchanged. The PROM selects the 192 kHz master after the load, so a master-select write re-evaluates a generator that was already loaded, with the words it was loaded with; without it the load alone computes 29.4 kHz. Contract: mediaio-rad1-audio, "Output channel: clock selection, descriptor forms and register 0x88" (bit 30 and the generator load words established as the PROM's intent, the general "bit 28+n" rule inferred, the select's timing unknown); ip30-rad, "PROM boot tune" facts 1 and 9.
The model demanded IRIX's exact control word and a matching second descriptor set. The Octane PROM uses one descriptor with end of descriptor (control bit 2) set and next index 0, so it never started. Decode the control word as the datasheet does: bits 6:3 are the index of the next descriptor, bit 2 ends the transfer after this descriptor. An end-of-descriptor transfer plays its buffer once and the channel clears 0x58 bit 0 itself, so a PROM that waits for the bit sees it clear after the pass (2.252 s for the tune at 32 kHz) and set before. A read of 0x58 brings the channel up to date first. Without the bit the next entry is loaded when the descriptor ends; IRIX's second set is that entry, so its ring still loops. A link to an entry that was never programmed is logged and the channel stops moving frames with the register unchanged: no source says what the chip does. The card kept only the descriptor RAM entries the supported rings' second sets cover and refused writes to the rest, so a link to any other entry read as never programmed and stopped the output after one pass. Linux's output start programs entry 7 (0x454..0x45c) with its working descriptor and links to it, so under Linux the ring stopped after its first 4 KiB lap. IRIX links to entry 1, which happened to be kept. All 16 entries at 0x400 + 12 i now store and read back, and a link to any of them loads it. Contract: mediaio-rad1-audio, "Output channel: clock selection, descriptor forms and register 0x88" (end of descriptor and the index established as the vendor code's description; the self-clearing bit established as the PROM's expectation, the hardware behaviour inferred; an ignored index and a link to an unprogrammed entry unknown); ip30-rad, "PROM boot tune" facts 2 to 5, 7, 8 and 13 and tests 1 to 4.
Writes to 0x88 and 0x8c were stored and otherwise ignored. The PROM writes 0x88 = 3 before the tune and 2 after it, and IRIX halt writes 2. 0x88 = 2 mutes and 3 releases the analog output relay; 0x8c = 3 mutes and 2 releases the DAC. Other values change no mute state, and before any such write nothing is muted, as the power-on state is unknown. A muted output still moves the ring, so the transfer is not stopped; the host stream and the WAV capture get silence. Contract: mediaio-rad1-audio, "Output channel: clock selection, descriptor forms and register 0x88" (relay and DAC meanings established as the vendor code's description; no effect on the DMA inferred from the PROM's ordering).
IRIX refills the Octane's 4096-byte output ring from its 1 kHz clock interrupt, writing only 36 to 90 frames (1 to 2 ms) ahead of the pointer the status slots report. The guest's clock interrupt and the card's status completions both run on the machine's timer thread, and the card's sample clock runs on host wall time. When the host holds that thread up for a few milliseconds, the card credits the whole hold-up at the next completion while IRIX's refill is just as late, so the pointer lands past IRIX's write point. IRIX then reads the ring as full, waits a lap, and the previous 11.6 ms of audio plays again. In captures of IRIX 6.5.30 playaiff the glitches are inserted laps. With the timer thread held up 3 ms every 100 ms, a 12 s stereo 44.1 kHz file came out 13.7 s long with 164 discontinuities; with this change it is 12.0 s long with one. Treat a status completion that fires more than one interval late as a hold-up and take the excess off the card's clock, as a machine stop already is, so the pointer, positions and UST stay in step with the guest's own late interrupt. A late completion puts the clock back to its own due time, so the next completion is a full interval away. Time a guest read has already seen is never taken back, and the 10 ms pump leaves the output to the status completions while they run, so a pump read inside a hold-up does not stop the late completion absorbing it. Held-back time must not grow without limit, or a host that keeps the timer thread late would run the card's clock, output rate and UST behind wall time for as long as it lasts. On-time completions win the time back at 1/32 of an interval each, and at most 200 ms is held back; past that a hold-up is credited and its lap replays, as before. The module notes name the rule an emulator approximation and state its cost. The tests check the clock itself and pin the one-interval threshold, the bound and the win-back.
Resampler::push stored the left and right samples with separate pushes and ignored a failed one. The consumer pops single samples, so the ring's free room can be odd; with one slot left the left sample went in and the right was dropped, and every pair after it played with the channels swapped until the stream was rebuilt. Push a pair only when both samples fit.
The host stream opened at the analog output's first start, inside the guest's enable write, and once only. The card's clock runs on host wall time, so however long the open took, and however long the host then took to pull its first samples, all fell due at once into the 200 ms host ring and the rest was dropped. A slow start is what a first launch on macOS gives: CoreAudio's first open in a new process, or an output device waking up. The Octane PROM's boot tune then played its first 0.1 s and jumped ahead, the "interrupted noise" heard on the first boot of a new build and not after. If the open failed, the sink stayed without a stream for the rest of the run, so a sound that starts before the host can play it was lost, and so was everything after it. Open the stream when the machine starts. With the host stand-in taking 300 ms to open, or not pulling samples for 700 ms, a PROM boot used to lose 110 to 510 ms of the tune near its start; opened at machine start it plays whole up to a 2.5 s late host, since the tune starts 3 to 4 s after power-on. Keep the sink when the open fails, remember when it failed, and try again at the next output start and once a second from the output pump while the channel runs. Nothing changes when audio is off. The rad1 debug log now reports how long the open took and when the host ring fills and drains again, so a host that is still late shows up in a log rather than only by ear.
jamesbraid
marked this pull request as ready for review
October 31, 2026 21:12
jamesbraid
marked this pull request as draft
October 31, 2026 21:17
jamesbraid
force-pushed
the
rad1-boot-tune
branch
3 times, most recently
from
November 1, 2026 00:23
98d594d to
1aeb929
Compare
jamesbraid
marked this pull request as ready for review
November 1, 2026 00:24
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.
This gets the Octane PROM's boot tune to play in full and the PROM's check after it to pass, and fixes glitches in IRIX audio:
Tested with the rad1 unit tests, a capture of the PROM boot tune against a reference recording, and playaiff runs with injected timer stalls (164 glitches in a 12 s clip before, 0-1 after).
Limitation: the card wins held-back time back at about 3% faster than real time, capped at 200 ms. While host timer stalls take less than about 3% of the time, lap replays go away and host underruns don't rise. Above that, the host plays short silences until the cap is reached, then falls back to the old replays, still fewer than before. On this host the stalls measured 1-2%.
Also inferred, not measured: that 0x88 = 2 mutes the output, and so cuts off a tune still playing after the PROM's check, comes from when the PROM and drivers write it. A link to a descriptor RAM entry nobody programmed is in no source; it stays reported as unsupported and stops the output.