Add Mackie Onyx 400F (Echo Fireworks) support: recognition, publication, EFC control, duplex streaming - #107
ottendorfcipher wants to merge 18 commits into
Conversation
|
Reviewed this alongside #101. Three commits here are device-independent and fix bugs that are live on hardware unrelated to the 400F, so I've cherry-picked them into #117 rather than let them wait on the Fireworks review:
Two things worth telling you. First, the cycle-master one matters more than you probably knew — two other contributors hit the same wall bringing up MOTU hardware and worked around it in worse places (kDefaultLinkControl in one, the audio backend in the other). Yours is the only version that fixes it in the policy layer and re-arms per generation, so it's the only one that survives a bus reset. I checked IOFireWireController.cpp:3366-3367 and it's exactly as you describe. Second — I reproduced the ring-lap bug on my own hardware. Focusrite Saffire Pro 24 DSP, completely different protocol family: Could you rebase #101 and #107 onto main? Both are ~20 commits back, and #107 is stacked on #101 so it can't be reviewed independently. The rebase will drop those three as already-upstream. Mark them ready when you're happy and I'll review the device support on its own. |
…apture Mackie (LOUD Technologies, OUI 0x000ff2) shipped the Onyx-i mixers with two FireWire implementations over the production run: former models on Oxford OXFW971 (AV/C, Linux snd-oxfw) and latter models on TCAT DICE with the LOUD category quirk 0x10 (Linux snd-dice). No model id for the Onyx 820i is published in ALSA or libffado - both match Loud vendor-wide - so recognition is gated behind a kMackieModelIdPendingCapture sentinel that refuses to match until a Config-ROM capture from a real unit replaces kOnyx820iModelId. LookupAudioProfile intentionally stays nullopt even after capture, following the PreSonus StudioLive sibling policy: no audio enablement until the stream geometry is verified against hardware. References consulted behaviorally (no code copied): Linux sound/firewire Kconfig, oxfw/oxfw.c, dice/dice.c; FFADO-user thread confirming DICE-variant 820i units in the wild.
Adds recognition (identity-only) for the Onyx models whose Config-ROM ids are published: Onyx-i Oxford run (0x081216), Onyx 1640i in both runs (Oxford 0x001640, DICE 0x000006), and Onyx Blackbird (0x000007). Ids are from the libffado 2.5.0 device database; Blackbird is cross-checked against snd-firewire-ctl-services runtime/dice model mapping. Same recognition-without-hardware policy as the PreSonus StudioLive siblings; no Onyx model is audio-enabled. The DICE-run 820i/1220i/1620i ids are published nowhere and stay sentinel-gated pending a hardware capture. Also wires Mackie::Lookup* into AudioProfileRegistry: the previous commit added the include but omitted both dispatch calls. That omission was invisible while the provider was fully sentinel-gated (it matched nothing); positive-case constexpr checks caught it.
…dentity Field capture 2026-08-17 from a real Onyx 820i behind an Apple TB->FW643 chain: GUID 0x000FF20400003AFC, vendor 0x000FF2, model 0x081216, unit specifier 0x00A02D version 0x010001 (1394TA AV/C), ROM strings "Loud Technologies Inc." / "Onyx-i". Confirms the Oxford-run 820i reports the shared Onyx-i id already in the recognition table (libffado-sourced), and that the GUID category byte is 0x04 - the Loud DICE gate value 0x10 is absent, triple-confirming the OXFW971 variant. No functional change; comments only.
EXTENDED STREAM FORMAT INFORMATION sweep over the MCP control plane (STATUS-class FCP only): capture plug = 8ch MBLA compound AM824 at 44.1/48/88.2/96 kHz, playback plug = 2ch MBLA at the same rates, one isoch plug per direction, no MIDI fields. Current format 44.1 kHz both directions. Notes the Loud snd-oxfw quirks (unreliable DBS, blocking transmission) for the future enablement work.
First enablement milestone for the Onyx 820i (asymmetric 8-in/2-out,
geometry captured live from hardware 2026-08-17):
- MackieAudioProfiles: Onyx-i Oxford run (shared id 0x081216) now returns
{Oxford, kAVCDriven}; 1640i both runs and Blackbird stay recognition-only.
- New MackieOnyx820iProfile (ADK isoch geometry): asymmetric TX 2ch/DBS 2,
RX 8ch/DBS 8, blocking, 44.1 kHz only until the AV/C rate transition is
wired (M4). Dispatched ahead of the DICE GenericProfile catch-all so the
device can never inherit the symmetric 2x2 fallback geometry.
- DeviceStreamModeQuirks: LOUD vendor-wide forced blocking (Linux snd-oxfw
oxfw.c:189-196; snd-dice is unconditionally CIP_BLOCKING).
- DeviceProtocolFactory intentionally has no Mackie clause yet: CoreAudio
publication works, StartStreaming fails cleanly with kIOReturnNotReady
until the runtime duplex control lands (M2).
- Tests: registry/factory integration-mode pins, identity pins, and an
asymmetry pin (DiceProfileTests) locking 8-in/2-out against the catch-all.
Live v6 run proved the predicted M1 watch item one level deeper: the OXFW971's Music subunit exists but implements no descriptor mechanism (standard descriptor access NOT IMPLEMENTED, non-standard direct read refused, 0 plugs parsed, hasAudioCapability=0), so the generic descriptor-driven path can never publish this device. Linux snd-oxfw never consults the Music subunit on these chips either - formats live at the unit level, where the live 820i capture was taken. Adds the BeBoB-pattern bypass in HandleInitializedUnit: for the Onyx-i Oxford run, publish the hardware-verified profile-owned configuration directly (8-in/2-out, 44.1 kHz, blocking), matching MackieOnyx820iProfile geometry.
…irk (M2) Streaming support for the Onyx-i Oxford run (live-captured 8-in/2-out): - MackieOnyxProtocol: runtime duplex control on the shared AV/C+CMP base (deliberate cross-family reuse — the BeBoB base is protocol-neutral AV/C+CMP, and Linux drives OXFW rate changes with the identical INPUT/OUTPUT PLUG SIGNAL FORMAT command). Asymmetric caps 8/2, plug-0 CMP, PCR-connectivity health, 44.1 kHz only until a live rate transition is validated (M4). - DeviceProtocolFactory: Mackie clause -> StartStreaming now reaches a real duplex record instead of kIOReturnNotReady. - DuplexStreamProfile: Onyx joins the IRM any-channel mask (CMP commits the IRM-chosen channel to the PCR), BeBoB-style start order (RX before TX, no pre-stream clock lock - the device is SYT-unaware), and sets captureTrustConfiguredStride. - Loud WRONG_DBS quirk end to end: profile flag -> coordinator -> IsochDuplexHostTransport -> DirectAudioReceiveConsumer -> RxAudioPacketProcessor takes the RX stride from the configured slot count instead of the CIP header dbs (ALSA CIP_WRONG_DBS semantics, amdtp-stream.c:766-769). Header dbs stays in telemetry. - Tests: WrongDbsQuirkTakesStrideFromConfiguration pins both sides - the quirk decodes the lying-header packet, and the untrusted path shows the zero-events stalled-capture illusion the quirk fixes. Coordinator mock updated for the widened transport signatures.
First live start attempt failed with kIOReturnUnsupported at the Prepare stage: a first-ever start has no session clock, so the coordinator's desiredClock fell back to the hardcoded 48 kHz default, which the 44.1-only Onyx protocol correctly refused (IsRateSupported). Fix mirrors the Duet precedent: EffectiveStartClockForProfile pins the Onyx-i (Oxford) start clock to the device's captured current rate until the AV/C rate transition is validated on this hardware (M4).
First live streaming session reached full RX (966 packets, 68.8% data = textbook 44.1k blocking cadence, zero geometry mismatches) and a running TX engine - and the Onyx stamps usable SYT, so the RX-SYT-authoritative clock model works unmodified. But record-only sessions collapsed after a growing TX prep deficit (3.3k -> 5.7k frames over ~5s, five start/stop cycles): with emptyPacketsDuringIdle=false the idle output leg must fabricate data packets from an unfed ring. Adopt Phase88's field-validated policy on the same AV/C+CMP base: send CIP NO-DATA packets while output is idle.
Field observation on an 8 GB M3 (v10, 44.1 kHz playback): sessions stay up with zero faults and no restarts, but audio audibly drops in and out. Telemetry: 200 TxPrepFrame starvation markers across the run, host callback latency spiking to 922 us (median 17 us), prep margin dipping to 120 frames. The Duet placeholder offsets (explicitly marked unvalidated for this device) leave a 64-frame (~1.45 ms) TX safety offset - insufficient headroom against those spikes. Widen to 192/256 safety, 256/256 reported latency (~3 ms additional reported latency).
Widens the Onyx-i rate set to 44.1/48 kHz across the three agreeing sites (runtime protocol, ADK profile, published nub config). The rate transition itself is the base protocol's existing INPUT/OUTPUT PLUG SIGNAL FORMAT sequence - the same command snd-oxfw uses for OXFW rate control. Replaces the Onyx start-clock force-pin with a device-scoped start DEFAULT: EffectiveStartClockForProfile no longer pins the Onyx (that would clobber an explicit user rate selection); instead the coordinator's naked-start fallback (previously hardcoded 48 kHz at both call sites) resolves via DefaultStartRateForRecord, which returns the Onyx's captured current rate. Explicit selections still arrive via session clocks and take precedence. 88.2/96 kHz remain behind the driver-wide clock gate pending SYT_INTERVAL-16 validation.
Field-verified regression on the 820i (v11): audio played clean before the rate work and dropped in and out after it. Root cause, confirmed in code: AudioDuplexCoordinator::RequestClockConfig persists session.pendingClock BEFORE executing the request, and the failure path (FailPendingClockRequest -> clockRequests_.FailPending) completes the request bookkeeping without scrubbing the session. A rate change that fails at the ADK transport reconfig (unimplemented for AV/C static-profile devices, kIOReturnUnsupported) therefore leaves pendingClock=48000 armed; the next session start consumes it, and with 48 kHz newly present in SupportedRates the base protocol programs the device to 48 kHz via SIGNAL FORMAT while the host graph runs 44.1 - audible periodic dropouts ([TxExposure] reason=rate-mismatch). Device-scoped fix, restoring the semantics validated on hardware: - rate lists back to 44.1-only at all three agreeing sites (protocol, ADK profile, published nub config) so CoreAudio cannot initiate the failing change and ApplyClockConfig refuses any stale pending clock; - the Onyx start-clock pin restored in EffectiveStartClockForProfile (defense in depth against stale session clocks). The general pending-clock hygiene defect and the AV/C rate-change reconfig gap are reported separately; the rate-list widening returns with those fixes.
The Onyx 400F/1200F are Echo Fireworks units (Linux snd-fireworks VENDOR_LOUD 0x000ff2, MODEL_MACKIE_400F 0x00400f), not Oxford or DICE: control is EFC (block-write command at 0xECC0'0000'0000, device answers by block-writing to 0xECC0'8000'0000 in the host's space), streaming is plain CMP + AM824 blocking/SYT-unaware. Apple's AppleFWAudio was the only macOS driver and is gone since Ventura. - Efc codec: header/params framing, seqnum echo, HWINFO GET_CAPS and HWCTL GET/SET_CLOCK parameter layouts, status table. - EfcResponseMailbox + FireworksEFC local-address handler: claim the host response window and route frames to live transports. - EfcTransport: serial async transaction runner, 125 ms x 3 attempts, bus-reset/abort mapping, in-order queue. - FireworksProtocol on the BeBoB/CMP base: TRANSPORT SET_TX_MODE (IEC 61883) once per session, EFC-driven clock apply (read, write only on change, 150 ms settle) with a watchdog, HWINFO probe at Initialize and a geometry gate that refuses to stream when the device's 1x channel counts differ from the static profile. - Onyx 400F recognition, Fireworks profile family, AVCDiscovery bypass publishing a static 10x10 @ 44.1 kHz nub, MackieOnyx400FProfile ADK geometry, factory dispatch. 1200F is recognition-only. - 37 host tests (codec, transport, protocol). Never run against real hardware; the HWINFO log lines are the capture that confirms or corrects the 10x10 geometry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 725f312e5077ec74aa7d6c4bc499e085971cb15a)
…rdown Findings from the review of the initial backend: - EfcTransport state is reached from the Default, dice, and nub queues while responses arrive on the AR path and timeouts on the scheduler; guard it with an IOLock like FCPTransport/CMPClient, run completions and bus/timer calls with the lock released, and capture a weak lifetime token in every bus/timer callback so late completions after teardown are no-ops. CancelAll now cancels the outstanding AT write. - EfcResponseMailbox slots carry an in-flight counter; RemoveObserver waits (bounded) for a Publish already inside the observer. - The 400F has MIDI in/out, so the AM824 data block is pcm + 1 MIDI slot per direction (DBS 11, Linux amdtp-am824 / fireworks_stream.c keep_resources: capture uses midi_out_ports, playback midi_in_ports). Profile, runtime caps and the HWINFO geometry gate now carry the slot. - FireworksProtocol destructor cancels the clock apply and EFC traffic (production drops the shared_ptr without Shutdown()). - An unarmable timer (kInvalidTimerToken) fails the command / clock apply instead of wedging the queue. - Note firmware 4.6.0's wrong DBS at >= 88.2 kHz (Linux CIP_WRONG_DBS) and the deliberate fail-fast on stale generation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 83eaa1a4ed0bee7b61285557aebf5339e7c0813b)
…w too First hardware contact with a Mackie Onyx 400F (GUID 0x000FF203B2EE7318, 2026-09-13): every HWINFO GET_CAPS command was acknowledged and the unit answered within a millisecond with a 284-byte block write — but to 0xFCC0'8000'0000, not the 0xECC0'8000'0000 that Linux fireworks_transaction.c and snd-firewire-ctl-services document as the default. The local dispatch therefore answered addr_error and the transport timed out after 3 attempts. The response address is firmware-stored (HWINFO flag bit 0 marks it changeable), so the mailbox now claims both windows. The local handler logs the arrival address at V2 for future captures. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 73c4cb4cfe07237cc35600ebb20f43a28b33d70a)
…ordinator Second hardware contact (v16, 2026-09-13): with both EFC response windows claimed, HWINFO GET_CAPS answers immediately — "Mackie" / "Onyx 400F", firmware 5.6.3, 10 PCM channels each way at 1x/2x/4x, MIDI 1 in / 1 out, clocks internal/word/S-PDIF, 44.1–96 kHz — and the 10+1 static geometry passes the gate. Every CoreAudio start then failed at Prepare with kIOReturnUnsupported before any further EFC command: a first-ever start carries no session clock, so the coordinator asked for its historical 48 kHz default, which the protocol's single-rate set refuses. Give the 400F the same start-rate pin the Onyx-i has (default start rate and the defense-in-depth override), and log the rejected rate / geometry state from FireworksProtocol::ApplyClockConfig so the refusal is visible. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit abb1419741682358a60dd67f3a1cf5a862ee2404)
…t a clock-lock gate Third hardware contact (v17, 2026-09-13): the start now clears Prepare — TRANSPORT SET_TX_MODE, HWCTL GET_CLOCK (unit at 48 kHz), SET_CLOCK to 44.1 kHz, 150 ms settle, IRM channel/bandwidth allocation, duplex armed at 11 slots each way — and then dies in WaitForStableGlobalClock: the 400F had no DuplexStreamProfile branch, so it inherited the DICE-style requiresPreStreamClockLock and the coordinator polled GET_CLOCK ~50 times waiting for a pre-connection lock that a CMP device never reports. Classify the 400F like the other CMP families: IRM-chosen iso channels, BeBoB ordering (connect both plugs, host RX before TX), no pre-stream lock gate, and the configured slot count as the capture stride (Fireworks dbc semantics and the fw 4.6.0 wrong-dbs quirk in Linux fireworks_stream.c). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 5fdc828fc92132255b11e7a4453240259e7b17ba)
…lug registers The BeBoB base's ConfirmDuplexStart re-reads iPCR and oPCR after the host isoch contexts start. On the Onyx 400F the oPCR read is issued in the same millisecond the IT context starts and the device never answers it (transaction trace: ATRequest with no ARResponse), so the confirm stage waited out the 12 s sync bridge and CoreAudio's StartIO failed while both streams were already carrying data. Linux snd-fireworks never re-reads the plugs after amdtp_domain_start (fireworks_stream.c); the connect-time compare-swaps already proved both plug states. FireworksProtocol now overrides ConfirmDuplexStart: it refuses with kIOReturnNotReady unless both plugs are connected, and otherwise completes at once from the recorded generation, channels, applied clock and static caps. Tests: two new FireworksProtocolTests cases. The CMP-side FakeBus is now address-keyed (iMPR/oMPR answer one plug at S400, plug-0 PCRs online and idle) with a real compare-swap in Lock, so the connect path runs end to end and the confirm stage is shown to issue no further reads, locks or EFC frames. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit fdcbfad5f50061e8738c4d9a6fe7717e97ad50d6)
b521705 to
f68f7c5
Compare
|
Glad those turned out to be useful beyond the 400F, and the Saffire Pro 24 stop line was great to see. Both branches are rebased onto main and marked ready: #101 is 12 commits on current main, and #107 is 6 on top of it with the three #117 commits dropped as already upstream. #107 still needs the Mackie plumbing from #101, so it stays stacked for now; the 400F-only diff is the last six commits, or this compare view: ottendorfcipher/ASFireWire@onyx-820i-support...onyx-400f-support. Happy to rebase it flat as soon as #101 lands. |
Adds support for the Mackie Onyx 400F (Echo Fireworks production run; the 1200F is the same silicon). Developed and verified against an Onyx 400F behind an Apple Thunderbolt→FireWire adapter chain on macOS 26.6.2 / M3. Stacked on #101: the branch starts from that PR's head and reuses its Mackie profile and AV/C bypass plumbing, so the first 12 commits review there and the last 6 are this PR. Both branches are rebased onto current main; the three device-independent fixes that were found during this bring-up (local root enables its own cycle master, IRM re-issue of unanswered reads/locks, IT hardware-ring lap realignment) were merged separately in #117 and are no longer part of this PR. The 400F-only diff is the last six commits, or this compare view: ottendorfcipher/ASFireWire@onyx-820i-support...onyx-400f-support.
Verified on hardware
keep_resources).receivingData); a listening test on the capture side is still pending.IT lap sighted→IT ring lap: laps=1 skipped=48corrections in seven seconds with the exposure lead unchanged, and steady state shows zero refused readings.Change summary (one commit per step)
feat(audio)— Echo Fireworks backend: EFC codec (header/params framing, seqnum echo, HWINFO and HWCTL parameter layouts),EfcResponseMailboxplus a local-address handler for the host response window,EfcTransport(serial, 125 ms × 3),FireworksProtocolon the BeBoB/CMP base (SET_TX_MODE once per session, EFC-driven clock apply with settle and watchdog, HWINFO probe with a geometry gate), Onyx 400F recognition, Fireworks profile family, AVCDiscovery bypass publishing a static 10×10 @ 44.1 kHz nub,MackieOnyx400FProfileADK geometry, factory dispatch. 1200F is recognition-only.fix(audio)— review fixes before first hardware contact: IOLock-guarded transport with weak lifetime tokens in every bus/timer callback, bounded observer removal, MIDI slot geometry (DBS 11 perfireworks_stream.c), destructor cancellation, unarmable-timer handling.fix(audio)— claim the0xFCC0'8000'0000EFC response window too: on first contact the unit acknowledged every command and answered within a millisecond, but at that address rather than the0xECC0'8000'0000thatfireworks_transaction.cand snd-firewire-ctl-services document. The response address is firmware-stored (HWINFO flag bit 0 marks it changeable), so the mailbox claims both.fix(audio)— start-clock pin: a first-ever start has no session clock and the coordinator's default requests 48 kHz, which the single-rate set refuses at Prepare (kIOReturnUnsupported); same mechanism as the Onyx-i and Duet pins.fix(audio)— duplex profile: classify the 400F like the other CMP families (IRM-chosen channels, BeBoB start order, configured slot count as the capture stride) and without the pre-stream clock-lock gate. It had inherited the DICE-style gate and polled GET_CLOCK ~50 times for a lock a CMP device never reports.fix(audio)— confirm a Fireworks duplex start without re-reading the plug registers: the base's oPCR read-back goes out in the same millisecond the IT context starts and the 400F never answers it, so the confirm stage waited out the 12 s sync bridge while both streams were already carrying data. Linux never re-reads afteramdtp_domain_start; the connect-time compare-swaps already proved both plugs.Design decisions flagged for review
FireworksProtocolsubclassesBeBoBProtocol, for the same reasonMackieOnyxProtocoldoes in Add Mackie Onyx-i (Oxford) support: recognition, publication, asymmetric duplex streaming #101: the base's CMP connect/verify/teardown and plug-0 stream lifecycle are exactly what Fireworks streaming needs (fireworks_stream.c: CMP + AM824 blocking, SYT-unaware). Control differs — EFC over async block writes rather than FCP — so the protocol overridesInitialize,PrepareDuplex,ApplyClockConfig,ConfirmDuplexStartandReadClockHealthand never touches the FCP path.InitializeandPrepareDuplexprobe HWINFO and refuse to stream if the unit's 1× channel or MIDI port counts differ, so a 1200F cannot stream on the 400F's numbers.SET_CLOCKis implemented and exercised on hardware, so widening the rate list should reduce to a one-line change once the reconfig path lands; 88.2/96 kHz additionally sit behind the driver-wide gate, and Linux notes firmware 4.6.0 reports a wrong DBS there (CIP_WRONG_DBS).FireworksProtocolincluding two confirm-stage cases that drive a real CMP connect through an address-keyed fake bus; and aDuplexStreamProfilepin for the CMP-driven classification. (The cycle-policy, IRM and ring-lap tests went upstream with bus/irm/isoch/ohci: four device-independent fixes from #107 and #114 #117.)References (Linux snd-fireworks; snd-firewire-ctl-services
protocols/fireworks; Apple IOFireWireFamily) were used as behavioral sources only, per the repository's reference policy; citations are in code comments. Local version-bump commits are excluded from this branch.Not included