[PATCH v3 3/5] rust: usb: expose device descriptor fields and queue readiness

Mike Lothian <[email protected]>
Newsgroups org.kernel.vger.rust-for-linux,org.kernel.vger.linux-kernel,org.kernel.vger.linux-usb
Message-ID <[email protected]>
A driver that identifies hardware before it decides to drive it needs the
device descriptor: idVendor, idProduct, bcdDevice, bcdUSB, the enumerated
speed, and the cached iManufacturer, iProduct and iSerialNumber strings.
bcdDevice in particular is the only revision a driver can read without
already speaking the device's own protocol.

Add can_send_n() alongside them, which reports whether the next count
queue slots can be submitted without waiting and reaps completed slots on
the way. A protocol that must not block halfway through a multi-URB record
uses it to defer the whole record and service its control plane first.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Mike Lothian <[email protected]>
---
 rust/kernel/usb.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 96 insertions(+)

diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index ad40c814616a..782bae53584d 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -1430,6 +1430,7 @@ fn is_active(&self) -> bool {
         matches!(self.urb, Some(QueueUrb::Active(_)))
     }
 
+    #[inline]
     fn wait(&self, timeout: Delta) -> bool {
         let millis = timeout.as_millis();
         let millis = if millis <= 0 {
@@ -1441,6 +1442,7 @@ fn wait(&self, timeout: Delta) -> bool {
             .wait_for_completion_timeout(crate::time::msecs_to_jiffies(millis))
     }
 
+    #[inline]
     fn finish(&mut self) -> Result<(i32, usize)> {
         let state = self.urb.take().ok_or(EIO)?;
         let active = match state {
@@ -1700,6 +1702,34 @@ fn reap(&mut self, i: usize, timeout: Delta) -> Result<bool> {
         Ok(true)
     }
 
+    /// Reports whether the next `count` queue slots can be submitted without waiting.
+    ///
+    /// Completed slots are reaped and any transfer error is returned. This is useful when a
+    /// higher-level protocol must not block halfway through a multi-URB record while waiting for
+    /// endpoint progress; callers can defer the whole record and service its control plane first.
+    #[inline]
+    pub fn can_send_n(&mut self, io: &Io<'_>, count: usize) -> Result<bool> {
+        self.inner.check(io)?;
+        if count > self.slots.len() {
+            return Ok(false);
+        }
+        for off in 0..count {
+            let i = (self.cursor + off) % self.slots.len();
+            if self.slots[i].is_active() {
+                if !self.slots[i].wait(Delta::ZERO) {
+                    return Ok(false);
+                }
+                // `wait_for_completion_timeout()` consumes the completion signal. Reap the URB
+                // now rather than leaving `send()` to wait for the signal a second time.
+                let (status, _) = self.slots[i].finish()?;
+                if status != 0 {
+                    return Err(Error::from_errno(status));
+                }
+            }
+        }
+        Ok(true)
+    }
+
     /// Submits `data` as a bulk OUT transfer without waiting for it to complete.
     ///
     /// If the slot about to be reused still has a transfer outstanding, this blocks up to
@@ -2626,6 +2656,72 @@ fn inner(&self) -> &bindings::usb_device {
     fn devnum(&self) -> u32 {
         self.inner().devnum as u32
     }
+
+    /// Returns the `idVendor` of the device descriptor.
+    pub fn vendor_id(&self) -> u16 {
+        self.inner().descriptor.idVendor
+    }
+
+    /// Returns the `idProduct` of the device descriptor.
+    pub fn product_id(&self) -> u16 {
+        self.inner().descriptor.idProduct
+    }
+
+    /// Returns the `bcdDevice` of the device descriptor.
+    ///
+    /// Vendors conventionally use this as the device revision, and it is the only version a driver
+    /// can read without speaking the device's own protocol.
+    pub fn bcd_device(&self) -> u16 {
+        self.inner().descriptor.bcdDevice
+    }
+
+    /// Returns the `bcdUSB` of the device descriptor.
+    pub fn bcd_usb(&self) -> u16 {
+        self.inner().descriptor.bcdUSB
+    }
+
+    /// Returns the enumerated bus speed as a human-readable string.
+    pub fn speed_str(&self) -> &'static str {
+        match self.inner().speed {
+            bindings::usb_device_speed_USB_SPEED_LOW => "low (1.5 Mbps)",
+            bindings::usb_device_speed_USB_SPEED_FULL => "full (12 Mbps)",
+            bindings::usb_device_speed_USB_SPEED_HIGH => "high (480 Mbps)",
+            bindings::usb_device_speed_USB_SPEED_WIRELESS => "wireless",
+            bindings::usb_device_speed_USB_SPEED_SUPER => "super (5 Gbps)",
+            bindings::usb_device_speed_USB_SPEED_SUPER_PLUS => "super-plus (10+ Gbps)",
+            _ => "unknown",
+        }
+    }
+
+    /// Returns the device's `iManufacturer` string, if the core cached one.
+    pub fn manufacturer(&self) -> Option<&CStr> {
+        // SAFETY: `manufacturer` is either null or a NUL-terminated string owned by the USB core
+        // for as long as the device exists, which outlives the borrow of `self`.
+        unsafe { Self::opt_cstr(self.inner().manufacturer) }
+    }
+
+    /// Returns the device's `iProduct` string, if the core cached one.
+    pub fn product(&self) -> Option<&CStr> {
+        // SAFETY: As for `manufacturer`.
+        unsafe { Self::opt_cstr(self.inner().product) }
+    }
+
+    /// Returns the device's `iSerialNumber` string, if the core cached one.
+    pub fn serial(&self) -> Option<&CStr> {
+        // SAFETY: As for `manufacturer`.
+        unsafe { Self::opt_cstr(self.inner().serial) }
+    }
+
+    /// # Safety
+    ///
+    /// `p` must be null or point to a NUL-terminated string that outlives `'a`.
+    unsafe fn opt_cstr<'a>(p: *mut crate::ffi::c_char) -> Option<&'a CStr> {
+        if p.is_null() {
+            return None;
+        }
+        // SAFETY: The caller guarantees `p` is a NUL-terminated string valid for `'a`.
+        Some(unsafe { CStr::from_char_ptr(p) })
+    }
 }
 
 impl Device<device::Bound> {
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.