Skip to content

RME Fireface 800: Stage 5C-5H bounded circular playback integration#83

Open
Clip4food wants to merge 7 commits into
mrmidi:mainfrom
Clip4food:feature/rme-fireface800
Open

RME Fireface 800: Stage 5C-5H bounded circular playback integration#83
Clip4food wants to merge 7 commits into
mrmidi:mainfrom
Clip4food:feature/rme-fireface800

Conversation

@Clip4food

Copy link
Copy Markdown

Summary

  • Adds a full RME Fireface 800 device protocol (ASFWDriver/Audio/Protocols/RME/) and the no-CIP (tag=0) isoch wire-format support it depends on across Audio/Wire/AMDTP, Shared/Isoch/IsochAudioTransport.hpp, and the Isoch transmit path (IsochTxDmaRing, IsochTransmitContext, IsochService).
  • Discovery: FWDevice/DeviceRegistry recover model_id from the Unit Directory (RME publishes it there, not the root directory).
  • Core Audio: publishes a read-only "RME Fireface 800 (Stage 5C)" endpoint (192 kHz, 12 in/12 out); StartIO deliberately stays rejected (kIOReturnUnsupported) after a single bounded Stage 5H integration burst — no live streaming yet.
  • Stage 5H: reuses the Stage 5G-validated 48-descriptor D-D-D-S silence cadence as a circular ring, runs it for a bounded 100 ms with RUN/no-interrupt polling, then tears down OHCI → FF800 → IRM in order.
  • Fixes a real bug found during hardware testing: ReservePlaybackResourcesPreflight's IRM snapshot read raced IRMClient's own IRM-node resolution right after a bus reset (NotFound while irmNodeId_ == 0xFF), permanently stranding the playback route with no retry. Now retries up to 10x/50ms, mirroring the existing PollDeviceTxIsochChannel idiom.

Heads up for review

  • Branched off 405cb99; main has since moved ~18 commits (notably refactor(isoch): make transmit queue payload opaque and the isoch receive-seam refactors), so this will likely need a rebase/conflict pass before merge — flagging so it's not a surprise.
  • I'm not the repo owner and don't have write access, so this comes from a fork.

Test plan

  • ./build.sh --test-only — 1334/1335 C++ tests pass (1 pre-existing unrelated failure: IsochDMAMemoryManagerTest.PayloadSlicingAndPageAlignment, page-alignment assumption in the host-test memory stub).
  • ./build.sh --config Release (Xcode, x86_64) + ./sign.sh — builds and signs cleanly.
  • Installed on real hardware (TI 104C:823F + RME Fireface 800, GUID 0x000a3500f021a1b4): Stage 5H's bounded circular silent playback burst (48 descriptors, 36 data packets, 12 skips, 100 ms) passed cleanly across a cold boot, a protocol reinit, and a forced bus reset — clean OHCI/FF800/IRM teardown each time, zero event errors, zero DEAD.
  • The specific IRM-NotFound-retry branch fixed above did not get re-exercised live (the race window is narrow and didn't reproduce on demand) — verified by code inspection and the original incident log instead.

🤖 Generated with Claude Code

MM added 7 commits July 20, 2026 03:43
… 5G/5H)

Safety commit of uncommitted RME work before continuing Stage 5H
development, per user request. Scope is intentionally narrow:
RMEFireface800Protocol (new) and the Isoch transmit-path changes it
depends on. Related supporting edits (DeviceProtocolFactory,
AudioProfileRegistry, device IDs, RMEAudioProfiles.hpp, etc.) remain
uncommitted in the working tree and were left out of this snapshot.
… and test build fix

Rounds out the earlier RME/Isoch backup commit with everything that
feature depends on to actually compile and run:

- No-CIP (tag=0) isoch header support end to end: IsochAudioTransport
  header build/decode helpers, includeCipHeader plumbed through
  AmdtpStreamConfig/AmdtpTxPacketizer/AudioStreamConfig/
  DiceStreamConfigMapper, and the OHCI TX descriptor path.
- Core Audio wiring for the RME read-only nub: AudioCoordinator
  publishes/tears down the Stage 5C endpoint, ASFWAudioDevice enters
  the Stage 5C preflight-only StartIO/StopIO path, ASFWAudioDriverZts
  primes the FF800 blocking cadence instead of NO-DATA.
- Discovery: FWDevice parses Model_ID (0x17) from the Unit Directory,
  DeviceRegistry recovers model_id from the raw Config ROM as a
  fallback (RME publishes it in the Unit Directory, not the root),
  DeviceManager refreshes identity on rediscovery.
- DeviceProtocolFactory/IDeviceProtocol/AudioProfileRegistry/
  AudioDeviceIds/AudioProfileTypes/RMEAudioProfiles: RME identity,
  kReadOnlyNub integration mode, and the bounded playback preflight
  seam (PlaybackPreflightRoute / RunBoundedPlaybackIntegrationPreflight)
  the Stage 5G/5H protocol implementation calls into.
- Info.plist: IOPCIMatch for this bench's TI 104C:823F controller,
  ASFWAutoStartAudioStreams=false (StartIO stays rejected per the
  Stage 5G/5H invariant). DriverContext.cpp: interrupt setup gets
  MSI-X/MSI/fallback diagnostics for bring-up on this board.
- project.pbxproj/ASFW.xcscheme: adds the new RME/RMEAudioProfiles
  file references so Xcode builds them. Hand-edited because xcodegen
  isn't installed in this environment -- re-run `xcodegen generate`
  once it is, to get a canonical regenerated project and diff-check
  it against this.
- tests/{protocols,devices}/CMakeLists.txt: add ConfigROMParser.cpp
  to the three test targets that compile DeviceRegistry.cpp, which
  now calls ConfigROMParser::ParseDirectory (previously an unresolved
  symbol at link time for SessionRegistryTests/SBP2HandlerTests/
  AudioDuplexCoordinatorTests).
Replaces the hand-edited project.pbxproj from the previous commit with
a canonical `xcodegen generate` output now that Homebrew + xcodegen are
installed on this machine. Also restores ASFWDriver.xcscheme, which
project.yml already declares (schemes.ASFWDriver) -- its earlier
deletion just meant every future `xcodegen generate` would keep
recreating it as a diff.

The stray backup/OLD artifacts (Info.plist.original,
DriverContext.cpp.backup-*, DMAMemoryManager.cpp.backup-dma,
ASFWDriverOLD.zip, ASFW.xcodeprojOLD/, projectOLD.pbxproj) were
deleted before regenerating -- they lived under ASFWDriver/, which
project.yml globs in full, so a prior generate had folded them into
the dext target as build Resources. That is almost certainly what
forced the earlier hand-edit workaround in the first place.
Hardware testing on the Fireface 800 (Stage 5H build) surfaced a real
bug: right after a bus reset, ReservePlaybackResourcesPreflight's IRM
snapshot read can race IRMClient's own IRM-node resolution --
IRMClient::ReadIRMWindow returns NotFound while irmNodeId_ is still
0xFF, before bus manager/Self-ID processing has caught up. There was
no retry, so playbackPreflightRoute_ stayed at 0 forever and every
subsequent StartIO logged "verified IRM route is not ready" until the
next full protocol reinit. Reproduced live: a mid-session bus reset
left the route permanently unarmed on the unpatched build.

Mirrors the existing PollDeviceTxIsochChannel bounded retry idiom in
this same file: up to 10 attempts, 50ms apart, before giving up.

Confirmed on hardware after the fix: Stage 5H's bounded circular
silent playback burst (48 descriptors, 100ms, clean OHCI/FF800/IRM
teardown) passes cleanly on a fresh dext load. The IRM snapshot
happened to succeed on first read this run (no NotFound race hit), so
the retry branch itself is unexercised by this specific run; still
pending is forcing another bus reset while the dext is live to confirm
the retry path recovers instead of stranding the route again.
Turns the bounded 100ms Stage 5H circular silence burst into a maintained
continuous stream, reachable only through a new developer-gated
UserClient/MCP path -- never through real Core Audio StartIO, which keeps
returning kIOReturnUnsupported unchanged.

Reuses the Stage 5H self-looping 48-descriptor D-D-D-S ring builder
verbatim (it was already duration-agnostic; only the run/cleanup entry
points were bounded). New transport-layer methods start the ring and
return immediately instead of polling for a fixed window, plus a
non-blocking health check (DEAD/event-error/anchor-advance) reused by
both a new Watchdog heartbeat (~1s, anomaly-only logging) and an
on-demand UserClient query.

RMEFireface800Protocol gains Start/Stop/Health hooks mirroring
RunBoundedPlaybackIntegrationPreflight's shape, guarded by a separate
continuousPlaybackInFlight_ flag checked alongside stage5hInFlight_ (same
OHCI context/IRM route, must never run both at once). Extracted the
FF800-stop+IRM-release tail into StopDeviceEngineAndReleaseIrm so it's
shared instead of duplicated between the bounded and continuous paths.
IsochTransmitContext::Stop() now stops an in-flight continuous cadence
too, since it deliberately never enters State::Running and would
otherwise be missed by driver teardown.

New UserClient selectors 64-66 (GUID-keyed, full 64-bit -- the existing
kMethodStartAudioStreaming=62/63 are an unrelated generic AV/C developer
path and hard-reject RME already) plumbed through to new MCP tools
asfw_continuous_tx_{start,stop,health}_dev, mirroring the existing
asfw_bus_reset_dev developer-trigger pattern.

Verified: full build (dext + Swift app) succeeds, ./build.sh --test-only
stays at 1334/1335 (same pre-existing unrelated failure, no regression).
Hardware verification of the continuous run itself is still pending --
blocked on a stuck systemextensionsctl uninstall requiring a reboot.
…cation

StartContinuousIsochTxSilence's prepareHost step called IsochService::
PrepareTransmit directly, which requires the shared payload/metadata/
control memory to already exist. Stage 5H never had this problem because
it only ever runs from inside a real Core Audio StartIO, which allocates
that memory via AllocateTxIsochResources before ever reaching the RME
branch. This dev-only path is driven straight from UserClient with no
CoreAudio session, so the buffer was never allocated -- every start
attempt failed with kIOReturnNotReady (confirmed live: kr=0xe00002d8).

Fix: allocate it explicitly with the same fixed no-CIP RME wire geometry
validated since Stage 5A (1536 bytes/packet: 32 frames x 12 dbs x 4
bytes, no CIP header) before calling PrepareTransmit, and free it
symmetrically on stop.

Also bumps ASFWDriver's CURRENT_PROJECT_VERSION from 2 to a fixed 100.
Every iteration during Stage 6 development produced a dext with the same
CFBundleVersion as whatever was already active system-extension-side;
OSSystemExtensionRequest won't replace an equal-or-lower version, so the
only path forward was systemextensionsctl uninstall -- which got stuck
in "terminating for uninstall but still running" (recoverable only by a
full reboot) three separate times this session. A comfortably high fixed
value means future installs are a normal automatic upgrade instead.
The buffer-allocation fix in StartContinuousIsochTxSilence (previous
commit) has not been exercised end to end on hardware yet. Every live
attempt this session failed earlier, at GetPlaybackPreflightRoute()
returning false, before ever reaching this function's body -- something
on the system (observed pattern consistent with coreaudiod) wins the
race for the single-use Stage 5F playback route within about a second
of every bus reset, ahead of this dev-only MCP-triggered path's three
sequential HTTP round trips. Not a Stage 6 regression: Stage 5H shows
the identical symptom whenever something beats it to the route.

Documented in place so the next session doesn't have to rediscover
this; two candidate next steps noted in the comment.
@mrmidi

mrmidi commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Hi and thanks for contribution! I'm looking into it. I want to finish some architecture shifts before actually merging/resolving conflicts and fixing some critical bugs in FireWire driver itself, so please wait some time.

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