From nobody Sat Jul 25 22:32:24 2026 Received: from mail-yx2-f1.google.com (mail-yx2-f1.google.com [74.125.224.129]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 19D6936604C for ; Sun, 12 Jul 2026 21:09:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.129 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783890554; cv=none; b=XlLtViaBZHOEqkGgRdPGD6HpSzUB4p0pW1mAFvCNFB6iKu94vkQnI8FwIAmyVW1A5cL2kmW5M+jMaR6B8W5uZLjwiQ9HQqL95kCfKozUlVSGIVBC/tZ8+9IEypnXXp36Du8IxUjCSVSF+sjXMINpHATjrXADNB+cpcoWrEgMu04= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783890554; c=relaxed/simple; bh=wt+tB9y390TcpLxqDw+hREzmpPjKqG33LQ5CkHM9iQo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=HiQMl3ta+bBvnVE3Sb7xV7SwOrH5kT4eDBnUdOM9FTfZDUSCmnOQNgw3MJ5T4xVcy9kQUBzE+iD1jsv5V9ccpaIFCkWNjeokPEAuXR6FVxmzR9y2yR1Ko1nn3Nltv5dmfVvZc8WDh7gdPOK47jukoS2v2d4eTwcV7jnP+eaDzek= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MYqt7GiO; arc=none smtp.client-ip=74.125.224.129 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MYqt7GiO" Received: by mail-yx2-f1.google.com with SMTP id 956f58d0204a3-6628252d235so822109d50.1 for ; Sun, 12 Jul 2026 14:09:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783890549; x=1784495349; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=qgFTptXx85PWXDmKZTTKsGUVnhLFqzTUqDOgVugKp74=; b=MYqt7GiOk1UeAEx8+wfrNjly8837ljNvO/eoS/Rdv5KvJXgt7urgp53BqmPYtGT0CH 2Syhe8xvCHw26J26sV0RWEJYswXehTWSUNCaMrD7H7znDwe29Jsb9/F0SPXrsxWn7mwR YTPh92mE7Rff/nFmv2+pTPHnwL3D90w7fWfbJl1N/Ia88ZxEjXIH8g9UvqBnZWk2d36n 3c/gXvqsprhMULlc5zrLWqenyOpE1hy0V+UeotNnwo1ytriAa6Ns1Nl15ubhWo/lKuPT /KVWYy7y5Agpv+kHySKW2tTlq/hf+JMXEEZow0+Efg1QFLTTfoW6GT4v5fonf5Ves3SU LFSQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783890549; x=1784495349; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=qgFTptXx85PWXDmKZTTKsGUVnhLFqzTUqDOgVugKp74=; b=hvCqcLRRdNLD4aHyIF/TlB4Ms7EKd2Z7+6iFD8ROYRtvqtHxoK1KoGkInJQS6j0AXg PQhd995VCjalithMKv51zuf56uBuUl93ryutCIMOqRLvdc7MC8qARtMAmbRyJApc/7Ri 60uJD/sDyORuSKJuw9kwUQYdupT+o9tEsUqP5nurcIu/6QHYwnkUsfeiKCKbnivVulof lCQVVvl7o5rPcMMvH8KDLuz0+TIjo6CJillX9ZXpJR8K0Dq7mNQetVVmpGjcIO8sxfsF XiNMQlAQSYG/CF55+5xMp6+AD/UKX51OX7n3UcdovpWtsGfILJNhjC4R6sKWBFyufzm1 IkIw== X-Gm-Message-State: AOJu0YwsbQs20b//MeVisHywAD8L1PIXJohMLZEIGEKOFRAUz1BmpIi/ Rk7dA+9R8R590pVaw5X74aFmvGfeGkNIFoJUKln4KXXNOWidV54VHHsB X-Gm-Gg: AfdE7ckCLb3lk6kFI7KLOoMOrnZhZxCqH+rTDZpc2urWtzEjwgUeaoG6pM8ZGLUnnE+ X1wKIpOIF+myb90INKcLoHHXn5PI6HLcFujypPcphmdRG/aUqGmygXHH1JH0XEP8+gaK9Emz+U6 Opj6gK93+nxyrPk/n3yJABP0i01NQWWmVMy2mebdqZcyyE8CHNaLBd7hr+M8Uh+b6hKaCmqTQIB LBfM9V1TzCO4dnQHuTBf+l805sUaVA4KxPaRJsBbpYKoxXdE5tR/f0whs1quH3gMGwG16EQ8KAn PkDtYZ8ytmqBzj+2TeAZ/fi7ZPxfWvx9titIlhyhgm7iDKiXsbFvYyeCAzGWkuoe9LSD01iSEqT UoqsJSVHxdylnjj20A7hUfuyU5hCDFYGaJsbC/8jGhCx8XxeBGmXbRjByTRzHW90WE6DFPp777l ur7r3uAlQbJ0wleW9MyDJ+cl1t5WpwZuoH/HmBRCRz/17KcZ8bJfu9S3/i1IlwXWBIaPVnSj8c4 oCgiPdK X-Received: by 2002:a05:690c:c16:b0:81d:2254:8504 with SMTP id 00721157ae682-81e9016d68emr50666257b3.56.1783890549094; Sun, 12 Jul 2026 14:09:09 -0700 (PDT) Received: from [127.0.0.2] (76-234-45-214.lightspeed.cicril.sbcglobal.net. [76.234.45.214]) by smtp.gmail.com with ESMTPSA id 00721157ae682-81e6be9834dsm99737387b3.9.2026.07.12.14.09.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 12 Jul 2026 14:09:08 -0700 (PDT) From: Colin Braun X-Google-Original-From: Colin Braun Date: Sun, 12 Jul 2026 16:07:58 -0500 Subject: [RFC PATCH 1/4] rust: usb: add USB ch9 standard descriptors and constants Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260712-urb-abstraction-v1-v1-1-9fa011634ead@gmail.com> References: <20260712-urb-abstraction-v1-v1-0-9fa011634ead@gmail.com> In-Reply-To: <20260712-urb-abstraction-v1-v1-0-9fa011634ead@gmail.com> To: Miguel Ojeda , Greg Kroah-Hartman , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , Mauro Carvalho Chehab , Alan Stern , Mathias Nyman Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-usb@vger.kernel.org, linux-media@vger.kernel.org, Colin Braun X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1783890545; l=10602; i=colin.braun.cl@gmail.com; s=20260630; h=from:subject:message-id; bh=wt+tB9y390TcpLxqDw+hREzmpPjKqG33LQ5CkHM9iQo=; b=0EGedI4jFhW8LW7B5X37qUv2IHDeUccyF/02Lw1xsp8e3A5oQNs1UZtnvFo2c2f3wP5Tc7Kfk PyUgtbsl48QB4XI686nMZuc0Hi/yIEz0kWlt7AZJd14o71gI5TI4cm7 X-Developer-Key: i=colin.braun.cl@gmail.com; a=ed25519; pk=DJ5DDAbTXtW6OgSgetK6R1PUfhtCNSvSayX3Je17ZAY= Add a new module for USB chapter 9 structs and constants. The goal of this patch is to provide safe Rust wrappers around the C types from include/uapi/linux/usb/ch9.h. These are needed by drivers and other USB abstractions (interfaces, endpoints, URBs, etc.). Signed-off-by: Colin Braun --- rust/kernel/usb.rs | 2 + rust/kernel/usb/ch9.rs | 295 +++++++++++++++++++++++++++++++++++++++++++++= ++++ 2 files changed, 297 insertions(+) diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 7aff0c82d0af..3ae9c05cd32a 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -31,6 +31,8 @@ ptr::NonNull, }; =20 +pub mod ch9; + /// An adapter for the registration of USB drivers. pub struct Adapter(T); =20 diff --git a/rust/kernel/usb/ch9.rs b/rust/kernel/usb/ch9.rs new file mode 100644 index 000000000000..f451b7273731 --- /dev/null +++ b/rust/kernel/usb/ch9.rs @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Abstractions for USB chapter 9. +//! +//! C header: [`include/linux/usb/ch9.h`](srctree/include/linux/usb/ch9.h) + +use crate::fmt; + +/// USB interface class code. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct InterfaceClass(u8); + +impl InterfaceClass { + /// Create an [`InterfaceClass`] from a raw `u8` class code. + pub const fn from_raw(class: u8) -> Self { + Self(class) + } + + /// Get the raw `u8` class code value. + pub const fn as_raw(self) -> u8 { + self.0 + } +} + +macro_rules! define_all_usb_classes { + ( + $($variant:ident =3D $binding:expr,)+ + ) =3D> { + impl InterfaceClass { + $( + #[allow(missing_docs)] + pub const $variant: Self =3D Self($binding as u8); + )+ + } + + impl fmt::Display for InterfaceClass { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + $( + &Self::$variant =3D> write!(f, stringify!($variant= )), + )+ + _ =3D> ::fmt(self, f), + } + } + } + }; +} + +define_all_usb_classes! { + PER_INTERFACE =3D bindings::USB_CLASS_PER_INTERFACE, + AUDIO =3D bindings::USB_CLASS_AUDIO, + COMM =3D bindings::USB_CLASS_COMM, + HID =3D bindings::USB_CLASS_HID, + PHYSICAL =3D bindings::USB_CLASS_PHYSICAL, + STILL_IMAGE =3D bindings::USB_CLASS_STILL_IMAGE, + PRINTER =3D bindings::USB_CLASS_PRINTER, + MASS_STORAGE =3D bindings::USB_CLASS_MASS_STORAGE, + HUB =3D bindings::USB_CLASS_HUB, + CDC_DATA =3D bindings::USB_CLASS_CDC_DATA, + CSCID =3D bindings::USB_CLASS_CSCID, + CONTENT_SEC =3D bindings::USB_CLASS_CONTENT_SEC, + VIDEO =3D bindings::USB_CLASS_VIDEO, + WIRELESS_CONTROLLER =3D bindings::USB_CLASS_WIRELESS_CONTROLLER, + PERSONAL_HEALTHCARE =3D bindings::USB_CLASS_PERSONAL_HEALTHCARE, + AUDIO_VIDEO =3D bindings::USB_CLASS_AUDIO_VIDEO, + BILLBOARD =3D bindings::USB_CLASS_BILLBOARD, + USB_TYPE_C_BRIDGE =3D bindings::USB_CLASS_USB_TYPE_C_BRIDGE, + MCTP =3D bindings::USB_CLASS_MCTP, + MISC =3D bindings::USB_CLASS_MISC, + APP_SPEC =3D bindings::USB_CLASS_APP_SPEC, + VENDOR_SPEC =3D bindings::USB_CLASS_VENDOR_SPEC, +} + +/// USB interface descriptor. +/// +/// Wraps the C `struct usb_interface_descriptor` defined in +/// `include/uapi/linux/usb/ch9.h`. Corresponds to USB 2.0 spec =C2=A79.6.= 5, +/// table 9-12. +#[repr(transparent)] +pub struct InterfaceDescriptor(bindings::usb_interface_descriptor); + +impl InterfaceDescriptor { + /// Returns the size of this descriptor in bytes. + #[allow(non_snake_case)] + pub fn bLength(&self) -> u8 { + self.0.bLength + } + + /// Returns the descriptor type (`USB_DT_INTERFACE`). + #[allow(non_snake_case)] + pub fn bDescriptorType(&self) -> u8 { + self.0.bDescriptorType + } + + /// Returns the interface number (zero-based). + #[allow(non_snake_case)] + pub fn bInterfaceNumber(&self) -> u8 { + self.0.bInterfaceNumber + } + + /// Returns the alternate setting number. + #[allow(non_snake_case)] + pub fn bAlternateSetting(&self) -> u8 { + self.0.bAlternateSetting + } + + /// Returns the number of endpoints used by this interface (excluding + /// the default control endpoint). + #[allow(non_snake_case)] + pub fn bNumEndpoints(&self) -> u8 { + self.0.bNumEndpoints + } + + /// Returns the interface class code. + #[allow(non_snake_case)] + pub fn bInterfaceClass(&self) -> InterfaceClass { + InterfaceClass(self.0.bInterfaceClass) + } + + /// Returns the interface subclass code. + #[allow(non_snake_case)] + pub fn bInterfaceSubClass(&self) -> u8 { + self.0.bInterfaceSubClass + } + + /// Returns the interface protocol code. + #[allow(non_snake_case)] + pub fn bInterfaceProtocol(&self) -> u8 { + self.0.bInterfaceProtocol + } + + /// Returns the index of the string descriptor describing this + /// interface. + #[allow(non_snake_case)] + pub fn iInterface(&self) -> u8 { + self.0.iInterface + } +} + +/// USB endpoint descriptor. +/// +/// Wraps the C `struct usb_endpoint_descriptor` defined in +/// `include/uapi/linux/usb/ch9.h`. Corresponds to USB 2.0 spec =C2=A79.6.= 6, +/// table 9-13. +#[repr(transparent)] +pub struct EndpointDescriptor(bindings::usb_endpoint_descriptor); + +impl EndpointDescriptor { + /// Returns the endpoint address (direction + endpoint number). + #[allow(non_snake_case)] + pub fn bEndpointAddress(&self) -> u8 { + self.0.bEndpointAddress + } + + /// Returns the endpoint attributes (transfer type). + #[allow(non_snake_case)] + pub fn bmAttributes(&self) -> u8 { + self.0.bmAttributes + } + + /// Returns the maximum packet size for this endpoint. + #[allow(non_snake_case)] + pub fn wMaxPacketSize(&self) -> u16 { + self.0.wMaxPacketSize + } + + /// Returns the interval for isochronous/interrupt endpoints. + #[allow(non_snake_case)] + pub fn bInterval(&self) -> u8 { + self.0.bInterval + } +} + +/// USB control request (SETUP packet). +/// +/// Wraps the C `struct usb_ctrlrequest` defined in +/// `include/uapi/linux/usb/ch9.h`. Corresponds to USB 2.0 spec =C2=A79.3, +/// table 9-2. +#[repr(transparent)] +pub struct CtrlRequest(bindings::usb_ctrlrequest); + +impl CtrlRequest { + /// Creates a new control request from its constituent fields. + pub const fn new( + requesttype: RequestType, + request: u8, + value: u16, + index: u16, + length: u16, + ) -> Self { + Self(bindings::usb_ctrlrequest { + bRequestType: requesttype.0, + bRequest: request, + wValue: value.to_le(), + wIndex: index.to_le(), + wLength: length.to_le(), + }) + } + + /// Returns the data-transfer direction encoded in the setup packet. + pub fn direction(&self) -> Direction { + if self.requesttype() & Direction::In as u8 =3D=3D 0 { + Direction::Out + } else { + Direction::In + } + } + + /// Returns the `bRequestType` field. + pub fn requesttype(&self) -> u8 { + self.0.bRequestType + } + + /// Returns the `bRequest` field. + pub fn request(&self) -> u8 { + self.0.bRequest + } + + /// Returns the `wValue` field (native endian). + pub fn value(&self) -> u16 { + u16::from_le(self.0.wValue) + } + + /// Returns the `wIndex` field (native endian). + pub fn index(&self) -> u16 { + u16::from_le(self.0.wIndex) + } + + /// Returns the `wLength` field (native endian). + pub fn length(&self) -> u16 { + u16::from_le(self.0.wLength) + } +} + +/// USB data transfer direction for a control request. +/// +/// Used in the `bRequestType` field of a SETUP packet +/// (USB 2.0 spec =C2=A79.3, table 9-2). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Direction { + /// Host-to-device. + Out =3D bindings::USB_DIR_OUT as u8, + /// Device-to-host. + In =3D bindings::USB_DIR_IN as u8, +} + +/// USB request type for a control request. +/// +/// Used in the `bmRequestType` field of a SETUP packet to distinguish +/// standard, class, and vendor-specific requests. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Type { + /// Standard request defined by the USB specification. + Standard =3D bindings::USB_TYPE_STANDARD as u8, + /// Class-specific request defined by a USB class specification. + Class =3D bindings::USB_TYPE_CLASS as u8, + /// Vendor-specific request. + Vendor =3D bindings::USB_TYPE_VENDOR as u8, + /// Reserved for future use. + Reserved =3D bindings::USB_TYPE_RESERVED as u8, +} + +/// USB setup packet request type (`bmRequestType`). +/// +/// Encodes the direction, type, and recipient of a control request. +pub struct RequestType(u8); + +/// USB request recipient for a control request. +/// +/// Used in the `bmRequestType` field of a SETUP packet. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Recipient { + /// Recipient is the device. + Device =3D bindings::USB_RECIP_DEVICE as u8, + /// Recipient is an interface. + Interface =3D bindings::USB_RECIP_INTERFACE as u8, + /// Recipient is an endpoint. + Endpoint =3D bindings::USB_RECIP_ENDPOINT as u8, + /// None of the above. + Other =3D bindings::USB_RECIP_OTHER as u8, +} + +impl RequestType { + /// Creates a [`RequestType`] from a direction, type, and recipient. + /// + /// The three fields are packed into a single `u8` per the USB + /// specification (USB 2.0 spec =C2=A79.3, table 9-2). + pub const fn new(dir: Direction, r#type: Type, recipient: Recipient) -= > Self { + Self(dir as u8 | r#type as u8 | recipient as u8) + } +} --=20 2.54.0 From nobody Sat Jul 25 22:32:24 2026 Received: from mail-yx2-f1.google.com (mail-yx2-f1.google.com [74.125.224.129]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 21DD8377547 for ; Sun, 12 Jul 2026 21:09:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.129 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783890553; cv=none; b=DrV0Em121pyqyfWqtgJSOMFcZjJEnBvOEnis53xlBOOtyTuBi2wASEar9cRxWCzpg+9L3W7fKxeHJNcJmyOWB/MxYJmBs15HbhqVf0lnLvIuRKM1EzSsDQtApALy+vOHJnmamrf6rcpBKDyoFkPEl13lfRTObDpG9fObFi4iwss= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783890553; c=relaxed/simple; bh=433A1TS43hWR1H7iwXEPSuG1bD/g08svGDVfoZJ6NgI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Ie3hrOKCpuIX/Uw5uRuAOVAgNEjAEDV7+gJklxQwecH2zmLLoiroWlKclh8egAqoq9ezVMGVWWjeUv0mdlhKg+HKpjLQ0pE6YdGWsX8XD2+Owvh6FF4whdkk4vB/OwDcJO5YNoGmoB0IhB/tbRlkz3Mq2k+CCZ0c0reZVRj1SdI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=iGU+sk3i; arc=none smtp.client-ip=74.125.224.129 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="iGU+sk3i" Received: by mail-yx2-f1.google.com with SMTP id 00721157ae682-7fc7dc7bebcso26604777b3.0 for ; Sun, 12 Jul 2026 14:09:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783890551; x=1784495351; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=GuLQmTHp2PWYFgz4XTneHKxNTQUROfu+lDrq700PFaQ=; b=iGU+sk3iFfdYU/oq0wtIh41DFVL0+3aFoPjZFZmL1GWmVyyS4TPLUxtABr5ZA8PUgn 4TFBUwqlr2hHJPxb5zk/JJKsWoOYgc3halDL3RV+nh5Q3L+w3277Z67Xoq3Np3/XlbzT u5yi4LzizLCKq/9VybB3ivSjmxBKJTQsexXrvlayIC/eRN6+kRD0C6hYSwIcSw7KZJin /JoQUIu9WNDnOcGxhTc4VGDWfrqQvKJpTZi2TGiiy1E9EYqLHpGfJSTbmuSc0s/+Fble LxZJrEv5tRbN8m+ayyLpHnGAjWoHaSDV9yxnXSotScJ2BKoZfLpl8NNIWb18ltL1e0x5 Vxbg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783890551; x=1784495351; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=GuLQmTHp2PWYFgz4XTneHKxNTQUROfu+lDrq700PFaQ=; b=j41qAvGCMi1xo/tOc+h/CB8SXd0iNoBOmXfZv4DvPijowNAuubwD/VopCszT/E9aR7 cv9pg5l36zfWTl/4iTr+8eE7qx92enhuBb5M5/9vuo5wpXrSE9gd0nZt5t9Bjz3TS/sb gip7+QqDeShGTjqQkeYOrlcFbDsSeHYSsVTXnemD7OQIG87+aFFeDJA1Q3NN7CIG1qdc 4aSuLN4tI0dY7ri2P/V7SCtu6DzA5pwB1mwk2nTrTutrvvW5ccp3RIPu2wk/C0Z5crXs wbVka8tbyCcsO7Wno3tQw9ZiLxPETp+gXT1EsPZ6QyXljzRzkUDKBDKAmAkh1w1JVkKC +QBw== X-Gm-Message-State: AOJu0Yzvy0IsXoXLkk4j9VLYOjCAlemEU0leNSNh9NOjo2hRB5kN5Okm UdrTbUyVk1vfYhQKWLAUJYvVVdeiAgFbyE03C9T5O9ymMjDYcc87zJfa X-Gm-Gg: AfdE7clLQd3FzDtcoxtkANWaF3aarhiWyy6rAwfKp2SCtEg4oCBvLaT6ajzAWPUsNXV ODwpBAF/qz+WH6h/VJeDAsP2+14LgvnWJRbV8bOa4BUuOdeudvHFqUo3Babe20d9L7zxqJ5NxPQ +z6zZ9Itwua5/eIEWwNUmzxecW4DNKCQEdWcl0+gZV9I7ONC9s9OJurXmvQCvLt7rEiXKeZIONJ /1poplKDHijXhxgtTMYm3Oxn7hR6V9qEZ8be4ZSss52n4HlxJQEcw/4tpqvUIJO0SQkf2CIZyLk G30BV8JQejncpEtTKKCjDepOq2L9bXHM4bl+zTHHXiosUSmfbkjHLBDDnWXCnc2gLX1/gkHX3U+ gItOpsSwdZz5A2ganyWWDTUjrsU0O3wvKUtV7TvHA45gLcqAWpGn98nFIgwSo69s8yjgWJdPEEW 3fBzCOd4YFDaWU5vLK22wHLpfxBiYOM+NGErbc1gnw1CxSq+QzW5/SpQE2cAa7ctOFsm0KcuSp1 a6vo9Ve X-Received: by 2002:a05:690c:6204:b0:81e:ae6f:ac0a with SMTP id 00721157ae682-81eae6fad86mr1460677b3.24.1783890551014; Sun, 12 Jul 2026 14:09:11 -0700 (PDT) Received: from [127.0.0.2] (76-234-45-214.lightspeed.cicril.sbcglobal.net. [76.234.45.214]) by smtp.gmail.com with ESMTPSA id 00721157ae682-81e6be9834dsm99737387b3.9.2026.07.12.14.09.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 12 Jul 2026 14:09:10 -0700 (PDT) From: Colin Braun X-Google-Original-From: Colin Braun Date: Sun, 12 Jul 2026 16:07:59 -0500 Subject: [RFC PATCH 2/4] rust: usb: add usb host interface and endpoint abstractions Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260712-urb-abstraction-v1-v1-2-9fa011634ead@gmail.com> References: <20260712-urb-abstraction-v1-v1-0-9fa011634ead@gmail.com> In-Reply-To: <20260712-urb-abstraction-v1-v1-0-9fa011634ead@gmail.com> To: Miguel Ojeda , Greg Kroah-Hartman , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , Mauro Carvalho Chehab , Alan Stern , Mathias Nyman Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-usb@vger.kernel.org, linux-media@vger.kernel.org, Colin Braun X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1783890545; l=9945; i=colin.braun.cl@gmail.com; s=20260630; h=from:subject:message-id; bh=433A1TS43hWR1H7iwXEPSuG1bD/g08svGDVfoZJ6NgI=; b=WweGzKtieH6aF7Jm/YkoDuks/UEPttylLJ4vxf3+OmeW7kCXqbsfMrjyuVxXpABIehw5bDsOB Eq5HcWRBcysD6DhIAODcSTi9AtwTpUN5ISASMymLKhTYfaO2VMTmG5i X-Developer-Key: i=colin.braun.cl@gmail.com; a=ed25519; pk=DJ5DDAbTXtW6OgSgetK6R1PUfhtCNSvSayX3Je17ZAY= The goal of this patch is to create safe USB host interface and endpoint descriptors necessary for USB driver development. Specifically, this safely wraps the C side `struct usb_host_interface` and `struct usb_host_endpoint` types. Additionally, support for querying and configuring USB interface altsettings is added to the existing Rust `Interface` and `Device` types. The `Device` struct is made public for two reasons: - Sending control URBs in a way that makes it clear that endpoint 0 is a shared device endpoint, not specific to an interface. - Allows for the possiblity of a future driver that binds to the USB device rather than an interface. Signed-off-by: Colin Braun --- rust/kernel/usb.rs | 194 +++++++++++++++++++++++++++++++++++++++++++++++++= +++- 1 file changed, 191 insertions(+), 3 deletions(-) diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 3ae9c05cd32a..21dddc735bdf 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -20,6 +20,12 @@ prelude::*, sync::aref::AlwaysRefCounted, types::Opaque, + usb::ch9::{ + Direction, + EndpointDescriptor, + InterfaceClass, + InterfaceDescriptor, // + }, ThisModule, // }; use core::{ @@ -29,6 +35,7 @@ MaybeUninit, // }, ptr::NonNull, + slice, // }; =20 pub mod ch9; @@ -358,6 +365,173 @@ impl Interface { fn as_raw(&self) -> *mut bindings::usb_interface { self.0.get() } + + fn inner(&self) -> &bindings::usb_interface { + // SAFETY: The type invariants guarantee that `self.0` wraps a val= id + // `struct usb_interface`. + unsafe { &*self.as_raw() } + } + + /// Returns the current alternate setting for this interface. + pub fn cur_altsetting(&self) -> &HostInterface { + // SAFETY: `cur_altsetting` is a valid `struct usb_host_interface` + // pointer provided by the USB core. `HostInterface` is + // `#[repr(transparent)]` over it. + unsafe { &*(self.inner().cur_altsetting as *const HostInterface) } + } + + /// Returns all alternate settings for this interface. + pub fn altsettings(&self) -> &[HostInterface] { + // SAFETY: `altsetting` is a valid array of `num_altsetting` + // entries provided by the USB core. `HostInterface` is + // `#[repr(transparent)]` over `usb_host_interface`. + unsafe { + slice::from_raw_parts( + self.inner().altsetting as *const HostInterface, + self.inner().num_altsetting as usize, + ) + } + } +} + +impl Interface { + /// Select an alternate setting for this interface. + /// + /// On success the device switches to the given alternate setting, + /// which may change the set of active endpoints. This is a convenience + /// wrapper around [`Device::set_interface`]. + pub fn set_interface(&self, altsetting: u8) -> Result { + let dev: &Device =3D self.as_ref(); + dev.set_interface(self.cur_altsetting().number(), altsetting) + } +} + +/// Abstraction for the USB Host Interface structure, i.e. `struct usb_hos= t_interface`. +#[repr(transparent)] +pub struct HostInterface(Opaque); + +impl HostInterface { + fn inner(&self) -> &bindings::usb_host_interface { + // SAFETY: The type invariants guarantee that `self.0` wraps a val= id + // `struct usb_host_interface`. + unsafe { &*self.0.get() } + } + + /// Returns the interface descriptor. + fn desc(&self) -> &InterfaceDescriptor { + // SAFETY: `desc` is a valid `struct usb_interface_descriptor` + // embedded in `usb_host_interface`. `InterfaceDescriptor` is + // `#[repr(transparent)]` over it. + unsafe { &*((core::ptr::from_ref(&self.inner().desc)).cast()) } + } + + /// Returns the list of endpoints in this alternate setting. + pub fn endpoints(&self) -> &[HostEndpoint] { + // SAFETY: `endpoint` is a valid array of `bNumEndpoints` entries. + // `HostEndpoint` is `#[repr(transparent)]` over + // `usb_host_endpoint`. + unsafe { + core::ptr::slice_from_raw_parts( + self.inner().endpoint as *const HostEndpoint, + self.desc().bNumEndpoints() as usize, + ) + .as_ref() + .unwrap_or(&[]) + } + } + + /// Returns the interface number (`bInterfaceNumber`). + pub fn number(&self) -> u8 { + self.desc().bInterfaceNumber() + } + + /// Returns the alternate setting number (`bAlternateSetting`). + pub fn alternate_setting(&self) -> u8 { + self.desc().bAlternateSetting() + } + + /// Returns the interface class (`bInterfaceClass`). + pub fn class(&self) -> InterfaceClass { + self.desc().bInterfaceClass() + } +} + +/// USB endpoint transfer type. +/// +/// Maps to the `bmAttributes` field of the endpoint descriptor +/// (`USB_ENDPOINT_XFER_*` constants). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum EndpointType { + /// Control endpoint. + Control =3D bindings::USB_ENDPOINT_XFER_CONTROL as u8, + /// Isochronous endpoint. + Isoc =3D bindings::USB_ENDPOINT_XFER_ISOC as u8, + /// Bulk endpoint. + Bulk =3D bindings::USB_ENDPOINT_XFER_BULK as u8, + /// Interrupt endpoint. + Int =3D bindings::USB_ENDPOINT_XFER_INT as u8, +} + +/// Abstraction for the USB Host Endpoint structure, i.e. [`struct usb_hos= t_endpoint`]. +/// +/// [`struct usb_host_endpoint`]: https://docs.kernel.org/driver-api/usb/u= sb.html#c.usb_host_endpoint +#[repr(transparent)] +pub struct HostEndpoint(Opaque); + +impl HostEndpoint { + fn inner(&self) -> &bindings::usb_host_endpoint { + // SAFETY: The type invariants guarantee that `self.0` wraps a val= id + // `struct usb_host_endpoint`. + unsafe { &*self.0.get() } + } + + /// Returns the endpoint descriptor. + fn desc(&self) -> &EndpointDescriptor { + // SAFETY: `desc` is a valid `struct usb_endpoint_descriptor` + // embedded in `usb_host_endpoint`. `EndpointDescriptor` is + // `#[repr(transparent)]` over it. + unsafe { &*(core::ptr::from_ref(&self.inner().desc).cast()) } + } + + /// Returns the direction of this endpoint (IN or OUT). + pub fn endpoint_dir(&self) -> Direction { + if self.desc().bEndpointAddress() & Direction::In as u8 =3D=3D 0 { + Direction::Out + } else { + Direction::In + } + } + + /// Returns the endpoint number (0-15). + pub fn endpoint_number(&self) -> u8 { + self.desc().bEndpointAddress() & bindings::USB_ENDPOINT_NUMBER_MAS= K as u8 + } + + /// Returns the transfer type of this endpoint. + pub fn endpoint_type(&self) -> EndpointType { + let val =3D self.desc().bmAttributes() & bindings::USB_ENDPOINT_XF= ERTYPE_MASK as u8; + // SAFETY: `bmAttributes` masked with `USB_ENDPOINT_XFERTYPE_MASK` + // is guaranteed to be 0-3, which maps exactly to the four + // `EndpointType` variants. + unsafe { core::mem::transmute::(val) } + } + + /// Returns the interval for interrupt and isochronous endpoints. + pub fn interval(&self) -> u8 { + self.desc().bInterval() + } + + /// Returns the maximum packet size for this endpoint. + pub fn maxp(&self) -> u16 { + u16::from_le(self.desc().wMaxPacketSize()) & bindings::USB_ENDPOIN= T_MAXP_MASK as u16 + } + + /// Returns the high-speed multiplier for isochronous endpoints. + pub fn maxp_mult(&self) -> u16 { + (u16::from_le(self.desc().wMaxPacketSize()) & bindings::USB_EP_MAX= P_MULT_MASK as u16) + >> bindings::USB_EP_MAXP_MULT_SHIFT + } } =20 // SAFETY: `usb::Interface` is a transparent wrapper of `struct usb_interf= ace`. @@ -382,8 +556,8 @@ fn as_ref(&self) -> &device::Device { } } =20 -impl AsRef for Interface { - fn as_ref(&self) -> &Device { +impl AsRef> for Interface { + fn as_ref(&self) -> &Device { // SAFETY: `self.as_raw()` is valid by the type invariants. let usb_dev =3D unsafe { bindings::interface_to_usbdev(self.as_raw= ()) }; =20 @@ -428,7 +602,7 @@ unsafe impl Sync for Interface {} /// /// [`struct usb_device`]: https://www.kernel.org/doc/html/latest/driver-a= pi/usb/usb.html#c.usb_device #[repr(transparent)] -struct Device( +pub struct Device( Opaque, PhantomData, ); @@ -439,6 +613,20 @@ fn as_raw(&self) -> *mut bindings::usb_device { } } =20 +impl Device { + /// Select an alternate setting for the given interface. + /// + /// On success the device switches the given interface to the given al= ternate setting, + /// which may change the set of active endpoints. + pub fn set_interface(&self, interface: u8, altsetting: u8) -> Result { + // SAFETY: `self.as_raw()` is a valid `struct usb_device` pointer = by the type + // invariants. `usb_set_interface` is safe to call on a bound devi= ce. + to_result(unsafe { + bindings::usb_set_interface(self.as_raw(), i32::from(interface= ), i32::from(altsetting)) + }) + } +} + // SAFETY: `Device` is a transparent wrapper of a type that doesn't depend= on `Device`'s generic // argument. kernel::impl_device_context_deref!(unsafe { Device }); --=20 2.54.0 From nobody Sat Jul 25 22:32:24 2026 Received: from mail-yx2-f1.google.com (mail-yx2-f1.google.com [74.125.224.129]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0CDC53793A2 for ; Sun, 12 Jul 2026 21:09:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.129 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783890556; cv=none; b=YpIn9RpNMJczkJjTtHY+uVGgV4xEXC9RiGB6JS2hnXyOOUSbdju7uWJ4Q8qqAsSj+akIzctGaIeLSPv9rpTyl7NINNVepfEF9tm4HO5//GlKkLZ+ZLpWFqfVdoXeuzXNL7v/R3nI3fkVEKF6YAZYpMmOZ/rCaYpBTGZlw5Hn1/Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783890556; c=relaxed/simple; bh=KEj2d3o151qmDKc/H1dXxyZtdyvAb7r5gfIIA413u7Q=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=JEP0Dw3Ufie4sebgCajmj8Q6S+Su5eTDN5UCYWIbyiquyVthoQHK5tslS4opkt49xOrQzjEXgp6jj+KWUE6YgaOBb3DibIiv5JwmsAZt0GdVz+287sXvhvTF8sUnn8JWx+OP+b+bcCmr+sgM0nIb3UxSwN6Zwd4PrPu23fGLht0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=XtuTVSnk; arc=none smtp.client-ip=74.125.224.129 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="XtuTVSnk" Received: by mail-yx2-f1.google.com with SMTP id 00721157ae682-81c6e4ce922so20181527b3.0 for ; Sun, 12 Jul 2026 14:09:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783890553; x=1784495353; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=Bfv9SDN5MkAXjEfB0DOwHnNab5SbI48kmmTcYXar85Y=; b=XtuTVSnkEMgA1eUxGb2cU6a/I4eHZ9uuyrNPoq4+iJ1yFjKxImrs4qbV4XV8scaQ9e 48JWPPW1J6I1tDUb6Fn/Cl6+CgqYZsHCeSpN3r/VKQMF8k8xdEy1idakQfGVSe6B9h3J lqyWu0QDpRm8cDQkkNf4zTCE89CbjCDU9YwYWiiyg2fe3qS1v8Oy5jfCQ+AInNBaeO54 v/rvTXauEURWul+5smbRSYpHmL4qJaaQyu/ImNI2jPebCxkLaWr0UB2yttu7ICg6Rz6B 4eJqUPDnA7uZW1IudHzDSHFFKmwXFATt+aDVV47AjufsT+91YSbBx4mVagmS2mkPEk+9 yt3Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783890553; x=1784495353; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=Bfv9SDN5MkAXjEfB0DOwHnNab5SbI48kmmTcYXar85Y=; b=q6ED54MBf3q7TeUdongW6IGxtaQ1a8VCaShOtayvVhJpfzx+8CqwnaJjEqPvZYEKgC u1YMDgDxFO6pImkFSEBNvMPWu2YWe23hYUxURc9cE83uJt8A/n53/Y3hPVtzXZqs8L0a DrIWRzCv4sIm2sczjkBXvbGHJTNzHLO70CN9Klg91d/D3Px66uw+GNiO+gNrAZ6knCMQ LCe06RkwkgOnBiZ7ztCQ0Z0f8TTP5dV3A/qTETbOQHZoxCti98Sq6EEQRgyv196qbXoT Ay/rn1smWgaSp2c/pRDIP7fmCkFAApRR4AEbZOFbmmS7uqGzwUR3IGghcUkaIffqm6nH i2iQ== X-Gm-Message-State: AOJu0YxBdvUPNv22YbbYME+m12C9zeHiCJCQlvbAXNMoOB5b75a8W4Kv 6Mqv8Z8T9PyCbr9hdGXsAtqA5Ayul3RWE2PKCdwyC0YeZ0NHtUczXs0I X-Gm-Gg: AfdE7clW+0QDIIOZdp9az9qcdAoWMluG4AtS+EfdRM6icnrR1HVD0QqTVOj8/e+URoV ZsL9lHIXXAWPtwUrAJORZmcpU68WRe7XB3dn86xKclHoVhrDVNsAyhD9ulKQI4Mi0VQVXAU1OLV mIWKwviPb0XzVyfENTGRwjoFwFOwVbiS89A6fawug51vcsA6tCsdJmDC56/Ub2r0mT8z+K0+mmC aYe7wzciCOYq5oOGCYElrN1jpi4X0dUiiRFZ7a2OqOM4NbocjxAEoTkFu5oj/bSKqck+eSkHxY3 PCB6BhTes4IJ6qOq5Auxx9uEYOlKX3I27movQmSUcjrok9VTyqTCW2PvPVrfOLEfUYJ9xZRbVDa zmISS8pN4QRZNlDOVNRaMoojf8HMHweEnc4ej6Qv5AR0X9DeywFebleqUpVOKZLvObDBR065VXT TE1QDLBhz5SPV1JSTaZ/fkqjXfMRED6Vgqy2gaFhdynY3WUWo0z3RqHiF9CpNdcCej4nE2QQ== X-Received: by 2002:a05:690c:e646:b0:81e:ae6d:cadb with SMTP id 00721157ae682-81eae6dd390mr998877b3.7.1783890552782; Sun, 12 Jul 2026 14:09:12 -0700 (PDT) Received: from [127.0.0.2] (76-234-45-214.lightspeed.cicril.sbcglobal.net. [76.234.45.214]) by smtp.gmail.com with ESMTPSA id 00721157ae682-81e6be9834dsm99737387b3.9.2026.07.12.14.09.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 12 Jul 2026 14:09:12 -0700 (PDT) From: Colin Braun X-Google-Original-From: Colin Braun Date: Sun, 12 Jul 2026 16:08:00 -0500 Subject: [RFC PATCH 3/4] rust: usb: add urb abstraction with control and isochronous support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260712-urb-abstraction-v1-v1-3-9fa011634ead@gmail.com> References: <20260712-urb-abstraction-v1-v1-0-9fa011634ead@gmail.com> In-Reply-To: <20260712-urb-abstraction-v1-v1-0-9fa011634ead@gmail.com> To: Miguel Ojeda , Greg Kroah-Hartman , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , Mauro Carvalho Chehab , Alan Stern , Mathias Nyman Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-usb@vger.kernel.org, linux-media@vger.kernel.org, Colin Braun X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1783890545; l=28681; i=colin.braun.cl@gmail.com; s=20260630; h=from:subject:message-id; bh=KEj2d3o151qmDKc/H1dXxyZtdyvAb7r5gfIIA413u7Q=; b=lFqfuC3cuvUxCJdDCousl1xyHd9mDwZ69jE1e78gRM8yReHjvSLD/i2T89rW649cY8fiqQjwj bgaVh3SKyrpDH9sDoBtKu0kYGtm3MFCxcLGrfoLPrt2Pbj+QHX2hibK X-Developer-Key: i=colin.braun.cl@gmail.com; a=ed25519; pk=DJ5DDAbTXtW6OgSgetK6R1PUfhtCNSvSayX3Je17ZAY= Add abstractions for the USB Request Block (URB). Reiterating the goals and how they are achieved: 1. No `unsafe` needed in driver code. This means providing a way for a driver to safely access private data sent with the URB. - This is accomplished using a trampoline-like mechanism to reconstruct a valid Urb, allowing us to access a driver's passed completion callback and provide it the URB result. 2. Drivers are forced to handle the URB status in their completion callback before accessing the URB data. - This is accomplished by wrapping the completed URB in a struct such that the URB status must be checked before it transitions to a type where the data is accessible. 3. Dropping an URB ensures it is not in-flight and frees its resources. - This is accomplished by creating a custom `Drop` implementation that kills the URB and performs the necessary actions to free its resources. 4. The URB can be safely resubmitted from the completion callback. - This is trivial, just a resubmit method that consumes the URB to avoid multiple resubmits or accessing data after submission. The typestate pattern is used to flag an urb handle as either idle or submitted. Once a driver submits an urb with an idle handle, the handle moves to an active state that the driver must hold (otherwise its `Drop` will run, killing the URB and freeing its resources). Pipe and urb creation functions are created to support future bulk, interrupt, and control requests. Signed-off-by: Colin Braun --- include/linux/usb.h | 4 + rust/kernel/usb.rs | 709 ++++++++++++++++++++++++++++++++++++++++++++++++= +++- 2 files changed, 711 insertions(+), 2 deletions(-) diff --git a/include/linux/usb.h b/include/linux/usb.h index 1da4ad1610bc..c588d73c1592 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1513,6 +1513,9 @@ typedef void (*usb_complete_t)(struct urb *); * @complete: Completion handler. This URB is passed as the parameter to t= he * completion function. The completion function may then do what * it likes with the URB, including resubmitting or freeing it. + * @rust_complete: Completion handler for Rust drivers. This is necessary = to + * allow Rust drivers to create completion handlers using the Rust ABI, + * avoiding the need for `extern "C"`. * @iso_frame_desc: Used to provide arrays of ISO transfer buffers and to * collect the transfer status for each buffer. * @@ -1663,6 +1666,7 @@ struct urb { int error_count; /* (return) number of ISO errors */ void *context; /* (in) context for completion */ usb_complete_t complete; /* (in) completion routine */ + void *rust_complete; /* (in) rust completion routine */ struct usb_iso_packet_descriptor iso_frame_desc[]; /* (in) ISO ONLY */ }; diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 21dddc735bdf..f4a3d7c0ef6c 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -18,9 +18,15 @@ to_result, // }, prelude::*, - sync::aref::AlwaysRefCounted, + sync::{ + aref::AlwaysRefCounted, + Arc, + ArcBorrow, // + }, + time::Delta, types::Opaque, usb::ch9::{ + CtrlRequest, Direction, EndpointDescriptor, InterfaceClass, @@ -34,7 +40,11 @@ offset_of, MaybeUninit, // }, - ptr::NonNull, + ops::Deref, + ptr::{ + self, + NonNull, // + }, slice, // }; =20 @@ -589,6 +599,645 @@ unsafe impl Send for Interface {} // allow any mutation through a shared reference. unsafe impl Sync for Interface {} =20 +crate::impl_flags!( + /// URB transfer flags. + /// + /// These correspond to the `URB_*` constants in `include/linux/usb.h`. + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] + pub struct TransferFlags(u32); + + /// Represents a single URB transfer flag. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum TransferFlag { + /// Short packet flag: return an error if the packet is shorter th= an + /// expected. + ShortNotOk =3D bindings::URB_SHORT_NOT_OK, + /// Isochronous ASAP flag: schedule the isochronous transfer as so= on as + /// possible. + IsoAsap =3D bindings::URB_ISO_ASAP, + /// Do not perform a DMA mapping for the transfer buffer. + NoTransferDmaMap =3D bindings::URB_NO_TRANSFER_DMA_MAP, + /// Send a zero-length packet at the end of the transfer. + ZeroPacket =3D bindings::URB_ZERO_PACKET, + /// Do not interrupt the CPU when the URB completes. + NoInterrupt =3D bindings::URB_NO_INTERRUPT, + } +); + +/// A USB pipe encoding endpoint type, direction, device address, and +/// endpoint number into a single `u32`. +/// +/// Pipe encoding follows the kernel's `PIPE_*` macros used by the USB +/// core for control, bulk, isochronous, and interrupt transfers. +#[derive(Clone, Copy)] +pub struct Pipe(u32); + +impl Pipe { + /// Create a host-to-device (OUT) control pipe (endpoint 0). + pub fn new_send_control_pipe(dev: &Device) -> Self { + Self(bindings::PIPE_CONTROL << 30 | dev.devnum() << 8) + } + + /// Create a device-to-host (IN) control pipe (endpoint 0). + pub fn new_receive_control_pipe(dev: &Device) -> Self { + Self(bindings::PIPE_CONTROL << 30 | dev.devnum() << 8 | bindings::= USB_DIR_IN) + } + + /// Create a device-to-host (IN) isochronous pipe. + pub fn new_receive_isoc_pipe(dev: &Device, endpoint: &HostEndpoint) ->= Self { + Self( + bindings::PIPE_ISOCHRONOUS << 30 + | dev.devnum() << 8 + | u32::from(endpoint.endpoint_number()) << 15 + | bindings::USB_DIR_IN, + ) + } + + /// Create a host-to-device (OUT) isochronous pipe. + pub fn new_send_isoc_pipe(dev: &Device, endpoint: &HostEndpoint) -> Se= lf { + Self( + bindings::PIPE_ISOCHRONOUS << 30 + | dev.devnum() << 8 + | u32::from(endpoint.endpoint_number()) << 15, + ) + } + + /// Create a host-to-device (OUT) bulk pipe. + pub fn new_send_bulk_pipe(dev: &Device, endpoint: &HostEndpoint) -> Se= lf { + Self( + bindings::PIPE_BULK << 30 + | dev.devnum() << 8 + | u32::from(endpoint.endpoint_number()) << 15, + ) + } + + /// Create a device-to-host (IN) bulk pipe. + pub fn new_receive_bulk_pipe(dev: &Device, endpoint: &HostEndpoint) ->= Self { + Self( + bindings::PIPE_BULK << 30 + | dev.devnum() << 8 + | u32::from(endpoint.endpoint_number()) << 15 + | bindings::USB_DIR_IN, + ) + } + + /// Create a host-to-device (OUT) interrupt pipe. + pub fn new_send_int_pipe(dev: &Device, endpoint: &HostEndpoint) -> Sel= f { + Self( + bindings::PIPE_INTERRUPT << 30 + | dev.devnum() << 8 + | u32::from(endpoint.endpoint_number()) << 15, + ) + } + + /// Create a device-to-host (IN) interrupt pipe. + pub fn new_receive_int_pipe(dev: &Device, endpoint: &HostEndpoint) -> = Self { + Self( + bindings::PIPE_INTERRUPT << 30 + | dev.devnum() << 8 + | u32::from(endpoint.endpoint_number()) << 15 + | bindings::USB_DIR_IN, + ) + } +} + +/// A single isochronous packet descriptor within an URB. +/// +/// Wraps `struct usb_iso_packet_descriptor` from the C USB core. +#[repr(transparent)] +pub struct IsoPacketDescriptor(bindings::usb_iso_packet_descriptor); + +impl IsoPacketDescriptor { + /// Returns the offset of the packet's data within the transfer buffer. + pub fn offset(&self) -> u32 { + self.0.offset + } + + /// Returns the length of the packet in bytes. + pub fn length(&self) -> u32 { + self.0.length + } + + /// Returns the actual number of bytes transferred in this packet. + /// + /// Valid only after the URB completes. + pub fn actual_length(&self) -> u32 { + self.0.actual_length + } + + /// Returns the per-packet completion status. + /// + /// Valid only after the URB completes. + pub fn status(&self) -> i32 { + self.0.status + } +} + +/// Trait implemented by all URB state marker types. +/// +/// Each state specifies pre-cleanup behaviour that runs before the +/// underlying allocation is freed. +pub trait UrbState { + /// Called before the URB allocation is freed. + fn pre_drop(urb: &mut bindings::urb); +} + +/// Marker type for an idle (unsubmitted) URB. +pub struct Idle; +/// Marker type for an active (submitted, in-flight) URB. +pub struct Active; + +impl UrbState for Idle { + fn pre_drop(_urb: &mut bindings::urb) {} +} +impl UrbState for Active { + fn pre_drop(urb: &mut bindings::urb) { + // SAFETY: `urb` is a valid pointer to an initialized `struct urb`. + unsafe { bindings::usb_kill_urb(urb) } + } +} + +/// A USB Request Block (URB). +/// +/// This structure wraps the C [`struct urb`] and provides a safe +/// abstraction for USB transfers. +/// +/// [`struct urb`]: https://www.kernel.org/doc/html/latest/driver-api/usb/= usb.html#c.urb +#[repr(transparent)] +pub struct Urb(Opaque, PhantomData); + +impl Urb { + fn as_raw(&self) -> *mut bindings::urb { + self.0.get() + } + + fn inner(&self) -> &bindings::urb { + // SAFETY: The type invariants guarantee that `self.0` wraps a val= id + // `struct urb`. + unsafe { &*self.as_raw() } + } + + fn status(&self) -> i32 { + self.inner().status + } + + /// Returns a borrow of the driver-private context data, if any. + pub fn context(&self) -> Option> { + let context =3D self.inner().context; + if context.is_null() { + None + } else { + // SAFETY: `context` was initialized by `Arc::into_raw` in `in= it_common`. + Some(unsafe { ArcBorrow::from_raw(context.cast()) }) + } + } +} + +/// A handle to a [`struct urb`] allocated via `usb_alloc_urb`. +/// +/// Created by [`Urb::new_bulk`], [`Urb::new_isoc`], etc. The URB is +/// owned by this handle =E2=80=94 dropping the handle frees the allocatio= n. +/// +/// Use [`Urb::submit`] to transition to [`UrbHandle`]. +pub struct UrbHandle { + /// Pointer to the underlying C `struct urb`. + urb: NonNull, + /// State marker. + _state: PhantomData, + /// Type of driver-private context data. + _ty: PhantomData, +} + +// SAFETY: The underlying urb is always reference-counted and can be relea= sed from any thread. +unsafe impl Send for UrbHandle {} + +impl Deref for UrbHandle { + type Target =3D Urb; + + fn deref(&self) -> &Self::Target { + // SAFETY: `Urb` is a `#[repr(transparent)]` wrapper of `struct= urb`, + unsafe { &*(self.urb.as_ptr() as *const Urb) } + } +} + +impl Drop for UrbHandle { + fn drop(&mut self) { + // SAFETY: `self.as_raw()` points to a valid, initialized C `struc= t urb`. + let urb: &mut bindings::urb =3D unsafe { &mut *self.as_raw() }; + S::pre_drop(urb); + + if !urb.context.is_null() { + // SAFETY: After `pre_drop` the URB is idle, so it is safe to + // reclaim the context data. + unsafe { + drop(Arc::from_raw(urb.context.cast::())); + } + } + + if !urb.setup_packet.is_null() { + // SAFETY: The setup packet was allocated via `KBox::into_raw`= in + // `init_common` and `urb.setup_packet` is still valid. + unsafe { + drop(KBox::from_raw(urb.setup_packet.cast::()= )); + } + } + + if !urb.transfer_buffer.is_null() { + // SAFETY: The transfer buffer was allocated via `KBox::into_r= aw` in + // `init_common` and `urb.transfer_buffer` is still valid. + unsafe { + drop(KBox::from_raw(ptr::slice_from_raw_parts_mut( + urb.transfer_buffer.cast::(), + urb.transfer_buffer_length as usize, + ))); + } + } + + // SAFETY: `urb` points to a valid, initialized `struct urb` + // and is not in-flight. + unsafe { bindings::usb_free_urb(ptr::from_mut(urb)) }; + } +} + +/// A completed URB whose status must be checked before accessing data. +/// +/// The driver receives this in its completion handler. Call +/// [`check`](UrbResult::check) to verify the transfer succeeded. +pub struct UrbResult<'a, T> { + /// The pinned URB reference delivered by the trampoline. + urb: Pin<&'a mut Urb>, +} + +impl<'a, T> Deref for UrbResult<'a, T> { + type Target =3D Urb; + + fn deref(&self) -> &Self::Target { + &self.urb + } +} + +impl<'a, T> UrbResult<'a, T> { + /// Re-submit the URB from a completion handler. + /// + /// Consumes this handle, transferring ownership to the kernel. + /// This is intentionally private, since a driver should always + /// check the result in the completion handler. + fn resubmit(self, mem_flags: kernel::alloc::Flags) -> Result { + // SAFETY: `self.urb.as_raw()` points to a valid, initialized C `s= truct urb`. + to_result(unsafe { bindings::usb_submit_urb(self.as_raw(), mem_fla= gs.as_raw()) }) + } + + /// Check the completion status and grant access to the URB data. + pub fn check(&mut self) -> Result> { + if self.status() !=3D 0 { + Err(Error::from_errno(self.status())) + } else { + Ok(UrbData { + urb: self.urb.as_mut(), + }) + } + } + + /// Check the completion status, granting data access on success or + /// resubmitting the URB on failure. + pub fn check_or_resubmit( + self, + mem_flags: kernel::alloc::Flags, + ) -> Result, Result> { + if self.status() !=3D 0 { + Err(self.resubmit(mem_flags)) + } else { + Ok(UrbData { urb: self.urb }) + } + } +} + +/// A successfully completed URB whose data is safe to read. +pub struct UrbData<'a, T> { + /// The pinned URB reference. + urb: Pin<&'a mut Urb>, +} + +impl<'a, T> Deref for UrbData<'a, T> { + type Target =3D Urb; + + fn deref(&self) -> &Self::Target { + &self.urb + } +} + +impl<'a, T> UrbData<'a, T> { + /// Returns the number of bytes actually transferred. + /// + /// For isochronous URBs this is the sum of all packet + /// `actual_length` values. + pub fn actual_length(&self) -> u32 { + self.inner().actual_length + } + + /// Returns the transfer buffer as a byte slice. + pub fn transfer_buffer(&self) -> &[u8] { + let urb =3D self.inner(); + if urb.transfer_buffer.is_null() { + &[] + } else { + // SAFETY: The transfer buffer was set in `init_common`. + // The pointer and length are valid for the lifetime of the `U= rb`. + unsafe { + slice::from_raw_parts( + urb.transfer_buffer as *const u8, + urb.transfer_buffer_length as usize, + ) + } + } + } + + /// Returns the ISO frame descriptors for this URB. + pub fn iso_frame_descs(&self) -> &[IsoPacketDescriptor] { + let urb =3D self.inner(); + + if urb.number_of_packets =3D=3D 0 { + &[] + } else { + let data =3D urb.iso_frame_desc.as_ptr().cast::(); + + // SAFETY: The `iso_frame_desc` flexible array was allocated as + // part of the `usb_alloc_urb` allocation. `number_of_packets` + // is the corresponding length. + unsafe { slice::from_raw_parts(data, urb.number_of_packets as = usize) } + } + } + + /// Extracts the payload data for a given ISO packet descriptor. + /// + /// Returns `Err` if the packet status is non-zero. + pub fn data_from_iso_packet_desc( + &self, + iso_packet_desc: &IsoPacketDescriptor, + ) -> Result<&[u8]> { + if iso_packet_desc.status() !=3D 0 { + return Err(Error::from_errno(iso_packet_desc.status())); + } + let urb =3D self.inner(); + // SAFETY: `iso_packet_desc.offset()` was computed in + // `init_common` and lies within the transfer buffer. + let data =3D + unsafe { (urb.transfer_buffer.cast::()).add(iso_packet_des= c.offset() as usize) }; + + // SAFETY: After URB completion `actual_length()` reflects the + // valid bytes in the packet. The slice is within the transfer + // buffer allocation. The packet status was verified above. + unsafe { + Ok(slice::from_raw_parts( + data, + iso_packet_desc.actual_length() as usize, + )) + } + } + + /// Re-submit the URB from a completion handler. + /// + /// Consumes this handle, transferring ownership to the kernel. + pub fn resubmit(self, mem_flags: kernel::alloc::Flags) -> Result { + // SAFETY: `self.as_raw()` points to a valid, initialized C `struc= t urb`. + to_result(unsafe { bindings::usb_submit_urb(self.as_raw(), mem_fla= gs.as_raw()) }) + } +} + +/// Trampoline function to call safe completion handlers. +/// +/// # Safety +/// +/// `urb_ptr` must point to a valid, initialized `struct urb` whose +/// `context` and `rust_complete` fields were set by [`Urb::init_common`]. +unsafe extern "C" fn urb_complete_trampoline(urb_ptr: *mut bindings::ur= b) { + // SAFETY: `urb_ptr` is a valid pointer provided by the USB core. + // `rust_complete` was set to a `fn(UrbResult<'_, T>)` when initialize= d. + let complete: fn(UrbResult<'_, T>) =3D unsafe { core::mem::transmute((= *urb_ptr).rust_complete) }; + // SAFETY: `urb_ptr` points to a valid `struct urb`. + let urb =3D unsafe { &mut *urb_ptr.cast() }; + // SAFETY: The data `urb` references is never moved. + let urb =3D unsafe { Pin::new_unchecked(urb) }; + complete(UrbResult { urb }); +} + +impl Urb { + #[allow(clippy::too_many_arguments)] + fn init_common( + mem_flags: kernel::alloc::Flags, + intf: &Interface, + pipe: Pipe, + setup_packet: Option>, + transfer_buffer: Option>, + context_data: Option>, + complete: fn(UrbResult<'_, T>), + number_of_packets: u32, + iso_packet_len: u16, + transfer_flags: TransferFlags, + interval: i32, + ) -> Result>> { + // SAFETY: `usb_alloc_urb` allocates a `struct urb` + ISO frame. + let urb_ptr =3D + unsafe { bindings::usb_alloc_urb(number_of_packets as c_int, m= em_flags.as_raw()) }; + if urb_ptr.is_null() { + return Err(ENOMEM); + } + + // SAFETY: `urb_ptr` points to allocated and zero-initialized memo= ry + // of the correct layout for `struct urb` + ISO tail. + let urb =3D unsafe { &mut *urb_ptr }; + + let dev: &Device =3D intf.as_ref(); + + urb.complete =3D Some(urb_complete_trampoline::); + urb.dev =3D dev.as_raw(); + urb.pipe =3D pipe.0; + urb.number_of_packets =3D number_of_packets as c_int; + urb.transfer_flags =3D u32::from(transfer_flags); + urb.interval =3D interval; + + // Set up ISO frame descriptors. + if number_of_packets > 0 { + // SAFETY: `urb_ptr` was allocated with `number_of_packets` ISO + // descriptors via `usb_alloc_urb`. `as_mut_slice` yields a va= lid + // mutable slice of that length. + let descs =3D unsafe { urb.iso_frame_desc.as_mut_slice(number_= of_packets as usize) }; + for (i, desc) in descs.iter_mut().enumerate() { + let pkt_len =3D u32::from(iso_packet_len); + desc.offset =3D (i as u32) * pkt_len; + desc.length =3D pkt_len; + } + } + + if let Some(sp) =3D setup_packet { + urb.setup_packet =3D KBox::into_raw(sp).cast::(); + } + + if let Some(tb) =3D transfer_buffer { + let len =3D tb.len(); + urb.transfer_buffer_length =3D len as u32; + urb.transfer_buffer =3D KBox::into_raw(tb).cast::(); + } + + if let Some(data) =3D context_data { + urb.context =3D Arc::into_raw(data).cast_mut().cast(); + } + + urb.rust_complete =3D complete as *mut core::ffi::c_void; + + let urb_handle =3D UrbHandle { + // SAFETY: `urb_ptr` is guaranteed non-null by the null check = above. + urb: unsafe { NonNull::new_unchecked(urb_ptr) }, + _state: PhantomData, + _ty: PhantomData, + }; + + // SAFETY: `urb_handle.urb` is never moved. + Ok(unsafe { Pin::new_unchecked(urb_handle) }) + } + + /// Submit the URB for execution. + /// + /// On success the caller receives an [`UrbHandle`] which + /// holds the resources for the in-flight URB. Dropping it cancels the + /// URB and frees the allocation. + pub fn submit( + self: Pin>, + mem_flags: kernel::alloc::Flags, + ) -> Result> { + // SAFETY: The urb pointed to is not moved. + let handle =3D unsafe { Pin::into_inner_unchecked(self) }; + // SAFETY: `handle.as_raw()` points to a valid, initialized `struc= t urb`. + let result =3D unsafe { bindings::usb_submit_urb(handle.as_raw(), = mem_flags.as_raw()) }; + + if result =3D=3D 0 { + let urb =3D handle.urb; + core::mem::forget(handle); + Ok(UrbHandle { + urb, + _state: PhantomData, + _ty: PhantomData, + }) + } else { + Err(Error::from_errno(result)) + } + } + + /// Creates a new bulk URB. + pub fn new_bulk( + mem_flags: kernel::alloc::Flags, + intf: &Interface, + pipe: Pipe, + transfer_buffer: KBox<[u8]>, + context_data: Option>, + complete: fn(UrbResult<'_, T>), + transfer_flags: TransferFlags, + ) -> Result>> { + Self::init_common( + mem_flags, + intf, + pipe, + None, + Some(transfer_buffer), + context_data, + complete, + 0, + 0, + transfer_flags, + 0, + ) + } + + /// Creates a new interrupt URB. + #[allow(clippy::too_many_arguments)] + pub fn new_int( + mem_flags: kernel::alloc::Flags, + intf: &Interface, + pipe: Pipe, + transfer_buffer: KBox<[u8]>, + context_data: Option>, + complete: fn(UrbResult<'_, T>), + transfer_flags: TransferFlags, + interval: i32, + ) -> Result>> { + Self::init_common( + mem_flags, + intf, + pipe, + None, + Some(transfer_buffer), + context_data, + complete, + 0, + 0, + transfer_flags, + interval, + ) + } + + /// Creates a new control URB. + #[allow(clippy::too_many_arguments)] + pub fn new_ctrl( + mem_flags: kernel::alloc::Flags, + intf: &Interface, + pipe: Pipe, + setup_packet: KBox, + transfer_buffer: Option>, + context_data: Option>, + complete: fn(UrbResult<'_, T>), + transfer_flags: TransferFlags, + ) -> Result>> { + Self::init_common( + mem_flags, + intf, + pipe, + Some(setup_packet), + transfer_buffer, + context_data, + complete, + 0, + 0, + transfer_flags, + 0, + ) + } + + /// Creates a new isochronous URB. + #[allow(clippy::too_many_arguments)] + pub fn new_isoc( + mem_flags: kernel::alloc::Flags, + intf: &Interface, + pipe: Pipe, + transfer_buffer: KBox<[u8]>, + context_data: Option>, + complete: fn(UrbResult<'_, T>), + number_of_packets: u32, + iso_packet_len: u16, + transfer_flags: TransferFlags, + interval: i32, + ) -> Result>> { + // Reject URBs whose buffer is too small to hold all packets. + let needed =3D (number_of_packets as usize).saturating_mul(iso_pac= ket_len as usize); + if transfer_buffer.len() < needed { + return Err(EINVAL); + } + + Self::init_common( + mem_flags, + intf, + pipe, + None, + Some(transfer_buffer), + context_data, + complete, + number_of_packets, + iso_packet_len, + transfer_flags, + interval, + ) + } +} + /// A USB device. /// /// This structure represents the Rust abstraction for a C [`struct usb_de= vice`]. @@ -611,6 +1260,17 @@ impl Device { fn as_raw(&self) -> *mut bindings::usb_device { self.0.get() } + + fn inner(&self) -> &bindings::usb_device { + // SAFETY: The type invariants guarantee that `self.0` wraps a val= id + // `struct usb_device`. + unsafe { &*self.as_raw() } + } + + /// Returns the USB device number assigned by the bus. + fn devnum(&self) -> u32 { + self.inner().devnum as u32 + } } =20 impl Device { @@ -625,6 +1285,51 @@ pub fn set_interface(&self, interface: u8, altsetting= : u8) -> Result { bindings::usb_set_interface(self.as_raw(), i32::from(interface= ), i32::from(altsetting)) }) } + + /// Send a USB control message synchronously. + /// + /// Wraps `usb_control_msg`. The pipe direction is inferred from the s= etup + /// packet's [`Direction`]. The optional `data` buffer is + /// written to for IN transfers or read from for OUT transfers. + /// + /// Returns the number of bytes transferred on success. + pub fn control_msg( + &self, + setup: &CtrlRequest, + data: Option<&mut [u8]>, + timeout: Delta, + ) -> Result { + let pipe =3D match setup.direction() { + Direction::In =3D> Pipe::new_receive_control_pipe(self), + Direction::Out =3D> Pipe::new_send_control_pipe(self), + }; + let (buf, len) =3D match data { + Some(d) =3D> (d.as_mut_ptr().cast::(), d.le= n() as u16), + None =3D> (ptr::null_mut(), 0), + }; + let timeout_ms =3D timeout.as_millis() as i32; + + // SAFETY: `self.as_raw()` returns a valid `struct usb_device` poi= nter. + let ret =3D unsafe { + bindings::usb_control_msg( + self.as_raw(), + pipe.0, + setup.request(), + setup.requesttype(), + setup.value(), + setup.index(), + buf, + len, + timeout_ms, + ) + }; + + if ret >=3D 0 { + Ok(ret) + } else { + Err(Error::from_errno(ret)) + } + } } =20 // SAFETY: `Device` is a transparent wrapper of a type that doesn't depend= on `Device`'s generic --=20 2.54.0 From nobody Sat Jul 25 22:32:24 2026 Received: from mail-yx2-f4.google.com (mail-yx2-f4.google.com [74.125.224.132]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B582037A82B for ; Sun, 12 Jul 2026 21:09:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.132 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783890557; cv=none; b=YMtYt8wW7rvp+KmpNrTyq15DsYPKtTfpOR0MUtAT2m2GHG8Xoe1cNSrUFu2BCqxfuTymBaDVHRzrSFrkDQxPAi32lW3msW6g2GD+xUEjiXx1ilUzPBPHtfPRgo/EyfGCUjkOqjFudk1OauMSy+btjmPwxGmrFP7LJ/cBeHpKb7M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783890557; c=relaxed/simple; bh=WQvKshDrAosb6Qtw7MpQmrVnyV9nFzUo7+XObHv3hgM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=KaOu8UV/3kFCmMe9BgznwZFQeRrSU7HBGGHiLROhxV+PeGJJkT6lBzTLvAt7hkoB3O1ASttoFhVTGXWHOz09yCQ6QsFNmjphsuJeBKeF3aRfJznkWRfSP7RF7sE38pCMVpWE+UkHZ0p4FYrnNYOqqQAqKiKOBDwU09vqEn1qfRk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=DB1EjdAl; arc=none smtp.client-ip=74.125.224.132 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="DB1EjdAl" Received: by mail-yx2-f4.google.com with SMTP id 00721157ae682-81e8f17ad9eso9331837b3.1 for ; Sun, 12 Jul 2026 14:09:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783890555; x=1784495355; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=1vodfbLbrZTh4k1ELxmOTY9e67AVLB+3FCuvHYZI16k=; b=DB1EjdAlERr7tsgkrOIT3Yd2ciRvMJcLNgj7Nf0htHZTL7i2oVRgb0vvOGcnQ+I3t3 2peqGbk4/4+hWBdF+mMlzWfzfsxhWLMa5NDU5JeKo8NGqbwpVvM3NrkzoXhVtL6MSD5J Qbqn9G2CTMXnigJj5We0Tn5aQTv3Mi9l8TJVwewFNGk9Ij4TozNGizC9K9ot5F3df9z6 FmmaC95IHbsJUwIFsjAwwav8/5F8FzXUw6IR4zohTLEgyBQGLTHkuxDykntnUIdWy84y hzDcEnG3BH9C9Xs8UGOlZuNBn13l9mXU8ZAmmBgvnKRmxIjvp1pdhJxrOc+yhUVmkmL5 2UGA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783890555; x=1784495355; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=1vodfbLbrZTh4k1ELxmOTY9e67AVLB+3FCuvHYZI16k=; b=CDk+HiaZM876B3dfNbwwGMdZpRDPi4Q6xQ7e5eqhP3hjXOzC+Myv67UrJEyjOXyaAX VZqZHsc/uYXRBySfJ6ZZaOSqRPRy73xsFXCuljSHidvVdm1PMEuN0jI0vA+ZwgWpKnZe SxLz2RtzeLiFlaw4zrhV2o+pxAEw/U6D1tLYQG6hqYp406aomRqWQCDjIUiZj83eUbvs Qs6jPPyHhhzQfN2w5uSeJdtDNnHo4YPbF4qMP3J/t9hnLXaTfBMOy9VSOBwgCpE/BnUs 1+CMZH4mjJFgig5w0sjeQ2YDgQu+FWCZEf6rUZAQ4gbH58wERv+k0rX53z8ztyoPicxI jmDw== X-Gm-Message-State: AOJu0YxFh5f6xW0uU6wn6R1CEcCIMaVVLf/kFZTSGLUuV3JCToQqV1E/ hVOT7a02Vw/us57LGkk9n1eW8nr8oCcmW/G+8G0DWvF1xzseWilDwQhj X-Gm-Gg: AfdE7ckZrEzdqXOQMwXvFXa2et4FGX/W0S1Y1EyGGJ2JQkwivwLvNDy01+l13AacMPQ dRUeTfOe1RORTK4qNrDDkFCH9Lp8RGqgcr1xMMPolAyenfqdMa1sBV8CTO5SVZN+ZlyUsInB15p XHqU9uW9/+gQJTxPcv/3cuko8ImKoqpwsQ8MWP3CMdyj5UC7RcGN/vePyUk10VQnc2O83T0VMeS BvfoGaOI7Nk0TIYNfXiUH2TjCJVntvFis4sbDmzcZn6I1fP9TnwsUhMLwvJuQMTNTKi/CXXg5Bs A9rfX+wOLrWoPYSyDb4uIWwPp3sKtO4FqXvOmkYRLOQyeyVjZN9bg+SsBMj1WKT+YWZUIHeXBDn 8EO2Q/MZPlq7q8F7MCHvpUifkHKXzRGKoQWKJczKsoCb7/g2pUgoVzIU2ldTcXCb/UI3CxtxL0Y DDbaA/XOhKz6EDKcfFCPnIgwtu2Mu6M0HzLsIh+Nd2un+M/M/YzViXzbPgtq+2PRpZjAs+4RDzi nVDx65c X-Received: by 2002:a05:690c:6008:b0:813:1001:cf85 with SMTP id 00721157ae682-81e9013e309mr54632687b3.37.1783890554640; Sun, 12 Jul 2026 14:09:14 -0700 (PDT) Received: from [127.0.0.2] (76-234-45-214.lightspeed.cicril.sbcglobal.net. [76.234.45.214]) by smtp.gmail.com with ESMTPSA id 00721157ae682-81e6be9834dsm99737387b3.9.2026.07.12.14.09.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 12 Jul 2026 14:09:14 -0700 (PDT) From: Colin Braun X-Google-Original-From: Colin Braun Date: Sun, 12 Jul 2026 16:08:01 -0500 Subject: [RFC PATCH 4/4] media: add gv-usb2 audio capture driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260712-urb-abstraction-v1-v1-4-9fa011634ead@gmail.com> References: <20260712-urb-abstraction-v1-v1-0-9fa011634ead@gmail.com> In-Reply-To: <20260712-urb-abstraction-v1-v1-0-9fa011634ead@gmail.com> To: Miguel Ojeda , Greg Kroah-Hartman , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , Mauro Carvalho Chehab , Alan Stern , Mathias Nyman Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-usb@vger.kernel.org, linux-media@vger.kernel.org, Colin Braun X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1783890545; l=16409; i=colin.braun.cl@gmail.com; s=20260630; h=from:subject:message-id; bh=WQvKshDrAosb6Qtw7MpQmrVnyV9nFzUo7+XObHv3hgM=; b=eAFC0DaDVkJFQFNbLa8qWNzd9PbtHYHv/Rf+X1jYBm8Fki7XhfYlHf1HyJU2IX//IDgvKazmW RlAbpZ3m6J9DD6TlV1eRsTsMk1/CiCcdzWH6384HOtl5AcRVpKSOuo5 X-Developer-Key: i=colin.braun.cl@gmail.com; a=ed25519; pk=DJ5DDAbTXtW6OgSgetK6R1PUfhtCNSvSayX3Je17ZAY= Add a Rust driver for the I-O Data GV-USB2 Composite-USB Video Capture Device. Leverages the USB abstractions from patches 1-3. The GV-USB2 contains an STK1150, a USB bridge microchip for converting old analog video signals into digital data. The STK1160's datasheet is available, but the same is not true for the STK1150. However, it seems that most of the registers are the same as the STK1160's. This implementation demonstrates the ability to send and receive data via control and isochronous USB transfers. Due to a lack of V4L2 and ALSA abstractions, this initial implementation only exposes data from an audio endpoint via debugfs. I would like to expand on this driver later. Signed-off-by: Colin Braun --- drivers/media/usb/Kconfig | 1 + drivers/media/usb/Makefile | 1 + drivers/media/usb/gv-usb2/Kconfig | 9 + drivers/media/usb/gv-usb2/Makefile | 1 + drivers/media/usb/gv-usb2/driver.rs | 361 +++++++++++++++++++++++++++++++= ++++ drivers/media/usb/gv-usb2/gv_usb2.rs | 16 ++ drivers/media/usb/gv-usb2/regs.rs | 25 +++ 7 files changed, 414 insertions(+) diff --git a/drivers/media/usb/Kconfig b/drivers/media/usb/Kconfig index 813171d25ac5..9867d0dc5626 100644 --- a/drivers/media/usb/Kconfig +++ b/drivers/media/usb/Kconfig @@ -14,6 +14,7 @@ if MEDIA_CAMERA_SUPPORT comment "Webcam devices" =20 source "drivers/media/usb/gspca/Kconfig" +source "drivers/media/usb/gv-usb2/Kconfig" source "drivers/media/usb/pwc/Kconfig" source "drivers/media/usb/s2255/Kconfig" source "drivers/media/usb/usbtv/Kconfig" diff --git a/drivers/media/usb/Makefile b/drivers/media/usb/Makefile index 6d171beea20d..f69aaec671ca 100644 --- a/drivers/media/usb/Makefile +++ b/drivers/media/usb/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_AU0828) +=3D au0828/ obj-$(CONFIG_VIDEO_CX231XX) +=3D cx231xx/ obj-$(CONFIG_VIDEO_EM28XX) +=3D em28xx/ obj-$(CONFIG_VIDEO_GO7007) +=3D go7007/ +obj-$(CONFIG_VIDEO_GVUSB2) +=3D gv-usb2/ obj-$(CONFIG_VIDEO_HDPVR) +=3D hdpvr/ obj-$(CONFIG_VIDEO_PVRUSB2) +=3D pvrusb2/ obj-$(CONFIG_VIDEO_STK1160) +=3D stk1160/ diff --git a/drivers/media/usb/gv-usb2/Kconfig b/drivers/media/usb/gv-usb2/= Kconfig new file mode 100644 index 000000000000..755a669bba26 --- /dev/null +++ b/drivers/media/usb/gv-usb2/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_GVUSB2 + tristate "GV-USB2 audio/video capture device driver" + depends on VIDEO_DEV && RUST + help + Rust driver for the I-O Data GV-USB2 Composite-USB Video Capture + Device. + + To compile this driver as a module, choose M here: the module + will be called gv_usb2. diff --git a/drivers/media/usb/gv-usb2/Makefile b/drivers/media/usb/gv-usb2= /Makefile new file mode 100644 index 000000000000..7cc0f83a11c1 --- /dev/null +++ b/drivers/media/usb/gv-usb2/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_VIDEO_GVUSB2) +=3D gv_usb2.o diff --git a/drivers/media/usb/gv-usb2/driver.rs b/drivers/media/usb/gv-usb= 2/driver.rs new file mode 100644 index 000000000000..a8ab03e9a133 --- /dev/null +++ b/drivers/media/usb/gv-usb2/driver.rs @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0 + +use crate::regs; +use kernel::{ + debugfs::{ + self, + BinaryWriter, // + }, + device, + prelude::*, + sync::{ + new_spinlock, + Arc, + SpinLock, // + }, + time::Delta, + usb, +}; + +/// The number of isochronous urbs to submit. +const NUM_URBS: usize =3D 4; + +/// The expected max packet size for audio. +const MAX_AUDIO_PACKET_SIZE: u16 =3D 256; + +/// The GV-USB2 USB driver struct. +/// +/// Created during probe and stored as the driver's private data. +pub(crate) struct GvUsb2Driver { + /// Shared audio state. + _audio: Arc, + /// Pool of isochronous URBs submitted for audio capture. + _urbs: [Option>; NUM_URBS], + /// The debugfs file exposing captured audio to userspace. + _capture_file: Pin>>, + /// The debugfs directory node. + _debugfs_dir: debugfs::Dir, +} + +/// Shared state for audio capture. +#[pin_data] +struct AudioState { + /// Ring buffer for streaming audio data from URBs to userspace. + #[pin] + ring: SpinLock, +} + +/// A ring buffer for audio data. +/// +/// Used to transfer audio data from the URB completions to userspace +/// via debugfs. +struct AudioRingBuffer { + /// Audio buffer, sized to a power of two. + buf: KBox<[u8]>, + /// Producer index. + head: u32, + /// Consumer index. + tail: u32, + /// Mask to handle ring buffer wrap-around. Must be capacity - 1. + mask: u32, +} + +impl AudioRingBuffer { + /// Write data into the ring buffer. + /// + /// Returns the number of bytes written. + fn write(&mut self, data: &[u8]) -> usize { + let capacity =3D self.mask as usize + 1; + let fill =3D self.head.wrapping_sub(self.tail) as usize; + + if fill >=3D capacity { + return 0; + } + + let to_write =3D core::cmp::min(capacity - fill, data.len()); + if to_write =3D=3D 0 { + return 0; + } + + let mask =3D self.mask as usize; + let head_idx =3D self.head as usize & mask; + let space_to_end =3D capacity - head_idx; + + if to_write <=3D space_to_end { + self.buf[head_idx..head_idx + to_write].copy_from_slice(&data[= ..to_write]); + } else { + let (first, rest) =3D data.split_at(space_to_end); + self.buf[head_idx..capacity].copy_from_slice(first); + self.buf[..to_write - space_to_end].copy_from_slice(rest); + } + + self.head =3D self.head.wrapping_add(to_write as u32); + + to_write + } + + /// Read data from the ring buffer. + /// + /// Returns the number of bytes copied into `buf`. + fn read(&mut self, buf: &mut [u8]) -> usize { + let capacity =3D self.mask as usize + 1; + let fill =3D self.head.wrapping_sub(self.tail) as usize; + + if fill =3D=3D 0 { + return 0; + } + + let to_read =3D core::cmp::min(fill, buf.len()); + let mask =3D self.mask as usize; + let tail_idx =3D self.tail as usize & mask; + let space_to_end =3D capacity - tail_idx; + + if to_read <=3D space_to_end { + buf[..to_read].copy_from_slice(&self.buf[tail_idx..tail_idx + = to_read]); + } else { + let first_len =3D space_to_end; + let second_len =3D to_read - space_to_end; + buf[..first_len].copy_from_slice(&self.buf[tail_idx..capacity]= ); + buf[first_len..to_read].copy_from_slice(&self.buf[..second_len= ]); + } + + self.tail =3D self.tail.wrapping_add(to_read as u32); + + to_read + } +} + +/// Debugfs reader that drains the audio ring buffer. +struct IsocCapture { + /// Shared audio state whose ring buffer is read on each `read()` call. + state: Arc, +} + +impl BinaryWriter for IsocCapture { + fn write_to_slice( + &self, + writer: &mut kernel::uaccess::UserSliceWriter, + offset: &mut kernel::fs::file::Offset, + ) -> Result { + let mut buf =3D [0u8; 4096]; + let n =3D match self.state.ring.try_lock() { + Some(mut guard) =3D> guard.read(&mut buf), + None =3D> 0, + }; + if n =3D=3D 0 { + return Ok(0); + } + writer.write_slice_file(&buf[..n], offset) + } +} + +kernel::usb_device_table!( + USB_TABLE, + MODULE_USB_TABLE, + ::IdInfo, + [(usb::DeviceId::from_id(0x04BB, 0x0532), ()),] +); + +impl usb::Driver for GvUsb2Driver { + type IdInfo =3D (); + type Data<'bound> =3D Self; + const ID_TABLE: usb::IdTable =3D &USB_TABLE; + + /// Probe the GV-USB2 device. + /// + /// Matches only audio interface 2 (isochronous endpoint 4). + /// Switches to altsetting 1, initializes the ADC, submits four isochr= onous + /// URBs, and exposes the audio stream via debugfs. + fn probe<'bound>( + intf: &'bound usb::Interface>, + _id: &usb::DeviceId, + _info: &'bound Self::IdInfo, + ) -> impl PinInit + 'bound { + let dev: &device::Device> =3D intf.as_ref(); + + dev_dbg!( + dev, + "INTERFACE {}: num altsettings =3D {}\n", + intf.cur_altsetting().number(), + intf.altsettings().len(), + ); + for endpoint in intf.cur_altsetting().endpoints() { + dev_dbg!( + dev, + " endpoint dir: {:?}, endpoint type: {:?}\n", + endpoint.endpoint_dir(), + endpoint.endpoint_type() + ); + } + + if intf.cur_altsetting().class() =3D=3D usb::ch9::InterfaceClass::= AUDIO { + // Audio class is supported by snd-usb-audio, reject the inter= face. + return Err(ENODEV); + } + + // Interface 2 is the audio one for gv-usb2, skip the others + if intf.cur_altsetting().number() !=3D 2 { + return Err(ENODEV); + } + + // Switch to altsetting 1 (full-bandwidth). + intf.set_interface(1)?; + + let audio_ep =3D find_audio_endpoint(intf)?; + dev_dbg!( + dev, + "Found audio endpoint: {}\n", + audio_ep.endpoint_number() + ); + + let buf =3D KBox::new([0; 32_768], GFP_KERNEL)?; + let audio: Arc =3D Arc::pin_init( + pin_init!(AudioState { + ring <- new_spinlock!(AudioRingBuffer { + buf, + head: 0, + tail: 0, + mask: 32_767, + }), + }), + GFP_KERNEL, + )?; + + // Initialize the audio ADC: GPIO, AC97 disable, I2S enable. + reset_adc(intf)?; + + let urbs =3D submit_audio_urbs(intf, audio_ep, &audio)?; + + let dir =3D debugfs::Dir::new(c"gv-usb2"); + let file =3D KBox::pin_init( + dir.read_binary_file( + c"audio_raw", + IsocCapture { + state: audio.clone(), + }, + ), + GFP_KERNEL, + )?; + + let driver =3D Self { + _audio: audio, + _urbs: urbs, + _capture_file: file, + _debugfs_dir: dir, + }; + + dev_dbg!(dev, "GV-USB2 successfully initialized\n"); + Ok(driver) + } + + /// Disconnect the GV-USB2 device. + /// + /// All resources are cleaned up when dropped. + fn disconnect<'bound>(intf: &'bound usb::Interface>, = _data: Pin<&Self>) { + let dev: &device::Device> =3D intf.as_ref(); + dev_dbg!(dev, "GV-USB2 disconnected\n"); + } +} + +/// Write a vendor-specific control register on the GV-USB2 device. +/// +/// Uses a vendor-type control request (`REQ_WRITE_REG`) to write the +/// given `value` to the given `reg` address. +fn write_reg(intf: &usb::Interface, reg: u16, value: u8) ->= Result { + let req =3D usb::ch9::CtrlRequest::new( + usb::ch9::RequestType::new( + usb::ch9::Direction::Out, + usb::ch9::Type::Vendor, + usb::ch9::Recipient::Device, + ), + regs::REQ_WRITE_REG, + u16::from(value), + reg, + 0, + ); + let dev: &usb::Device =3D intf.as_ref(); + dev.control_msg(&req, None, Delta::from_millis(1_000)) + .map(|_| ()) +} + +/// Initialize the audio ADC on the GV-USB2. +/// +/// Configures GPIO direction and output values, disables the AC97 codec, +/// and enables the I2S interface. +fn reset_adc(intf: &usb::Interface) -> Result { + // GPIO: bits 4+5 as outputs; SOUND_ENABLE=3D0 (active), SDA_RESET=3D1= (inactive) + write_reg(intf, regs::GPIO_CTRL + 2, 0x30)?; + write_reg(intf, regs::GPIO_CTRL, 0x10)?; + // Disable AC97, enable I2S + write_reg(intf, regs::AC97_CTRL, 0x00)?; + write_reg(intf, regs::I2S_CTRL, 0x01) +} + +/// Completion callback for isochronous audio URBs. +/// +/// Copies received audio data into the ring buffer and resubmits the URB. +fn audio_complete(result: usb::UrbResult<'_, AudioState>) { + let Ok(urb_data) =3D result.check_or_resubmit(GFP_ATOMIC) else { + return; + }; + + if let Some(state) =3D urb_data.context() { + let mut ring_guard =3D state.ring.lock(); + for desc in urb_data.iso_frame_descs() { + if let Ok(data) =3D urb_data.data_from_iso_packet_desc(desc) { + ring_guard.write(data); + } + } + } + + let _ =3D urb_data.resubmit(GFP_ATOMIC); +} + +/// Create and submit isochronous URBs for audio capture. +/// +/// URBs are resubmitted continuously by `audio_complete`. +fn submit_audio_urbs( + intf: &usb::Interface, + ep: &usb::HostEndpoint, + audio: &Arc, +) -> Result<[Option>; NUM_URBS]> { + const PACKETS_PER_URB: u32 =3D 8; + const URB_BUF_SIZE: usize =3D (PACKETS_PER_URB as usize) * (MAX_AUDIO_= PACKET_SIZE as usize); + + let dev: &usb::Device =3D intf.as_ref(); + let pipe =3D usb::Pipe::new_receive_isoc_pipe(dev, ep); + let mut urbs =3D [const { None }; NUM_URBS]; + for active_urb in urbs.iter_mut().take(NUM_URBS) { + let buf =3D KBox::new([0u8; URB_BUF_SIZE], GFP_KERNEL)?; + let urb_handle =3D usb::Urb::::new_isoc( + GFP_KERNEL, + intf, + pipe, + buf, + Some(audio.clone()), + audio_complete, + PACKETS_PER_URB, + MAX_AUDIO_PACKET_SIZE, + usb::TransferFlag::IsoAsap.into(), + i32::from(ep.interval()), + )?; + active_urb.replace(urb_handle.submit(GFP_KERNEL)?); + } + Ok(urbs) +} + +/// Attempt to find the isochronous audio IN endpoint on the interface. +fn find_audio_endpoint(intf: &usb::Interface) -> Result<&usb::HostEndpoint= > { + for altsetting in intf.altsettings() { + for ep in altsetting.endpoints() { + if ep.endpoint_dir() =3D=3D usb::ch9::Direction::In + && ep.endpoint_type() =3D=3D usb::EndpointType::Isoc + && ep.endpoint_number() =3D=3D 4 + && ep.maxp() =3D=3D MAX_AUDIO_PACKET_SIZE + { + return Ok(ep); + } + } + } + Err(ENODEV) +} diff --git a/drivers/media/usb/gv-usb2/gv_usb2.rs b/drivers/media/usb/gv-us= b2/gv_usb2.rs new file mode 100644 index 000000000000..e42c61c54773 --- /dev/null +++ b/drivers/media/usb/gv-usb2/gv_usb2.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust GV-USB2 driver. + +use crate::driver::GvUsb2Driver; + +kernel::module_usb_driver! { + type: GvUsb2Driver, + name: "gv_usb2", + authors: ["Colin Braun"], + description: "GV-USB2 Composite-USB Video Capture Device", + license: "GPL v2", +} + +mod driver; +mod regs; diff --git a/drivers/media/usb/gv-usb2/regs.rs b/drivers/media/usb/gv-usb2/= regs.rs new file mode 100644 index 000000000000..cc96086d5d40 --- /dev/null +++ b/drivers/media/usb/gv-usb2/regs.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 + +// USB Requests + +/// USB request value to read to a register on the STK1150. +#[allow(dead_code)] +pub(crate) const REQ_READ_REG: u8 =3D 0x00; + +/// USB request value to write to a register on the STK1150. +pub(crate) const REQ_WRITE_REG: u8 =3D 0x01; + +// Registers (accessed via REQ_READ_REG and REQ_WRITE_REG) + +/// GPIO Control Register. +/// +/// b31: EEPROM Disable +/// b25-16: DIR +/// b9-0: VALUE +pub(crate) const GPIO_CTRL: u16 =3D 0x0000; + +/// Audio Control Register 0. +pub(crate) const AC97_CTRL: u16 =3D 0x0500; + +/// I2S Control Register. +pub(crate) const I2S_CTRL: u16 =3D 0x050C; --=20 2.54.0