The dock exposes control on a bulk pipe pair, video on one or two further
bulk endpoints, and an interrupt endpoint the dock pushes on. A dock
generation differs in how many of those it has and in whether video shares
the control pipe, so the transport takes the endpoint numbers as data
rather than deciding them.

Add the link: a bound-interface wrapper that opens those pipes, submits
video as a queue of pipelined URBs, and reports a stall as a stall rather
than retrying into a wedged dock.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Mike Lothian <[email protected]>
---
 drivers/gpu/drm/vino/usb_link.rs | 250 +++++++++++++++++++++++++++++++
 1 file changed, 250 insertions(+)
 create mode 100644 drivers/gpu/drm/vino/usb_link.rs

diff --git a/drivers/gpu/drm/vino/usb_link.rs b/drivers/gpu/drm/vino/usb_link.rs
new file mode 100644
index 000000000000..04b9131d05ed
--- /dev/null
+++ b/drivers/gpu/drm/vino/usb_link.rs
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! The dock's USB endpoints, and the I/O handle every transfer goes through.
+//!
+//! [`Endpoints`] is resolved once against interface 0's descriptor and 
direction/type-checked, so
+//! the rest of the driver names an endpoint by what it is rather than by a 
bare address. A
+//! [`UsbLink`] can only exist while the device's [`usb::IoWindow`] is open, 
so a transfer cannot
+//! outlive the binding.
+
+use super::*;
+
+/// The dock's endpoints, resolved once against interface 0's descriptor and 
validated for
+/// direction and transfer type by [`usb::Interface::endpoint`].
+///
+/// Resolving up front means the rest of the driver names an endpoint by what 
it *is* rather than
+/// by a bare address, so a bulk-OUT transfer cannot be aimed at the 
interrupt-IN status endpoint.
+/// The whole set is [`Copy`] (each entry is an address plus a max-packet 
size), so it can be
+/// carried by value in a [`UsbLink`] rather than borrowed.
+#[derive(Clone, Copy)]
+pub(crate) struct Endpoints {
+    /// EP02: host->dock control-plane bulk writes.
+    pub(crate) ctrl_out: usb::Endpoint<usb::BulkOut>,
+    /// EP84: dock->host control-plane bulk replies.
+    pub(crate) ctrl_in: usb::Endpoint<usb::BulkIn>,
+    /// Per-connector video bulk-OUT endpoints, from 
[`DockProfile::video_endpoints`].
+    pub(crate) video: [usb::Endpoint<usb::BulkOut>; drm_sink::MAX_CONNECTORS],
+}
+
+impl Endpoints {
+    /// Resolves every endpoint the driver uses against `intf`'s active 
alternate setting, and
+    /// returns how many connectors the device actually backs.
+    ///
+    /// The control endpoints and the first video endpoint are required. The 
rest are not: a dock
+    /// in a known family with fewer outputs simply does not expose the later 
ones, and the count
+    /// that does resolve is the connector count. Taking it from the device is 
the difference
+    /// between supporting such a dock and refusing to bind to it, because a 
profile can only
+    /// describe the variants someone has tested. Navarro repeats addresses 
across connectors
+    /// (0/2 share `0x08`, 1/3 share `0x0a`), so this counts entries rather 
than distinct
+    /// endpoints, and the profile's own count remains the upper bound -- four 
resolvable entries
+    /// on a two-connector Ridge dock still means two connectors.
+    pub(crate) fn resolve<Ctx: device::DeviceContext>(
+        intf: &usb::Interface<Ctx>,
+        profile: &DockProfile,
+    ) -> Result<(Self, u8)> {
+        let mut video = 
[intf.endpoint::<usb::BulkOut>(profile.topology.video_endpoints[0])?;
+            drm_sink::MAX_CONNECTORS];
+        let mut connectors = 1u8;
+        for (slot, addr) in video
+            .iter_mut()
+            .zip(profile.topology.video_endpoints)
+            .skip(1)
+        {
+            let Ok(ep) = intf.endpoint::<usb::BulkOut>(addr) else {
+                break;
+            };
+            *slot = ep;
+            connectors += 1;
+        }
+
+        Ok((
+            Self {
+                ctrl_out: intf.endpoint::<usb::BulkOut>(EP_CTRL_OUT)?,
+                ctrl_in: intf.endpoint::<usb::BulkIn>(EP_CTRL_IN)?,
+                video,
+            },
+            connectors.min(profile.topology.connectors),
+        ))
+    }
+}
+
+/// A live USB transfer handle: an [`usb::Io`] token proving I/O is currently 
permitted, plus the
+/// resolved [`Endpoints`].
+///
+/// Obtaining one requires the device's [`usb::IoWindow`] to still be open, so 
a transfer cannot be
+/// issued after `disconnect()` has closed it. Carrying the endpoints in the 
handle also keeps raw
+/// endpoint addresses out of transfer call sites.
+pub(crate) struct UsbLink<'a> {
+    window: &'a Arc<usb::IoWindow>,
+    io: usb::Io<'a>,
+    pub(crate) endpoints: Endpoints,
+}
+
+impl<'a> UsbLink<'a> {
+    /// Opens a link on `window`, failing with `ENODEV` once the window has 
been closed.
+    pub(crate) fn open(window: &'a Arc<usb::IoWindow>, endpoints: Endpoints) 
-> Result<Self> {
+        Ok(Self {
+            io: window.enter()?,
+            window,
+            endpoints,
+        })
+    }
+
+    /// Opens the persistent EP84 control-plane reader.
+    pub(crate) fn ctrl_in_queue(&self, depth: usize, buf_len: usize) -> 
Result<usb::BulkInQueue> {
+        usb::BulkInQueue::new(
+            self.window,
+            &self.io,
+            &self.endpoints.ctrl_in,
+            depth,
+            buf_len,
+        )
+    }
+
+    /// Opens the pipelined EP02 control-plane writer.
+    pub(crate) fn ctrl_out_queue(&self, depth: usize, buf_len: usize) -> 
Result<usb::BulkOutQueue> {
+        usb::BulkOutQueue::new(
+            self.window,
+            &self.io,
+            &self.endpoints.ctrl_out,
+            depth,
+            buf_len,
+        )
+    }
+
+    /// Returns the canonical queue slot for `connector`'s physical video 
endpoint.
+    ///
+    /// A dock profile may repeat an endpoint address for multiple physical 
connectors (Navarro
+    /// uses EP08 for connectors 0/2 and EP0a for 1/3).  Those connectors must 
share one
+    /// persistent queue: separate queues could submit interleaved URBs to the 
same pipe.
+    pub(crate) fn video_pipe_index(&self, connector: usize) -> Result<usize> {
+        let endpoint = self.endpoints.video.get(connector).ok_or(EINVAL)?;
+        self.endpoints
+            .video
+            .iter()
+            .position(|candidate| candidate.address() == endpoint.address())
+            .ok_or(EINVAL)
+    }
+
+    /// Opens the pipelined video writer for `connector`'s physical video 
endpoint.
+    pub(crate) fn video_queue(
+        &self,
+        connector: usize,
+        depth: usize,
+        buf_len: usize,
+    ) -> Result<usb::BulkOutQueue> {
+        let ep = self.endpoints.video.get(connector).ok_or(EINVAL)?;
+        usb::BulkOutQueue::new(self.window, &self.io, ep, depth, buf_len)
+    }
+
+    /// The underlying I/O token, for the paths that open a persistent queue.
+    pub(crate) fn io(&self) -> &usb::Io<'a> {
+        &self.io
+    }
+
+    /// Writes a control-plane message to EP02.
+    pub(crate) fn ctrl_send(&self, data: &[u8], timeout: Delta, gfp: Flags) -> 
Result<usize> {
+        self.io
+            .bulk_send(&self.endpoints.ctrl_out, data, timeout, gfp)
+    }
+
+    /// Reads a control-plane reply from EP84.
+    pub(crate) fn ctrl_recv(&self, data: &mut [u8], timeout: Delta, gfp: 
Flags) -> Result<usize> {
+        self.io
+            .bulk_recv(&self.endpoints.ctrl_in, data, timeout, gfp)
+    }
+
+    /// Reads the standard two-byte status word for a video endpoint.
+    ///
+    /// Bit zero is `ENDPOINT_HALT`.  This is deliberately read-only: it 
distinguishes a dock
+    /// which has stalled the pipe from one which is merely returning NRDY 
without perturbing the
+    /// endpoint's host or device sequence state.
+    pub(crate) fn video_endpoint_status(&self, connector: usize) -> 
Result<u16> {
+        let address = 
self.endpoints.video.get(connector).ok_or(EINVAL)?.address();
+        let mut status = [0u8; 2];
+        self.control_recv(
+            0x00, // USB_REQ_GET_STATUS
+            0x82, // device-to-host, standard, endpoint
+            0,
+            u16::from(address),
+            &mut status,
+            timeout(),
+            GFP_KERNEL,
+        )?;
+        Ok(u16::from_le_bytes(status))
+    }
+
+    /// Sends the standard endpoint-halt clear without running Linux's host 
endpoint reset helper.
+    ///
+    /// Navarro uses this only during cold setup, before any video URB has 
touched the endpoint, so
+    /// the host sequence state is already its enumeration value. The working 
DLM wire transaction
+    /// is two raw `CLEAR_FEATURE(ENDPOINT_HALT)` requests 12.647 ms apart 
followed by its vendor
+    /// commit 143 us later. `usb_clear_halt()` adds an xHCI endpoint reset 
after the request; that
+    /// made each Vino operation return about 18 ms late and changed this 
transaction's ordering.
+    pub(crate) fn clear_video_halt_wire(&self, connector: usize) -> Result {
+        let address = 
self.endpoints.video.get(connector).ok_or(EINVAL)?.address();
+        self.control_send(
+            0x01, // USB_REQ_CLEAR_FEATURE
+            0x02, // host-to-device, standard, endpoint
+            0,    // USB_ENDPOINT_HALT
+            u16::from(address),
+            &[],
+            timeout(),
+            GFP_KERNEL,
+        )
+    }
+
+    /// Recover a video endpoint the dock has stalled.
+    ///
+    /// Unlike [`Self::clear_video_halt_wire`], this runs the full 
`usb_clear_halt()`, which resets
+    /// the host side's data toggle as well as sending `CLEAR_FEATURE`. That 
reset is exactly what
+    /// a real stall needs and what the cold-setup path deliberately avoids: 
after a stall the host
+    /// and device sequence state have diverged, and sending the bare request 
leaves every later
+    /// transfer failing.
+    ///
+    /// Nothing else clears a stalled pipe, so without this one stall is 
permanent -- and on a dock
+    /// whose video shares the control endpoint it takes the control plane 
down with it.
+    pub(crate) fn clear_video_halt(&self, connector: usize) -> Result {
+        let ep = self.endpoints.video.get(connector).ok_or(EINVAL)?;
+        self.io.clear_halt(ep)
+    }
+
+    /// Issues a control OUT transfer on EP0.
+    pub(crate) fn control_send(
+        &self,
+        request: u8,
+        request_type: u8,
+        value: u16,
+        index: u16,
+        data: &[u8],
+        timeout: Delta,
+        gfp: Flags,
+    ) -> Result {
+        self.io
+            .control_send(request, request_type, value, index, data, timeout, 
gfp)
+    }
+
+    /// Issues a control IN transfer on EP0.
+    pub(crate) fn control_recv(
+        &self,
+        request: u8,
+        request_type: u8,
+        value: u16,
+        index: u16,
+        data: &mut [u8],
+        timeout: Delta,
+        gfp: Flags,
+    ) -> Result {
+        self.io
+            .control_recv(request, request_type, value, index, data, timeout, 
gfp)
+    }
+
+    /// Selects an alternate setting on the driver's own interface.
+    pub(crate) fn set_alternate_setting(&self, alternate: u8) -> Result {
+        self.io.set_alternate_setting(alternate)
+    }
+}
+/// EP84 (dock-to-host) transfer size.
+///
+/// Replies larger than one transfer are delivered as consecutive fragments.
+pub(crate) const EP84_BUF: usize = 4096;

Reply via email to