Switch 2 Pro USB read-only input transport owner¶
Status: the production-shaped contract, concrete Windows read-only discovery, native overlapped-read lease, and lifecycle are implemented and replay/unit-test verified. The adapter is deliberately not registered or enabled in DS4Windows, no controller was contacted by this tranche, and no live hardware or latency claim is made.
Scope¶
Switch2ProUsbInputTransport.cs is the next boundary after
Switch2PhysicalInputBoundary.cs. It owns one candidate-specific, read-only
USB input lifetime without treating the legacy SwitchProDevice/HidDevice
path as the wire contract.
The factory accepts two deliberately separate injected dependencies:
ISwitch2ProUsbOsDiscoveryAdaptersupplies one atomic, descriptor-only composite observation. It exposes no path, serial, instance ID, handle, or printable controller identity.ISwitch2ProUsbNativeAdapteropens a read-only composite lease only after the existing exact admission gate succeeds. The returned registration must equal the admitted registration or the lease is rejected and quiescently released.
The lease retains the admitted relationship between HID input MI_00 and the MI_01 topology/presence node, but its public surface can only start, cancel, quiesce, and release a 64-byte MI_00 input read. It has no command, output, feature, LED, rumble, haptic, or initialization method. This tranche therefore cannot transmit anything to a controller.
Switch2ProUsbWindowsAdapter implements both injected seams as a one-shot
Windows capability. Its SetupAPI/HID/WinUSB implementation follows the audited
hardware-verifier discovery algorithm: complete present-interface enumeration,
exact VID/PID/MI component matching, explicit HID-parent and Windows-container
edges, exact bcdDevice and HID report capabilities, one MI_01 device-interface
path, default alternate setting, and exact two-pipe topology. Matching HID
collections are grouped by opaque container, and each container must
independently contain exactly one admitted MI_00 and one admitted MI_01. An
ambiguous or incomplete container is rejected without first-matching and does
not suppress another independently valid controller. Once a target-looking
node has an attributable nonempty container, its metadata, parent, service, or
interface-list failure invalidates only that container; failure of the global
SetupAPI enumeration itself still fails the complete snapshot closed. An active
entry whose identity or container cannot be read is also a conservative global
failure: it could be a duplicate interface for a later candidate, so continuing
would leave exact per-container cardinality unproven. Raw paths, instance IDs,
serial-bearing strings, and handles remain inside internal types.
Opening consumes the preceding observation. The adapter re-enumerates and
compares the exact private device-tree identity before acquisition, opens MI_00
with GENERIC_READ, FILE_SHARE_READ, and overlapped semantics, opens MI_01
with read access and read/write sharing only to retain and query its presence
and topology, validates both live handles, and re-enumerates the complete
composite again before returning the lease. The MI_01 handle is not exposed to
the transport lease and has no output method.
Immediately after consuming an observation, and before native re-enumeration or handle acquisition, the default Windows adapter atomically reserves the admitted opaque container in a process-wide registry. Reserved containers are skipped by later observations, while different container identities can hold independent leases. Candidate selection advances by opaque-container cursor so a persistently inaccessible first candidate cannot starve a later valid pad. The reservation transfers to the lease and is released only after both retained resources report successful terminal disposal. Quiescence alone does not release it, and a partial or failed disposal retains it until a successful retry. An aborted open releases its reservation only after every partially acquired handle was successfully released; an unproven partial release deliberately leaves the container fenced fail-closed.
The registry strongly owns ambiguous cleanup capabilities. A cleanup ambiguity before container attribution enters a process-wide unattributed quarantine and blocks all later discovery/reservation. Once reserved, the exact reservation entry roots the corresponding native cleanup owner. Terminal reservation publication first seals native starts, runs the release hook with the terminal gate open to stale callbacks, then commits removal under that same gate. A reentrant or concurrent stale callback therefore latches quarantine before removal, while a throwing hook retains the exact release capability for retry.
Admission and ownership¶
The transport owner delegates admission to
Switch2PhysicalDeviceFactory.TryAdmitProUsb; it does not add a second or
weaker matcher. The admitted device remains exactly:
- Nintendo
057E:2069,bcdDevice 0201; - exactly one HID-class MI_00 and exactly one WinUSB MI_01 in the same nonempty opaque Windows container;
- exact HID usage/report shape and exact two-pipe MI_01 topology already
documented by
switch2-physical-input-boundary.md.
Discovery rejection happens before native acquisition. Native acquisition is candidate-specific and is checked again through the opaque registration. Multiple admitted Pro composites may coexist when their opaque Windows containers differ; duplicate candidates within one container remain rejected. The registry coordinates all default instances of this new Windows adapter in the current process but does not yet coordinate DS4Windows' legacy enumerator.
One owner has one preallocated 64-byte input buffer and permits one read claim at a time. A claim contains the immutable device generation, transport generation, a monotonically increasing nonzero read sequence, and a private owner fence. A duplicate, old, or foreign completion is rejected before parser state or publication changes. Sequence exhaustion closes the lifetime instead of reusing a token.
Completion and lifecycle¶
The native implementation may complete a successful start synchronously or asynchronously. The owner never calls discovery, native start/cancel/wait/ dispose, or the canonical-frame sink while holding its lifecycle lock.
Open
-> one native MI_00 read claim
-> exact claim + generation check
-> exact 64-byte/report-05 physical adapter
-> immutable Switch2CanonicalInputFrame
-> injected sink (outside the lifecycle lock)
Open -> StopRequested -> native quiescence -> managed callback quiescence
-> Disposing -> Disposed
RequestStop is idempotent and reserves at most one cancellation for the
active claim. TryQuiesceAndDispose requires an explicit timeout from zero to
5,000 milliseconds. It first waits within that same budget for an in-progress
native begin or cancellation call to return, so TryWaitForInputQuiescence
never overlaps either control call and the native lease need not make them
concurrency-safe. Native transition timeout, native-quiescence timeout, or a
still-running managed callback leaves the stopped owner retryable and does not
close a possibly active lease. A true native-quiescence result is the seam's
guarantee that no old callback can arrive; only then can a cancelled read
without a completion be retired.
DisposeQuiesced requires prior native/managed quiescence, submits no new
controller I/O, and invokes no callback. Its synchronous managed/native resource
release (ThreadPoolBoundHandle.Dispose, WinUsb_Free, and CloseHandle) has
no hard wall-clock bound. Input and presence resources record successful release
independently. If one release fails, a retry releases only the remaining
resource and cannot report terminal disposal while either handle is retained.
The Windows lease binds its overlapped MI_00 handle to the thread pool, pins
only the caller's single preallocated report buffer for the duration of the
read, and allocates one native OVERLAPPED for the exact claim. Cancellation
uses CancelIoEx(handle, exactOverlapped) and never performs handle-wide
cancellation. The native operation signals quiescence only after its managed
completion has returned. The outer lease additionally drains callbacks and
suppresses stale, duplicate, or post-quiescence delivery before either retained
handle can be released.
The lease reuses one claim context and one completion delegate. The native
input handle reuses one operation, one wait primitive, one persistently pinned
64-byte owner buffer, and one PreAllocatedOverlapped; completed submissions
must cross explicit exact-claim retirement before the same storage can be reset
for the next claim. Begin never performs hidden zero-time retirement. A
lease-local control fence serializes per-read retirement and terminal
quiescence, while an immutable operation reference plus monotonic submission
epoch is revalidated before and after native wait and release. Exact
cancellation intentionally remains callable while retirement is waiting; the
native operation serializes CancelIoEx against release of that same
OVERLAPPED. The injected
steady-state test observes zero managed allocations across 20,000 successive
claim/completion cycles.
The owner itself has no timer, worker, polling loop, idle renewal, queue, log,
or persistent state. Switch2ProUsbInputReadPump is the separate dormant
completion-driven worker. It atomically acquires exclusive use of an idle
owner, issues exactly one read, waits for native and managed completion of that
exact submission, retires its reusable native storage, and immediately issues
the next read only when the atomically returned claim-keyed completion result is
Published and the owner lifetime is still open. Native quiescence alone never
authorizes rearm: cancellation which reaches quiescence without a callback is a
stop-only result. It uses one blocking background thread and no timer, sleep,
or fixed polling interval. A retirement timeout stops the lifetime fail-closed;
it never skips retirement or starts a second read. Public manual read methods
are fenced after the pump takes ownership, and a second pump cannot attach.
Pump stop requests exact cancellation through the owner, joins the worker within the caller's one explicit budget, then uses the remaining budget for quiescence and terminal disposal. A join or disposal timeout retains the owner, native lease, and process-local container reservation for an exact retry. No thread priority/MMCSS policy is asserted, and no rate or latency result is inferred from this scheduling shape. Exact cancellation is allowed to overlap a worker already blocked in retirement; retirement cannot release or recycle the submission until that cancellation and native/managed quiescence are complete.
Integration boundary¶
The following work remains intentionally absent:
- global
DS4Devicesregistration or anInputDeviceTypechange; - binding the dormant completion-driven pump and canonical sink to the existing runtime/profile/removal lifecycle;
- a transport-neutral runtime/profile adapter and removal UI;
- calibration reads, initialization, command transactions, output, LEDs, haptics, audio, BLE, or Joy-Con 2 lifecycle;
- live hardware, unplug/replug, suspend/resume, or latency verification.
The exact remaining coexistence blocker is the legacy DS4Devices/HidDevice
enumerator: it neither consults this registry nor excludes or atomically hands
off an admitted 057E:2069 MI_00, so it could independently open the same
physical controller. Before live registration, that path needs one explicit
classification and atomic exclusion/handoff policy, and any future MI_01 output
owner needs a separately proven sole-writer policy. The runtime/profile and
removal lifecycles also remain absent, and controlled hardware verification
must still prove open/share, unplug, suspend/resume, report-rate, and end-to-end
presentation behavior. This adapter does not claim, hide, reassociate, rebind,
initialize, or write to the controller.
Provenance¶
No new wire fact or third-party code was introduced here. The exact USB facts
and their project-owned hardware evidence are inherited from and linked by
switch2-physical-input-boundary.md. The Windows implementation independently
adapts the project-owned, audited SetupAPI/HID/WinUSB algorithms in
Switch2UsbHardwareVerify; it does not call the verifier or import captured
hardware identity. Neither production tests nor public APIs contain a device
path, instance ID, hardware serial, or captured raw report.
Offline verification¶
Switch2ProUsbInputTransportTests verifies admission-before-open, rejection of
duplicate interface cardinality, lease-registration mismatch cleanup, exact
device/transport generation delivery, sole read ownership, duplicate/old/
foreign completion rejection, sequence-exhaustion closure, cancellation
idempotence, partial-open and throwing-start cleanup, bounded retryable
quiescence/disposal, serialization of native begin/cancel/wait calls,
cross-thread sink reentry (no callback under the owner lock), absence of
output/raw-identity/native HidDevice capability, and zero managed allocations
across 20,000 steady-state begin/accept/publish cycles. Pump tests add exact
completion-driven rearm, exclusive ownership, duplicate-pump rejection,
fail-closed retirement failure, non-publish rejection, no premature rearm,
repeated asynchronous cycles, cancellation-without-callback stop semantics,
late-callback suppression, bounded join timeout, and retryable disposal.
Switch2ProUsbWindowsAdapterTests adds injected replay coverage for exact
three-snapshot acquisition, one-shot observation consumption, per-container
enumeration of two valid controllers, rejection of same-container duplicates,
atomic concurrent reservation, reservation retention through quiescence and
failed disposal, release/re-open after successful terminal disposal,
linearized concurrent observation, non-starving candidate rotation,
same-container partial-open poisoning, concurrent terminal-disposal ordering,
same-thread and concurrent stale-callback races during reservation publication,
whole-snapshot rejection for unattributable entry failure versus isolation of
an invalid attributable container,
missing/multiple and bcd/cardinality rejection, input share/open and MI_01
presence-open failure, rebind before and after acquisition,
descriptor/topology changes, synchronous and asynchronous completion, one
outstanding read, exact/stale cancellation, cancellation/completion races,
device removal, duplicate delivery, cancel-without-callback quiescence,
post-quiescence suppression, lease-local retirement exclusion, exact cancel
concurrent with a blocked retirement, epoch-safe reuse, required disposal
ordering and retry after either retained resource rejects disposal,
read-only/overlapped access policy, and absence of raw identity or output
capability from the public boundary.
dotnet test DS4WindowsTests\DS4WindowsTests.csproj -c Release \
-p:Platform=x64 --no-restore \
--filter "FullyQualifiedName~Switch2ProUsbInputTransportTests|FullyQualifiedName~Switch2ProUsbWindowsAdapterTests"
Passed: 75, Failed: 0, Skipped: 0
These include 29 transport-owner/pump tests and 46 injected Windows-adapter tests. They are replay/unit simulations, not hardware verification or a measured input-rate/latency claim.