Skip to content

Tezro: the ICE1724 audio controller - #640

Open
jamesbraid wants to merge 3 commits into
audio/ice1724-outputfrom
audio/ice1724
Open

jamesbraid wants to merge 3 commits into
audio/ice1724-outputfrom
audio/ice1724

Conversation

@jamesbraid

Copy link
Copy Markdown
Collaborator

The Tezro lists an ICE1724 audio controller behind the TI bridge on its IO9, and iris had nothing to answer to it, so IRIX had no audio processor and sfplay had nothing to open. This adds the controller and fits it with [tezro] audio = true (off by default).

Three commits:

  1. pci: give a PCI-to-PCI bridge 32-bit I/O windows: the Tezro's PIC puts I/O BARs above 64 KiB, which a 16-bit window cannot forward, so a function behind the bridge ended in master abort. Only the Tezro's bridge takes it, through with_wide_windows.
  2. dm8: add the ICE1724 PCI audio controller: seven DMA engines, EEPROM, converters, AC'97 variant, multichannel output with an ITU-R BS.775 downmix, and a read-ahead hold-up for IRIX's polling (measured in IRIX runs on a loaded host: 4 to 19% of samples a lap old without it, 0.2% with it).
  3. ip53: fit the ICE1724 audio controller behind the TI bridge on request: the machine wiring, the no_audio warning, the monitor listing, the CHANGELOG bullet, and PIC INTC routing, which the card at device 2 needs (without it the IRIX guest panicked on an unrouted pin; a PIC test fails without it).

Scrutinise: the hold-up approximation is an emulator policy, not the chip's behaviour; the downmix is a choice; the subsystem ID is the DM8 board's recipe. Cost of the read-ahead is in the dm8 commit: inside this device only, not timed, arm64 unmeasured.

Held for a listening test on a real output: nobody has heard it, because the build container has no audio device. The IRIX guest plays, sfplay moves frames and the frame counts are visible in the monitor.

Depends on the PR below for the shared sink and the multichannel open.

A type 1 header modelled only the 16-bit I/O window: its I/O base and
limit registers read 0 in the capability nibble and the upper 16 bits
at 0x30 were fixed at zero. A bridge window above 64 KiB could not be
programmed, so no I/O BAR placed there forwarded. A host bridge whose
PCI I/O windows start on megabyte boundaries, as the Origin and Tezro
PIC's do, puts every I/O BAR above 64 KiB, and a function behind a
bridge on such a machine was unreachable: the guest's accesses ended in
master abort. The Tezro's onboard audio chip sits behind the TI bridge
and hit exactly this.

Report 32-bit I/O addressing in bits 3:0 of the I/O base and limit, as
the TI PCI2050 does and as the PCI-to-PCI bridge specification allows,
store the upper halves, and forward an I/O BAR that lies wholly inside
the window they make. A window with zero upper halves is the old 16-bit
one. The TI PCI2050 also stores the upper 32 bits of the prefetchable
base and limit, which stay read-only zero in other bridges.

Only the Tezro's bridge takes this, through a new constructor
(`PciToPciBridge::with_wide_windows`): the registers are established for
the PCI2050 and not for the other bridges the machines fit, whose
behaviour does not change. Nothing calls the constructor until a device
is fitted behind that bridge; tests cover the window registers and the
forwarding.
The Tezro lists an IC Ensemble ICE1724 (Envy24HT, 1412:1724) as its
audio controller, and the DMediaPro DM8 option card is the same chip.
Nothing in iris could answer to it, so a guest that probed for it found
no audio: hinv had no audio processor and sfplay had nothing to open.

Model the controller as a PCI function: a byte-granular register file
behind each of its two I/O BARs, seven DMA engines sharing one sample
rate, the configuration EEPROM on the I2C port, the two write-only
converters behind bit-banged GPIO pins, and an AC'97 variant without
either. The interleaved engine plays all its slots, and the S/PDIF
engine mixes into the front pair, through one host stream with as many
channels as the host device has; a narrower host gets a standard
downmix. Recording writes silence, as iris has no host capture path.

A host with fewer speakers than slots gets the ITU-R BS.775 stereo
matrix with nothing scaled down: each speaker plays its own channel at
unity, centre and surrounds fold in at -3 dB, the low-frequency channel
is dropped, and a sum past full scale saturates. Scaling each speaker's
folds to sum to unity would keep a full-scale 7.1 frame from clipping,
but IRIX plays stereo in the first two of eight slots with the rest
silent, and that scaling played every IRIX sound 9.9 dB quiet (0.32 of
the file's level). This is a choice, not the chip's behaviour. So is
the fold BS.775 has no rule for: on a 5.1 or quad host the side pair
folds into the back pair at unity. A mono host falls through to the
stereo layout, unchanged. Which speaker each slot drives is not
established for this card either: the usual host order (front pair,
centre, low frequency, back pair, side pair) is inferred.

The header uses the bus's policy for read-only and write-one-to-clear
bits, and the function answers only what the chip defines beyond that:
the power-management state, the subsystem pair behind its write enable,
and the interrupt status bit. The reset state masks every interrupt
source, and interrupts follow the OR of the unmasked sources the
vendor's documentation gives. Accesses and values it does not establish
are logged under the new dm8 log module and counted. D3hot to D0 resets
the registers but not the header, because a function cannot reset its
own header without leaving the bus's decode table stale; the module
notes list that and the other approximations.

A transfer to or from guest memory runs with the state lock let go, so
the bus and memory locks below the card are never taken inside it, and a
flag keeps other paths that move the engines out until it ends.

IRIX steers its playback by polling. It masks the engine's interrupt,
reads the address register about once a millisecond and writes only
about 2 ms past the position it read. The card's clock is host time, so
when the host does not run the guest for longer than that, an engine
that reads on passes what was written and plays the previous lap of the
23 ms ring again: a buzz on a loaded host. In IRIX 6.5 runs on a host at
load 400 to 650, 4 to 19% of the samples played were a lap old, and the
captured output repeated exactly one ring at a time. So, as an emulator
policy with no hardware counterpart, the one HAL2 already uses for IRIX,
an engine whose interrupt is masked and whose address or count the guest
read in the last 50 ms moves no more than 1.5 ms past the position read,
and waits there; time held beyond 100 ms is dropped. A run with it
replayed 0.2%. HAL2 waits at 1 ms; here 1 ms held the engine below real
time whenever a poll came late and put short gaps in the output. IRIX's
polls were 1.00 ms apart at the median, p90 1.03 to 1.07 ms and p99 1.1
to 2.8 ms on a loaded Linux host (p99 up to 13 ms on a loaded Mac), so
1.5 ms sits above most poll gaps and below the 2 ms IRIX writes ahead.
A guest the host holds up still loses the time, as a gap instead of an
old lap: on a heavily loaded Mac, gaps of 1 to 500 ms in the output went
from 46 (285 ms) to 185 (894 ms), so there the buzz turns into stutter.
The polling and the 2 ms are measured in IRIX runs, not documented, and
only IRIX was measured. An engine that takes its interrupt, or whose
position nobody reads, is not held. The risk is a guest that masks the
interrupt, polls further apart than 1.5 ms and writes a whole ring
ahead: it is held to 1.5 ms of audio per poll, so 10 ms polls would play
at about 15% of real time, with gaps. No such guest is known to run.

Cost of the read-ahead: a read of an engine's address or count register
stores the time and frame count the guest saw (a compare on the offset
and one store under the lock the read already holds), and each engine
sync takes one extra branch on that record. Both are in this device's
own code, which exists only on a Tezro with audio switched on, so no
other device and no CPU path pays for it. It was not timed; on arm64
the stores are plain ones, and the instruction count is a handful per
register read.

Without a host audio device in the build container, whether the output
is audible and what the samples sound like are unverified. The subsystem
ID is the DM8 board's recipe, a model choice: the Tezro board's own is
not known.

Tests cover the register contract and access widths, the engines' timing
against a fake clock, the read-ahead against a modelled IRIX writer, the
sink, that no transfer holds the state lock, and the card on a real bus
behind a PCI-to-PCI bridge.
Tezro hardware listings print an ICE1724 audio controller at device 2,
beside the TI PCI-to-PCI bridge, and the bridge was the one device on
the IO9 with an empty secondary bus. By exclusion the controller sits
at device 2 of that bus. With it fitted, IRIX attaches it: hinv lists
an Iris Audio Processor (MAD revision 1), /hw/audio/MAD1 appears, and
sfplay moves frames.

Add [tezro] audio, off by default so the empty bus stays the safe
behaviour. When set, the controller is fitted at device 2 of the
bridge's secondary bus, started and stopped with the board, given the
machine's timers, and played to the host as [audio] says, or silently
under no_audio. It takes the DM8 board's converters, subsystem pair and
configuration EEPROM, the one design with a documented recipe; the
Tezro board's own are unknown. The reset state masks every interrupt
source, so nothing reaches the PIC unless a guest unmasks one.

The monitor lists the controller and prints what it has done: the
engines' frame counts, the converter writes and the count of accesses
nothing establishes. Without a host audio device in a build container,
that is the only way to tell a guest that plays from one that does not.

With no_audio set the card is still fitted, so the guest sees an audio
processor and plays to it, and nothing comes out: no stream is opened and
nothing said why. Someone who sets both keys sees silence and has no
clue. Say so twice: iris prints one line at startup when it fits the
card with no_audio set, and --validate warns about the same combination.
Both use one message.

A function at device 2 behind the bridge raises INTC of the bridge's slot
once the bridge's pin rotation has carried it, and the PIC routed only
INTA, so attaching it panicked the IRIX guest on an unrouted pin. INTC of
device d now goes to the same input as its INTA, d - 1, as IRIX's pin rule
gives. No function on the IO9 raises INTC itself, so the machines without
the card are unchanged; INTB and INTD stay unwired because nothing
establishes them. A PIC test with a function behind a bridge at device 2
fails without it.

Tests place the controller on the board and read its identity, class,
subsystem pair and interrupt pin through the bridge's bus, check the bus
stays empty without the key, and check the status text. The CHANGELOG
gets the one bullet: the key is new and a user sets it.
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