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

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.

@Dreambrother7

Copy link
Copy Markdown

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and
we've independently solved the same problem — device identity that isn't in the root
directory — with two different mechanisms, in the same function.

Your side: FindUnitModelIdInRawROM + the PopulateDeviceIdentity change, recovering
model_id from the unit directory because RME publishes it there.

My side (1c6ab94): I added
unitSpecId/unitSwVersion to
DeviceProfileQuery
and pass them through at
DeviceRegistry.cpp:52-53,
so the profile providers can match on the unit directory directly
(MOTU provider).

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in
Discovery/DeviceRegistry.cpp, so whoever rebases second gets a conflict — flagging it now
rather than at merge time.

Worth noting the two aren't equivalent, and I don't think either subsumes the other:

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

So the general shape is probably "surface the whole unit directory identity to the profile
layer, and let each provider pick the field its family actually keys on" — model_id for
RME, sw_version for MOTU. If that sounds right I'm happy to rebase mine onto yours (or
the other way round, whichever lands first) and fold both into one mechanism.

@mrmidi — no action needed from you unless you want to call the shape; just didn't want two
contributors quietly diverging on the same discovery seam.

@mrmidi

Copy link
Copy Markdown
Owner

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and we've independently solved the same problem — device identity that isn't in the root directory — with two different mechanisms, in the same function.

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

Great findings. Basically that's why Discord server exists for — to share the knowledge and solve the same problems together. :)

Sign up for freeto 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.

3 participants

@Clip4food@mrmidi@Dreambrother7
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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

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.

@Dreambrother7

Copy link
Copy Markdown

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and
we've independently solved the same problem — device identity that isn't in the root
directory — with two different mechanisms, in the same function.

Your side: FindUnitModelIdInRawROM + the PopulateDeviceIdentity change, recovering
model_id from the unit directory because RME publishes it there.

My side (1c6ab94): I added
unitSpecId/unitSwVersion to
DeviceProfileQuery
and pass them through at
DeviceRegistry.cpp:52-53,
so the profile providers can match on the unit directory directly
(MOTU provider).

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in
Discovery/DeviceRegistry.cpp, so whoever rebases second gets a conflict — flagging it now
rather than at merge time.

Worth noting the two aren't equivalent, and I don't think either subsumes the other:

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

So the general shape is probably "surface the whole unit directory identity to the profile
layer, and let each provider pick the field its family actually keys on" — model_id for
RME, sw_version for MOTU. If that sounds right I'm happy to rebase mine onto yours (or
the other way round, whichever lands first) and fold both into one mechanism.

@mrmidi — no action needed from you unless you want to call the shape; just didn't want two
contributors quietly diverging on the same discovery seam.

@mrmidi

Copy link
Copy Markdown
Owner

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and we've independently solved the same problem — device identity that isn't in the root directory — with two different mechanisms, in the same function.

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

Great findings. Basically that's why Discord server exists for — to share the knowledge and solve the same problems together. :)

Sign up for freeto 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.

3 participants

@Clip4food@mrmidi@Dreambrother7
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

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.

@Dreambrother7

Copy link
Copy Markdown

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and
we've independently solved the same problem — device identity that isn't in the root
directory — with two different mechanisms, in the same function.

Your side: FindUnitModelIdInRawROM + the PopulateDeviceIdentity change, recovering
model_id from the unit directory because RME publishes it there.

My side (1c6ab94): I added
unitSpecId/unitSwVersion to
DeviceProfileQuery
and pass them through at
DeviceRegistry.cpp:52-53,
so the profile providers can match on the unit directory directly
(MOTU provider).

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in
Discovery/DeviceRegistry.cpp, so whoever rebases second gets a conflict — flagging it now
rather than at merge time.

Worth noting the two aren't equivalent, and I don't think either subsumes the other:

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

So the general shape is probably "surface the whole unit directory identity to the profile
layer, and let each provider pick the field its family actually keys on" — model_id for
RME, sw_version for MOTU. If that sounds right I'm happy to rebase mine onto yours (or
the other way round, whichever lands first) and fold both into one mechanism.

@mrmidi — no action needed from you unless you want to call the shape; just didn't want two
contributors quietly diverging on the same discovery seam.

@mrmidi

Copy link
Copy Markdown
Owner

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and we've independently solved the same problem — device identity that isn't in the root directory — with two different mechanisms, in the same function.

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

Great findings. Basically that's why Discord server exists for — to share the knowledge and solve the same problems together. :)

Sign up for freeto 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.

3 participants

@Clip4food@mrmidi@Dreambrother7
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

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.

@Dreambrother7

Copy link
Copy Markdown

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and
we've independently solved the same problem — device identity that isn't in the root
directory — with two different mechanisms, in the same function.

Your side: FindUnitModelIdInRawROM + the PopulateDeviceIdentity change, recovering
model_id from the unit directory because RME publishes it there.

My side (1c6ab94): I added
unitSpecId/unitSwVersion to
DeviceProfileQuery
and pass them through at
DeviceRegistry.cpp:52-53,
so the profile providers can match on the unit directory directly
(MOTU provider).

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in
Discovery/DeviceRegistry.cpp, so whoever rebases second gets a conflict — flagging it now
rather than at merge time.

Worth noting the two aren't equivalent, and I don't think either subsumes the other:

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

So the general shape is probably "surface the whole unit directory identity to the profile
layer, and let each provider pick the field its family actually keys on" — model_id for
RME, sw_version for MOTU. If that sounds right I'm happy to rebase mine onto yours (or
the other way round, whichever lands first) and fold both into one mechanism.

@mrmidi — no action needed from you unless you want to call the shape; just didn't want two
contributors quietly diverging on the same discovery seam.

@mrmidi

Copy link
Copy Markdown
Owner

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and we've independently solved the same problem — device identity that isn't in the root directory — with two different mechanisms, in the same function.

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

Great findings. Basically that's why Discord server exists for — to share the knowledge and solve the same problems together. :)

Sign up for freeto 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.

3 participants

@Clip4food@mrmidi@Dreambrother7
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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

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.

@Dreambrother7

Copy link
Copy Markdown

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and
we've independently solved the same problem — device identity that isn't in the root
directory — with two different mechanisms, in the same function.

Your side: FindUnitModelIdInRawROM + the PopulateDeviceIdentity change, recovering
model_id from the unit directory because RME publishes it there.

My side (1c6ab94): I added
unitSpecId/unitSwVersion to
DeviceProfileQuery
and pass them through at
DeviceRegistry.cpp:52-53,
so the profile providers can match on the unit directory directly
(MOTU provider).

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in
Discovery/DeviceRegistry.cpp, so whoever rebases second gets a conflict — flagging it now
rather than at merge time.

Worth noting the two aren't equivalent, and I don't think either subsumes the other:

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

So the general shape is probably "surface the whole unit directory identity to the profile
layer, and let each provider pick the field its family actually keys on" — model_id for
RME, sw_version for MOTU. If that sounds right I'm happy to rebase mine onto yours (or
the other way round, whichever lands first) and fold both into one mechanism.

@mrmidi — no action needed from you unless you want to call the shape; just didn't want two
contributors quietly diverging on the same discovery seam.

@mrmidi

Copy link
Copy Markdown
Owner

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and we've independently solved the same problem — device identity that isn't in the root directory — with two different mechanisms, in the same function.

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

Great findings. Basically that's why Discord server exists for — to share the knowledge and solve the same problems together. :)

Sign up for freeto 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.

3 participants

@Clip4food@mrmidi@Dreambrother7
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

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.

@Dreambrother7

Copy link
Copy Markdown

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and
we've independently solved the same problem — device identity that isn't in the root
directory — with two different mechanisms, in the same function.

Your side: FindUnitModelIdInRawROM + the PopulateDeviceIdentity change, recovering
model_id from the unit directory because RME publishes it there.

My side (1c6ab94): I added
unitSpecId/unitSwVersion to
DeviceProfileQuery
and pass them through at
DeviceRegistry.cpp:52-53,
so the profile providers can match on the unit directory directly
(MOTU provider).

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in
Discovery/DeviceRegistry.cpp, so whoever rebases second gets a conflict — flagging it now
rather than at merge time.

Worth noting the two aren't equivalent, and I don't think either subsumes the other:

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

So the general shape is probably "surface the whole unit directory identity to the profile
layer, and let each provider pick the field its family actually keys on" — model_id for
RME, sw_version for MOTU. If that sounds right I'm happy to rebase mine onto yours (or
the other way round, whichever lands first) and fold both into one mechanism.

@mrmidi — no action needed from you unless you want to call the shape; just didn't want two
contributors quietly diverging on the same discovery seam.

@mrmidi

Copy link
Copy Markdown
Owner

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and we've independently solved the same problem — device identity that isn't in the root directory — with two different mechanisms, in the same function.

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

Great findings. Basically that's why Discord server exists for — to share the knowledge and solve the same problems together. :)

Sign up for freeto 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.

3 participants

@Clip4food@mrmidi@Dreambrother7
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

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.

@Dreambrother7

Copy link
Copy Markdown

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and
we've independently solved the same problem — device identity that isn't in the root
directory — with two different mechanisms, in the same function.

Your side: FindUnitModelIdInRawROM + the PopulateDeviceIdentity change, recovering
model_id from the unit directory because RME publishes it there.

My side (1c6ab94): I added
unitSpecId/unitSwVersion to
DeviceProfileQuery
and pass them through at
DeviceRegistry.cpp:52-53,
so the profile providers can match on the unit directory directly
(MOTU provider).

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in
Discovery/DeviceRegistry.cpp, so whoever rebases second gets a conflict — flagging it now
rather than at merge time.

Worth noting the two aren't equivalent, and I don't think either subsumes the other:

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

So the general shape is probably "surface the whole unit directory identity to the profile
layer, and let each provider pick the field its family actually keys on" — model_id for
RME, sw_version for MOTU. If that sounds right I'm happy to rebase mine onto yours (or
the other way round, whichever lands first) and fold both into one mechanism.

@mrmidi — no action needed from you unless you want to call the shape; just didn't want two
contributors quietly diverging on the same discovery seam.

@mrmidi

Copy link
Copy Markdown
Owner

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and we've independently solved the same problem — device identity that isn't in the root directory — with two different mechanisms, in the same function.

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

Great findings. Basically that's why Discord server exists for — to share the knowledge and solve the same problems together. :)

Sign up for freeto 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.

3 participants

@Clip4food@mrmidi@Dreambrother7
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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

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.

@Dreambrother7

Copy link
Copy Markdown

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and
we've independently solved the same problem — device identity that isn't in the root
directory — with two different mechanisms, in the same function.

Your side: FindUnitModelIdInRawROM + the PopulateDeviceIdentity change, recovering
model_id from the unit directory because RME publishes it there.

My side (1c6ab94): I added
unitSpecId/unitSwVersion to
DeviceProfileQuery
and pass them through at
DeviceRegistry.cpp:52-53,
so the profile providers can match on the unit directory directly
(MOTU provider).

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in
Discovery/DeviceRegistry.cpp, so whoever rebases second gets a conflict — flagging it now
rather than at merge time.

Worth noting the two aren't equivalent, and I don't think either subsumes the other:

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

So the general shape is probably "surface the whole unit directory identity to the profile
layer, and let each provider pick the field its family actually keys on" — model_id for
RME, sw_version for MOTU. If that sounds right I'm happy to rebase mine onto yours (or
the other way round, whichever lands first) and fold both into one mechanism.

@mrmidi — no action needed from you unless you want to call the shape; just didn't want two
contributors quietly diverging on the same discovery seam.

@mrmidi

Copy link
Copy Markdown
Owner

Heads-up on a collision: I've just landed MOTU protocol-v2 support on a fork branch and we've independently solved the same problem — device identity that isn't in the root directory — with two different mechanisms, in the same function.

Both touch PopulateDeviceIdentity/MaybeInferKnownIdentityFromGuid in

  • Your model_id recovery would in fact work for MOTU — the 828mkII does publish
    Model_Id 0x101800 in its unit directory (I captured its ROM off hardware this week).
  • But MOTU's authoritative discriminator isn't model_id. Linux matches the whole family
    on VENDOR_ID | SPECIFIER_ID | VERSION — see SND_MOTU_DEV_ENTRY / motu_id_table in
    sound/firewire/motu/motu.c
    (motu.c:151-181). Unit_Sw_Version is what separates 828mkII (0x000003) from Traveler
    (0x000009) etc.; the unit model_id isn't used for matching there at all.

Great findings. Basically that's why Discord server exists for — to share the knowledge and solve the same problems together. :)

Sign up for freeto 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.

3 participants

@Clip4food@mrmidi@Dreambrother7