Skip to content

rad1: play the Octane boot tune in full and stop audio replaying a ring lap - #373

Open
jamesbraid wants to merge 6 commits into
techomancer:mainfrom
jamesbraid:rad1-boot-tune
Open

jamesbraid wants to merge 6 commits into
techomancer:mainfrom
jamesbraid:rad1-boot-tune

Conversation

@jamesbraid

@jamesbraid jamesbraid commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

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:

  • The analog output is paced from the generator the 0x58 word selects, so the tune plays at its own rate.
  • An end-of-descriptor transfer plays once, and a descriptor links to any programmed entry of the 16-entry descriptor RAM: IRIX links to entry 1, Linux to entry 7. Before, a link to an entry other than the supported rings' own stopped the output after one lap.
  • The output relay and DAC mute words are tracked, and silence the host output while the transfer runs on.
  • A failed host audio open is retried instead of leaving the card silent.
  • The host stream opens when the machine starts, so a slow first open on the host no longer cuts into the boot tune.
  • The card's clock holds through a host timer hold-up. IRIX refills its ring from the 1 kHz clock interrupt, only 1-2 ms ahead of the play pointer. When the host stalled the timer thread, the card credited the whole stall at once, the pointer passed IRIX's write point and an old lap of the ring played again. This is an emulator approximation and the code says so.

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.

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
jamesbraid marked this pull request as ready for review October 31, 2026 21:12
@jamesbraid
jamesbraid marked this pull request as draft October 31, 2026 21:17
@jamesbraid
jamesbraid force-pushed the rad1-boot-tune branch 3 times, most recently from 98d594d to 1aeb929 Compare November 1, 2026 00:23
@jamesbraid
jamesbraid marked this pull request as ready for review November 1, 2026 00:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant