aboutsummaryrefslogtreecommitdiff
path: root/embassy-usb/src
diff options
context:
space:
mode:
authorChris Maniewski <[email protected]>2024-04-22 01:37:24 +0200
committerChris Maniewski <[email protected]>2024-04-27 23:14:16 +0200
commit095af927910b06f7af291f76c5236e0da8322402 (patch)
tree5911faf9cc2b4469a2ddd805acb81e2e832c2f69 /embassy-usb/src
parentda86c086510490602ffdd688760fb59cc7a1e524 (diff)
feature: WebUSB capability implementation
This adds the WebUSB implementation as per https://wicg.github.io/webusb/, using one in-endpoint and one out-endpoint as well as an example for the RP2040 to illustrate this capability.
Diffstat (limited to 'embassy-usb/src')
-rw-r--r--embassy-usb/src/builder.rs5
-rw-r--r--embassy-usb/src/class/mod.rs1
-rw-r--r--embassy-usb/src/class/web_usb.rs186
3 files changed, 192 insertions, 0 deletions
diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs
index 387b780de..7168e077c 100644
--- a/embassy-usb/src/builder.rs
+++ b/embassy-usb/src/builder.rs
@@ -417,6 +417,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
417 self.builder.config_descriptor.write(descriptor_type, descriptor); 417 self.builder.config_descriptor.write(descriptor_type, descriptor);
418 } 418 }
419 419
420 /// Add a custom Binary Object Store (BOS) descriptor to this alternate setting.
421 pub fn bos_capability(&mut self, capability_type: u8, capability: &[u8]) {
422 self.builder.bos_descriptor.capability(capability_type, capability);
423 }
424
420 fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { 425 fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
421 let ep = self 426 let ep = self
422 .builder 427 .builder
diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs
index 452eedf17..b883ed4e5 100644
--- a/embassy-usb/src/class/mod.rs
+++ b/embassy-usb/src/class/mod.rs
@@ -3,3 +3,4 @@ pub mod cdc_acm;
3pub mod cdc_ncm; 3pub mod cdc_ncm;
4pub mod hid; 4pub mod hid;
5pub mod midi; 5pub mod midi;
6pub mod web_usb;
diff --git a/embassy-usb/src/class/web_usb.rs b/embassy-usb/src/class/web_usb.rs
new file mode 100644
index 000000000..10ebf318d
--- /dev/null
+++ b/embassy-usb/src/class/web_usb.rs
@@ -0,0 +1,186 @@
1//! WebUSB API capability implementation.
2//!
3//! See https://wicg.github.io/webusb
4
5use core::mem::MaybeUninit;
6
7use crate::control::{InResponse, Recipient, Request, RequestType};
8use crate::descriptor::capability_type;
9use crate::driver::Driver;
10use crate::{Builder, Handler};
11
12const USB_CLASS_VENDOR: u8 = 0xff;
13const USB_SUBCLASS_NONE: u8 = 0x00;
14const USB_PROTOCOL_NONE: u8 = 0x00;
15
16const WEB_USB_REQUEST_GET_URL: u16 = 0x02;
17const WEB_USB_DESCRIPTOR_TYPE_URL: u8 = 0x03;
18
19/// URL descriptor for WebUSB landing page.
20///
21/// An ecoded URL descriptor to point to a website that is suggested to the user when the device is connected.
22pub struct Url<'d>(&'d str, u8);
23
24impl<'d> Url<'d> {
25 /// Create a new WebUSB URL descriptor.
26 pub fn new(url: &'d str) -> Self {
27 let (prefix, stripped_url) = if let Some(stripped) = url.strip_prefix("https://") {
28 (1, stripped)
29 } else if let Some(stripped) = url.strip_prefix("http://") {
30 (0, stripped)
31 } else {
32 (255, url)
33 };
34 assert!(
35 stripped_url.len() <= 252,
36 "URL too long. ({} bytes). Maximum length is 252 bytes.",
37 stripped_url.len()
38 );
39 Self(stripped_url, prefix)
40 }
41
42 fn as_bytes(&self) -> &[u8] {
43 self.0.as_bytes()
44 }
45
46 fn scheme(&self) -> u8 {
47 self.1
48 }
49}
50
51/// Configuration for WebUSB.
52pub struct Config<'d> {
53 /// Maximum packet size in bytes for the data endpoints.
54 ///
55 /// Valid values depend on the speed at which the bus is enumerated.
56 /// - low speed: 8
57 /// - full speed: 8, 16, 32, or 64
58 /// - high speed: 64
59 pub max_packet_size: u16,
60 /// URL to navigate to when the device is connected.
61 ///
62 /// If defined, shows a landing page which the device manufacturer would like the user to visit in order to control their device.
63 pub landing_url: Option<Url<'d>>,
64 /// Vendor code for the WebUSB request.
65 ///
66 /// This value defines the request id (bRequest) the device expects the host to use when issuing control transfers these requests. This can be an arbitrary u8 and is not to be confused with the USB Vendor ID.
67 pub vendor_code: u8,
68}
69
70struct Control<'d> {
71 ep_buf: [u8; 128],
72 vendor_code: u8,
73 landing_url: Option<&'d Url<'d>>,
74}
75
76impl<'d> Control<'d> {
77 fn new(config: &'d Config<'d>) -> Self {
78 Control {
79 ep_buf: [0u8; 128],
80 vendor_code: config.vendor_code,
81 landing_url: config.landing_url.as_ref(),
82 }
83 }
84}
85
86impl<'d> Handler for Control<'d> {
87 fn control_in(&mut self, req: Request, _data: &mut [u8]) -> Option<InResponse> {
88 let landing_value = if self.landing_url.is_some() { 1 } else { 0 };
89 if req.request_type == RequestType::Vendor
90 && req.recipient == Recipient::Device
91 && req.request == self.vendor_code
92 && req.value == landing_value
93 && req.index == WEB_USB_REQUEST_GET_URL
94 {
95 if let Some(url) = self.landing_url {
96 let url_bytes = url.as_bytes();
97 let len = url_bytes.len();
98
99 self.ep_buf[0] = len as u8 + 3;
100 self.ep_buf[1] = WEB_USB_DESCRIPTOR_TYPE_URL;
101 self.ep_buf[2] = url.scheme();
102 self.ep_buf[3..3 + len].copy_from_slice(url_bytes);
103
104 return Some(InResponse::Accepted(&self.ep_buf[..3 + len]));
105 }
106 }
107 None
108 }
109}
110
111/// Internal state for WebUSB
112pub struct State<'d> {
113 control: MaybeUninit<Control<'d>>,
114}
115
116impl<'d> Default for State<'d> {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl<'d> State<'d> {
123 /// Create a new `State`.
124 pub const fn new() -> Self {
125 State {
126 control: MaybeUninit::uninit(),
127 }
128 }
129}
130
131/// WebUSB capability implementation.
132///
133/// WebUSB is a W3C standard that allows a web page to communicate with USB devices.
134/// See See https://wicg.github.io/webusb for more information and the browser API.
135/// This implementation provides one read and one write endpoint.
136pub struct WebUsb<'d, D: Driver<'d>> {
137 _driver: core::marker::PhantomData<&'d D>,
138}
139
140impl<'d, D: Driver<'d>> WebUsb<'d, D> {
141 /// Builder for the WebUSB capability implementation.
142 ///
143 /// Pass in a USB `Builder`, a `State`, which holds the the control endpoint state, and a `Config` for the WebUSB configuration.
144 pub fn configure(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: &'d Config<'d>) {
145 let mut func = builder.function(USB_CLASS_VENDOR, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE);
146 let mut iface = func.interface();
147 let mut alt = iface.alt_setting(USB_CLASS_VENDOR, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None);
148
149 alt.bos_capability(
150 capability_type::PLATFORM,
151 &[
152 // PlatformCapabilityUUID (3408b638-09a9-47a0-8bfd-a0768815b665)
153 0x0,
154 0x38,
155 0xb6,
156 0x08,
157 0x34,
158 0xa9,
159 0x09,
160 0xa0,
161 0x47,
162 0x8b,
163 0xfd,
164 0xa0,
165 0x76,
166 0x88,
167 0x15,
168 0xb6,
169 0x65,
170 // bcdVersion of WebUSB (1.0)
171 0x00,
172 0x01,
173 // bVendorCode
174 config.vendor_code,
175 // iLandingPage
176 if config.landing_url.is_some() { 1 } else { 0 },
177 ],
178 );
179
180 let control = state.control.write(Control::new(config));
181
182 drop(func);
183
184 builder.handler(control);
185 }
186}