Skip to content

t8030: opt-in audio hardware (audio=on, audiodev=) - #24

Merged
dylanneve1 merged 2 commits into
masterfrom
audio/mca-codec-stubs
Oct 22, 2026
Merged

dylanneve1 merged 2 commits into
masterfrom
audio/mca-codec-stubs

Conversation

@dylanneve1

@dylanneve1 dylanneve1 commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

For Scion-Project/scion#91 (audio milestone 1; plan in ios-on-pixel docs/AUDIO.md).

Upstream already models the MCA audio controller (hw/audio/mca.c), the CS42L77 codec and the CS35L27 amp, but t8030 deletes every audio node from the device tree, so iOS never loads its audio drivers. The MCA also can't be pointed at a backend: with any -audiodev on the command line it printed "Failed to create QEMU sound card for Multi-Channel Audio".

Changes

  • -M t8030,audio=on[,audiodev=<id>]: audio=on keeps the mca-switch,t8030, mcaCluster,t8030, mca,t8030, audio-control/audio-data,cs42l77, audio-control/audio-data,cs35l27 and audio,embedded-resource-manager nodes (new AUDIO_COMP list in boot.c, gated by AppleBootInfo.audio) and creates the MCA, codec and amp models. audiodev= selects the backend. audiodev= without audio=on is an error.
  • Default off. Without audio=on the guest gets the same device tree as before. The unused MCA/codec/amp models are no longer created.
  • MCA is now a realized qdev with an audiodev property (DEFINE_AUDIO_PROPERTIES). The voice opens in realize, and errors go through errp.
  • Trace events (apple_mca_*, apple_cs35l27_*, apple_cs42l77_cmd) replace the #if 0 printfs.

Verification (macOS host build, iOS 14.0 18A5351d, real SEP, throwaway qcow2 overlay)

  • audio=on,audiodev=snd0 -audiodev wav,id=snd0,...: a fresh VM runs data migration and boots to Setup Assistant ("Hello") with no panic. The traces show iOS's audio drivers probing the hardware: the MCA switch programs all 6 clusters (MCLK config, units 0x300/0x500 cleared), AppleCS42L77Audio reads the chip ID over SPI3, and AppleCS35L27Amp reads its device ID over I2C2.
  • The same VM without the option boots as before (baseline run).
  • Not verified yet: any playback. Nothing plays at Setup Assistant, and I stopped before triggering a system sound, so the wav capture is empty. That is milestone 2 (ios-on-pixel#92). The amp's secSpeaker_control and the codec's AOP serial port point at the AOP audio service, which is still not instantiated.
  • Android cross build: not built locally (low disk). This PR's CI covers it. The change is plain C with no new dependencies.

One caveat for whoever tests this: hard-quitting a VM soon after boot desynced the SEP anti-replay state, and the next boot panicked with SEP Panic: :sars/sars. It looks state-related, not caused by this change: a fresh state with audio=on booted cleanly. I didn't rerun the panicking state with audio off, though. Use throwaway overlays.


Update: rebased on master and combined with MakrSas/Inferno's audio work

  • Rebased onto master d9118068e5. audio=on gating sits beside master's gpu=on gating (apple_boot_process_dt_node(…, keep_gpu, audio)). The t8101 device list gates the MCA and speaker in the same way.
  • New commit d3187aa204, ported from MakrSas/Inferno ios (commits 102ac5d, a06b785, 6fbf9c0, 8398566, 58f0040, 683bc40, c01889c; author Makr). MakrSas reports playback and recording working end to end with these. Here everything is behind audio=on, where MakrSas used the INFERNO_AUDIO env var:
    • The AOP audio service is created. It answers attach/detach, the IO-handler count, the version and buffer sizes, tracks requested power states and answers RegisterAccess. Replies are built in a local buffer and clipped to the guest's buffer. GetDeviceID now has a bounds check (new here).
    • AUDIO_COMP contains the full tree the audio server needs: AOP audio nodes, audio-aop-mca2, the LP mic, the CS42L77 data nodes, the alc links and the haptics chain (without the actuator the route comes up empty, nort). aop-mca is exempt from REM_NAMES under audio=on.
    • A 5 ms drain completes transfers on the I2S DMA channels other than the speaker's, so the audio server isn't watchdog-killed.
    • MCA: the capture side runs on its own clock, and the timer keeps consuming samples while the unit is stopped. A TX reset reads back as done (no "reset SAC fail"), and the DMA FIFO is flushed on reset and on enable.
    • CS42L77: accesses past the register map read as 0 and writes are dropped.
  • MakrSas's SIO stop-completion and AOP reply-length fixes affect every boot, so they are in a separate PR, aop, sio: reply-length heap overflow and stop completion (port from MakrSas/Inferno) #59. Clean stream stops need aop, sio: reply-length heap overflow and stop completion (port from MakrSas/Inferno) #59.

Testing of the combined branch (Android build, emulator-5554, plus #59)

  • Restore boot (restore_bench.py, iOS 14.0 18A5351d restore, real SEP, -smp 4):
    • Default (audio off): 3/3 runs reached waiting_host, no panic, no audio lines in serial.
    • audio=on: 3/3 reached waiting_host, no panic. Serial shows the AOP audio controller finding 13 devices, the PCM asset manager AKCI, AppleEmbeddedAudioResourceManager registering, and AppleCS42L77Audio starting. The DSP firmware load times out, which is expected with no userspace in restore mode.
  • patch-check: identical to master. a13-test: 29/29.
  • Not tested: playback in a full iOS boot.

🤖 Generated with Claude Code

dylanneve1 added a commit that referenced this pull request Oct 22, 2026
* tcg/aarch64: two fewer instructions in the softmmu TLB fast path

Every guest load and store runs the inline TLB lookup. On an AArch64
host it was

    sub  x16, x19, #ofs           ; &CPUTLBDescFast
    ldp  x16, x17, [x16]          ; mask, table
    and  x16, x16, addr, lsr #s   ; index
    add  x17, x17, x16            ; &CPUTLBEntry
    ldr  x16, [x17, #cmp]         ; addr_read or addr_write
    ldr  x17, [x17, #24]          ; addend
    (add  tmp, addr, #size-1)     ; unaligned accesses only
    and  tmp, addr|tmp, #pagemask
    cmp  x16, tmp
    b.ne slow

The SUB is not needed: CPUTLBDescFast[] is the end of
CPUNegativeOffsetState, so every mmu index's {mask,table} is within
LDP's scaled 7-bit immediate of env (-424..-24 bytes with 26 modes);
keep SUB+LDP only as the fallback for a larger offset. And with the
addend moved between addr_read and addr_write in CPUTLBEntry, one LDP
loads either comparator together with the addend (loads: [read,
addend] at +0; stores: [addend, write] at +8). The fast path is 8
instructions (+1 for unaligned accesses) instead of 10 (+1), and one
dependent instruction shorter.

CPUTLBEntry.addr_idx[] is now indexed through TLB_CMP_IDX(access_type);
tlb_read_idx and tlb_set_compare are its only users, the other TCG
backends address the fields by name. Nothing guest-visible changes.

Also adds two a13-test microbenchmarks for the softmmu path:
w10 (320M loads/stores that hit the TLB) and w11 (4M loads from 16384
distinct pages: a TLB refill each).

* hw/arm/tests: describe w10/w11 in 16 KiB TLB pages (a13-test's page size)

---------

Co-authored-by: Claudius <claudiusthebot@gmail.com>
Upstream already models the MCA audio controller, the CS42L77 codec and
the CS35L27 amp, but t8030 deletes every audio node from the device tree,
so iOS never loads its audio drivers, and the MCA's sound card can't be
pointed at a backend (with any -audiodev on the command line it failed
with "Failed to create QEMU sound card").

- New machine options: audio=on keeps the MCA switch/cluster, CS42L77,
  CS35L27 and audio resource manager nodes and creates the models;
  audiodev=<id> picks the -audiodev for the speaker output. Default off:
  without audio=on the guest sees the same device tree as before and the
  (unused) audio models are no longer created.
- MCA: a realized qdev with an "audiodev" property; the voice is opened
  in realize and errors are reported instead of printed.
- Trace events for the MCA, CS35L27 and CS42L77 instead of #if 0 printfs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…=on (port from MakrSas/Inferno)

Folds MakrSas/Inferno's audio work (branch ios) into Pyre's opt-in audio=on.
MakrSas reports playback and recording working end to end from an iOS app to
the emulator's audio backend with it. Everything here applies only with
-M t8030,audio=on; without it the guest sees the same device tree as before
and none of these models are created.

- The AOP audio service (hw/audio/aop-audio.c) is created with audio=on.
  MakrSas gated it on the INFERNO_AUDIO environment variable; here it is the
  machine property. The service now:
  * reports one IO handler and a version, and answers attach/detach and
    the buffer-size question, so AppleAOPAudioController no longer fails
    _getBuffer and re-handshakes forever;
  * records the power state the guest requests (DEV_PROP_REQUESTED_STATE)
    and reports it back, so the codec's MCLK manager stops re-requesting;
  * answers RegisterAccess with zeros;
  * builds replies in a local buffer and copies out only as much as the
    guest's buffer holds, instead of writing past a smaller one;
  * bounds-checks GetDeviceID's index (new here).
  The debug printfs stay off (#if 0).
- boot.c: AUDIO_COMP grows to the whole tree MakrSas found the audio server
  needs: the AOP audio nodes, audio-aop-mca2, the low-power mic, the
  CS42L77's halogen/hawking/dsp-debug1/mikeybus/loopback data nodes, the
  alc links and the haptics chain (without the actuator the built-in route
  is empty, so AVAudioSession throws 'nort'). The aop-mca name is exempt
  from REM_NAMES under audio=on. MakrSas kept audio-aop-mca2 in KEEP_COMP for
  every boot; here it is in AUDIO_COMP.
- t8030: a 5 ms timer drains every I2S SIO DMA channel except the
  speaker's, so transfers queued on ports that nothing models complete and
  the audio server is not killed by its watchdog.
- MCA: the capture side runs on its own clock; the timer keeps running while
  the unit is stopped and throws samples away, so queued transfers still
  complete; it is armed at realize. A TX unit reset reads back as done
  ("reset SAC fail" panic), and the DMA FIFO is emptied on reset and on the
  enabling edge (fifoCheckEmpty).
- CS42L77: transfers past the end of the register map read as zero and
  writes are dropped, instead of overrunning regs[].

Ported from MakrSas/Inferno commits 102ac5d, a06b785, 6fbf9c0,
8398566, 58f0040, 683bc40 and c01889c by Makr
<ngrom33@gmail.com>, under the same licences as the files they touch. The
SIO stop fix (2a79c19) and the AOP reply-length fix (181c83d), which
MakrSas found through audio but which affect every machine, are in a
separate PR. MakrSas's INFERNO_AUDIO_DMA_TRACE debug dump is not ported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dylanneve1
dylanneve1 force-pushed the audio/mca-codec-stubs branch from 12a5eb3 to d3187aa Compare October 22, 2026 19:26
@dylanneve1
dylanneve1 marked this pull request as ready for review October 22, 2026 19:28
@dylanneve1
dylanneve1 merged commit d8d4309 into master Oct 22, 2026
5 checks passed
@dylanneve1
dylanneve1 deleted the audio/mca-codec-stubs branch November 2, 2026 01:56
dylanneve1 pushed a commit that referenced this pull request Nov 8, 2026
Every guest load and store runs the inline TLB lookup. On an AArch64
host it was

    sub  x16, x19, #ofs           ; &CPUTLBDescFast
    ldp  x16, x17, [x16]          ; mask, table
    and  x16, x16, addr, lsr #s   ; index
    add  x17, x17, x16            ; &CPUTLBEntry
    ldr  x16, [x17, #cmp]         ; addr_read or addr_write
    ldr  x17, [x17, #24]          ; addend
    (add  tmp, addr, #size-1)     ; unaligned accesses only
    and  tmp, addr|tmp, #pagemask
    cmp  x16, tmp
    b.ne slow

The SUB is not needed: CPUTLBDescFast[] is the end of
CPUNegativeOffsetState, so every mmu index's {mask,table} is within
LDP's scaled 7-bit immediate of env (-424..-24 bytes with 26 modes);
keep SUB+LDP only as the fallback for a larger offset. And with the
addend moved between addr_read and addr_write in CPUTLBEntry, one LDP
loads either comparator together with the addend (loads: [read,
addend] at +0; stores: [addend, write] at +8). The fast path is 8
instructions (+1 for unaligned accesses) instead of 10 (+1), and one
dependent instruction shorter.

CPUTLBEntry.addr_idx[] is now indexed through TLB_CMP_IDX(access_type);
tlb_read_idx and tlb_set_compare are its only users, the other TCG
backends address the fields by name. Nothing guest-visible changes.

Also adds two a13-test microbenchmarks for the softmmu path:
w10 (320M loads/stores that hit the TLB) and w11 (4M loads from 16384
distinct pages: a TLB refill each).
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