feat(scsi): SCSIControllerDriverKit HBA bridging SBP-2 (generic target, tested on CoolScan 9000)#46
Merged
Merged
Conversation
Contributor
Author
…n 9000) Implements the SCSIControllerDriverKit direction noted in the project TODO: an IOUserSCSIParallelInterfaceController that lets user-space SCSI clients (e.g. SCSITaskLib / VueScan) drive an SBP-2 device without porting its command set. - SCSIController/: the HBA (ASFWSCSIController), a phantom SBP-2 nub for the provider match, and a process-global bridge (SBP2BridgeHub + SBP2TargetBridge) that auto-logs-in on discovery and routes SCSI tasks to the SBP-2 session. - SBP-2 command plane matured for real command traffic: ORB_POINTER-per-command submission (matches Apple's shipping SCSI transport and Linux sbp2), dead-bit agent-reset recovery, ORB timeout -> AGENT_RESET -> LUN reset -> bus-reset escalation ladder, page-table data descriptors, autosense surfacing, and the ISessionScheduler-based ORB timers. Known limitation: the phantom target is static and its INQUIRY identity is hardcoded to the CoolScan 9000, so the HBA is effectively single-target for now (a hot-plug model that creates the target after login, letting a real INQUIRY through, is the generic follow-up). RESERVE/RELEASE are answered synthetically because the LS-9000 firmware wedges on a RESERVE(6) retry. Depends on the AR-stitching fix (prior commit); without it a full scan wedges at the DMA buffer boundary. Host tests cover the command plane, page tables, and the SBP-2 handler; hardware-verified end-to-end (real film scan on macOS Tahoe).
SessionRegistry fires SetLoginStateObserver on login up/down (not on bus-reset suspend). SBP2BridgeHub gains a reverse target-observer slot so the HBA can be driven from the FireWire side. Wired registry -> bridge -> hub -> HBA; the HBA observer is inert (logs only) — the static phantom target is unchanged. Foundation for login-gated hot-plug (WI-3/WI-4). (cherry picked from commit b50c494)
… spoof) Framework auto-creates + probes target 0 ~3s before SBP-2 login. Instead of spoofing a hardcoded identity, hold the probe INQUIRY completion and replay it with the device's real INQUIRY once login fires (generic, no per-device ident). HW-verified: VueScan finds + opens the real Nikon LS-9000 ED, full SCSI flow through the bridge. Bump dext to v53.
…arget-0) Explicit destroy of the presence-scan target 0 during HBA teardown re-entered framework termination on the aux queue; target never quiesced, registry busy timed out at 60s (panic IOService.cpp:5986, IOSCSITargetDevice + IOThunderboltPort). Framework terminates target 0 as a child of the stopping controller. Held-INQUIRY flush kept as backstop against a leaked OSAction. (cherry picked from commit 099868c)
The 51 was a local HW-test artifact — build.sh/bump.sh auto-increments CFBundleVersion each build so macOS reloads the dext instead of caching an identical-version install. That iteration count is meaningless upstream, where the baseline is 1 and release versioning is the maintainer's call. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two exactly-once violations in the SBP-2 command plane, both on paths the happy-path scan never exercises (found in PR mrmidi#46 review): B1 — double-completion on synchronous fetch-agent write failure. When bus_.WriteBlock returns a null handle synchronously (AT ring full / async-label pool exhausted / bus-reset generation edge), FetchAgent::AppendImmediate fires the ORB completion inline. That completion nulls commandORB_ (CleanupCommandResources) and defers delivery behind a LUN-reset ORB, leaving resultCallback_ armed, yet SubmitORB returns false. CommandExecutor::SubmitCommand then skipped its rollback (commandORB_ no longer == submittedORB) but still returned false, so SBP2TargetBridge::Pump fired its own fallback completion — a second ParallelTaskCompletion on the same task plus an OSAction/controller over-release. SubmitCommand now reports the task ACCEPTED (true) whenever the completion already ran, so the caller does not double-fire. H1 — deferred result stranded when a bus reset/teardown drops the LUN-reset ORB. The timeout->LUN-reset escalation clears commandInFlight_/commandORB_ before deferring delivery to the management ORB's onComplete. If OnBusReset()/Cleanup()/ ~CommandExecutor() runs in that window, CleanupManagementResources drops the ORB without firing onComplete, and the commandInFlight_/commandORB_ guard misses the deferred state — resultCallback_ never fires, wedging SBP2TargetBridge's pump and leaking the SCSI task (VueScan hangs until power-cycle). All three now flush any still-armed result after dropping the management ORB; NotifyResultCallback moves the callback out, so it stays exactly-once. Tests: SynchronousWriteFailureDoesNotDoubleCompleteTask (B1) and DeferredResultFlushedWhenBusResetDropsLunResetORB (H1); both verified to fail without the fix. DeferredFireWireBus gains FailNextWriteBlock() to model a synchronous submit failure. Full host suite green (1219). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…service ref) SBP2TargetBridge held SessionRegistry as a bare reference and dereferenced it from IsReady()/Pump() on the HBA service's queue after releasing its own lock. The HBA reaches the bridge through the process-global SBP2BridgeHub holding only a shared_ptr to the bridge, which does not keep the registry alive, while ServiceContext::Reset() frees the registry on the driver teardown queue — a cross-service use-after-free (the FW-60 class) found in PR mrmidi#46 review. Hold the registry as a std::weak_ptr and lock() it for the duration of each call: a live strong ref keeps it alive across the deref, a null lock is treated as not-ready. This does not extend the registry's lifetime or change teardown ordering — in-flight commands are still force-completed by the registry's own Cleanup on teardown; it only closes the deref-after-free window on the HBA queue. Ctor takes the shared_ptr (from DriverContext) to seed the weak_ptr. No host test (the bridge has no host coverage yet); validated by review + the v54 mid-scan unplug teardown HW test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… scanner The SCSI-over-SBP2 HBA carries no LS-9000 device identity: matching, discovery, login, and identity replay are all generic. The only 9000- motivated pieces are five tuned constants / quirk workarounds, all either generic-safe (sole-initiator RESERVE/RELEASE) or fire-on-fault only. Reword their comments so each reads as observed-on-9000-but-valid-for-any-single-LUN- SBP-2-scanner, preventing a future reader from re-tightening a value to a 9000-only figure. No behavioral change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What
Adds the SCSIControllerDriverKit path from the TODO: an
IOUserSCSIParallelInterfaceControllerso user-space SCSI clients (SCSITaskLib, e.g. VueScan) can drive an SBP-2 device without porting that device's command set into the driver. First hardware is a Nikon Super CoolScan 9000 ED, driven end to end on Tahoe.Nothing in the driver is specific to the 9000 — it's just the first device I had to test against. See "Not tied to the 9000" below.
How
SCSIController/— the HBA (ASFWSCSIController), a phantom SBP-2 nub for the provider match, and a process-global bridge (SBP2BridgeHub+SBP2TargetBridge) that watches discovery, logs into the SBP-2 unit, and routes SCSI tasks to the session.ORB_POINTER-per-command submission (same as Apple's shipping SCSI transport and Linuxsbp2— neither uses the doorbell for SCSI), dead-bit agent-reset recovery, an ORB-timeout →AGENT_RESET→ LUN-reset → bus-reset escalation ladder, page-table data descriptors, autosense, andISessionSchedulerORB timers instead of IOSleep-on-queue.Stopdoes not callUserDestroyTargetForIDon it. Calling destroy re-entered the framework's own termination on the aux queue and hung target quiesce (60s registry busy-timeout panic). Held-INQUIRY flush stays as a backstop.Not tied to the 9000
There's no GUID/vendor/model match anywhere. Matching co-matches the OHCI PCI device; SBP-2 unit adoption keys on the standard spec_ID / sw_version; node ID, LUN, speed, ORB size, timeouts and max_payload all come from Config ROM and the bus at runtime. And all the SCSI command generation (INQUIRY, MODE SELECT, LUT upload, READ) is in VueScan, not the dext.
So another single-LUN SBP-2 scanner — a CoolScan 8000, say — should enumerate, log in and run through the same path with no code change. It'd be capable out of the box; only the 9000 is actually validated. The handful of 9000-observed constants and workarounds are commented as such in-code (each is either generic-safe or only fires on a fault).
Fixed since the first cut
WriteBlockfailure could complete a task twice (submit-path error plus teardown/abort).SessionRegistryviaweak_ptrinstead of a raw cross-service reference, closing a use-after-free on teardown (FW-60 class).Known limitations (intentional, commented in code)
RESERVE/RELEASEare answered synthetically GOOD. This HBA is the only initiator on the bus, so a reservation is uncontended and GOOD is correct for any single-initiator SBP-2 target. (What flagged it: the reference path never sends them, and 9000 firmware also wedges on aRESERVE(6)retry.)Testing
IOThunderboltPortand the SCSI target with an outstanding task.Stopruns, the in-flight command is force-completed (kIOReturnAborted), the framework terminates target 0 — no panic (this exact path used to 60s-busy-timeout-panic before the teardown fix). Reconnecting after a mid-scan yank needs a scanner power-cycle, same as the old macOS FireWire/SCSI stack — that's the 9000 firmware, not us.Dependency
Needs the AR-stitching fix (#45), or a full scan wedges at the DMA buffer boundary. #45 is merged, so this is rebased straight onto
mainand no longer stacked.