Merge branch 'master' into usb_msc
This commit is contained in:
@ -13,8 +13,31 @@ target = "thumbv7em-none-eabi"
|
||||
[features]
|
||||
defmt = ["dep:defmt", "embassy-usb-driver/defmt"]
|
||||
usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"]
|
||||
msos-descriptor = []
|
||||
default = ["usbd-hid"]
|
||||
|
||||
# BEGIN AUTOGENERATED CONFIG FEATURES
|
||||
# Generated by gen_config.py. DO NOT EDIT.
|
||||
max-interface-count-1 = []
|
||||
max-interface-count-2 = []
|
||||
max-interface-count-3 = []
|
||||
max-interface-count-4 = [] # Default
|
||||
max-interface-count-5 = []
|
||||
max-interface-count-6 = []
|
||||
max-interface-count-7 = []
|
||||
max-interface-count-8 = []
|
||||
|
||||
max-handler-count-1 = []
|
||||
max-handler-count-2 = []
|
||||
max-handler-count-3 = []
|
||||
max-handler-count-4 = [] # Default
|
||||
max-handler-count-5 = []
|
||||
max-handler-count-6 = []
|
||||
max-handler-count-7 = []
|
||||
max-handler-count-8 = []
|
||||
|
||||
# END AUTOGENERATED CONFIG FEATURES
|
||||
|
||||
[dependencies]
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" }
|
||||
|
44
embassy-usb/README.md
Normal file
44
embassy-usb/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
# embassy-usb
|
||||
|
||||
TODO crate description
|
||||
|
||||
## Configuration
|
||||
|
||||
`embassy-usb` has some configuration settings that are set at compile time, affecting sizes
|
||||
and counts of buffers.
|
||||
|
||||
They can be set in two ways:
|
||||
|
||||
- Via Cargo features: enable a feature like `<name>-<value>`. `name` must be in lowercase and
|
||||
use dashes instead of underscores. For example. `max-interface-count-3`. Only a selection of values
|
||||
is available, check `Cargo.toml` for the list.
|
||||
- Via environment variables at build time: set the variable named `EMBASSY_USB_<value>`. For example
|
||||
`EMBASSY_USB_MAX_INTERFACE_COUNT=3 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`.
|
||||
Any value can be set, unlike with Cargo features.
|
||||
|
||||
Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting
|
||||
with different values, compilation fails.
|
||||
|
||||
### `MAX_INTERFACE_COUNT`
|
||||
|
||||
Max amount of interfaces that can be created in one device. Default: 4.
|
||||
|
||||
|
||||
## Interoperability
|
||||
|
||||
This crate can run on any executor.
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
This crate requires nightly Rust, due to using "async fn in trait" support.
|
||||
|
||||
## License
|
||||
|
||||
This work is licensed under either of
|
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
<http://www.apache.org/licenses/LICENSE-2.0>)
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
|
||||
|
||||
at your option.
|
||||
|
94
embassy-usb/build.rs
Normal file
94
embassy-usb/build.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
static CONFIGS: &[(&str, usize)] = &[
|
||||
// BEGIN AUTOGENERATED CONFIG FEATURES
|
||||
// Generated by gen_config.py. DO NOT EDIT.
|
||||
("MAX_INTERFACE_COUNT", 4),
|
||||
("MAX_HANDLER_COUNT", 4),
|
||||
// END AUTOGENERATED CONFIG FEATURES
|
||||
];
|
||||
|
||||
struct ConfigState {
|
||||
value: usize,
|
||||
seen_feature: bool,
|
||||
seen_env: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let crate_name = env::var("CARGO_PKG_NAME")
|
||||
.unwrap()
|
||||
.to_ascii_uppercase()
|
||||
.replace('-', "_");
|
||||
|
||||
// only rebuild if build.rs changed. Otherwise Cargo will rebuild if any
|
||||
// other file changed.
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// Rebuild if config envvar changed.
|
||||
for (name, _) in CONFIGS {
|
||||
println!("cargo:rerun-if-env-changed={crate_name}_{name}");
|
||||
}
|
||||
|
||||
let mut configs = HashMap::new();
|
||||
for (name, default) in CONFIGS {
|
||||
configs.insert(
|
||||
*name,
|
||||
ConfigState {
|
||||
value: *default,
|
||||
seen_env: false,
|
||||
seen_feature: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let prefix = format!("{crate_name}_");
|
||||
for (var, value) in env::vars() {
|
||||
if let Some(name) = var.strip_prefix(&prefix) {
|
||||
let Some(cfg) = configs.get_mut(name) else {
|
||||
panic!("Unknown env var {name}")
|
||||
};
|
||||
|
||||
let Ok(value) = value.parse::<usize>() else {
|
||||
panic!("Invalid value for env var {name}: {value}")
|
||||
};
|
||||
|
||||
cfg.value = value;
|
||||
cfg.seen_env = true;
|
||||
}
|
||||
|
||||
if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") {
|
||||
if let Some(i) = feature.rfind('_') {
|
||||
let name = &feature[..i];
|
||||
let value = &feature[i + 1..];
|
||||
if let Some(cfg) = configs.get_mut(name) {
|
||||
let Ok(value) = value.parse::<usize>() else {
|
||||
panic!("Invalid value for feature {name}: {value}")
|
||||
};
|
||||
|
||||
// envvars take priority.
|
||||
if !cfg.seen_env {
|
||||
if cfg.seen_feature {
|
||||
panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value);
|
||||
}
|
||||
|
||||
cfg.value = value;
|
||||
cfg.seen_feature = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut data = String::new();
|
||||
|
||||
for (name, cfg) in &configs {
|
||||
writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap();
|
||||
}
|
||||
|
||||
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
let out_file = out_dir.join("config.rs").to_string_lossy().to_string();
|
||||
fs::write(out_file, data).unwrap();
|
||||
}
|
74
embassy-usb/gen_config.py
Normal file
74
embassy-usb/gen_config.py
Normal file
@ -0,0 +1,74 @@
|
||||
import os
|
||||
|
||||
abspath = os.path.abspath(__file__)
|
||||
dname = os.path.dirname(abspath)
|
||||
os.chdir(dname)
|
||||
|
||||
features = []
|
||||
|
||||
|
||||
def feature(name, default, min, max, pow2=None):
|
||||
vals = set()
|
||||
val = min
|
||||
while val <= max:
|
||||
vals.add(val)
|
||||
if pow2 == True or (isinstance(pow2, int) and val >= pow2):
|
||||
val *= 2
|
||||
else:
|
||||
val += 1
|
||||
vals.add(default)
|
||||
|
||||
features.append(
|
||||
{
|
||||
"name": name,
|
||||
"default": default,
|
||||
"vals": sorted(list(vals)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
feature("max_interface_count", default=4, min=1, max=8)
|
||||
feature("max_handler_count", default=4, min=1, max=8)
|
||||
|
||||
# ========= Update Cargo.toml
|
||||
|
||||
things = ""
|
||||
for f in features:
|
||||
name = f["name"].replace("_", "-")
|
||||
for val in f["vals"]:
|
||||
things += f"{name}-{val} = []"
|
||||
if val == f["default"]:
|
||||
things += " # Default"
|
||||
things += "\n"
|
||||
things += "\n"
|
||||
|
||||
SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n"
|
||||
SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n"
|
||||
HELP = "# Generated by gen_config.py. DO NOT EDIT.\n"
|
||||
with open("Cargo.toml", "r") as f:
|
||||
data = f.read()
|
||||
before, data = data.split(SEPARATOR_START, maxsplit=1)
|
||||
_, after = data.split(SEPARATOR_END, maxsplit=1)
|
||||
data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after
|
||||
with open("Cargo.toml", "w") as f:
|
||||
f.write(data)
|
||||
|
||||
|
||||
# ========= Update build.rs
|
||||
|
||||
things = ""
|
||||
for f in features:
|
||||
name = f["name"].upper()
|
||||
things += f' ("{name}", {f["default"]}),\n'
|
||||
|
||||
SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n"
|
||||
SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n"
|
||||
HELP = " // Generated by gen_config.py. DO NOT EDIT.\n"
|
||||
with open("build.rs", "r") as f:
|
||||
data = f.read()
|
||||
before, data = data.split(SEPARATOR_START, maxsplit=1)
|
||||
_, after = data.split(SEPARATOR_END, maxsplit=1)
|
||||
data = before + SEPARATOR_START + HELP + \
|
||||
things + " " + SEPARATOR_END + after
|
||||
with open("build.rs", "w") as f:
|
||||
f.write(data)
|
@ -1,10 +1,12 @@
|
||||
use heapless::Vec;
|
||||
|
||||
use crate::control::ControlHandler;
|
||||
use crate::config::*;
|
||||
use crate::descriptor::{BosWriter, DescriptorWriter};
|
||||
use crate::driver::{Driver, Endpoint, EndpointType};
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter};
|
||||
use crate::types::*;
|
||||
use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START};
|
||||
use crate::{Handler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
@ -120,8 +122,8 @@ impl<'a> Config<'a> {
|
||||
/// [`UsbDevice`] builder.
|
||||
pub struct Builder<'d, D: Driver<'d>> {
|
||||
config: Config<'d>,
|
||||
handler: Option<&'d dyn DeviceStateHandler>,
|
||||
interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>,
|
||||
handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>,
|
||||
interfaces: Vec<Interface, MAX_INTERFACE_COUNT>,
|
||||
control_buf: &'d mut [u8],
|
||||
|
||||
driver: D,
|
||||
@ -130,6 +132,9 @@ pub struct Builder<'d, D: Driver<'d>> {
|
||||
device_descriptor: DescriptorWriter<'d>,
|
||||
config_descriptor: DescriptorWriter<'d>,
|
||||
bos_descriptor: BosWriter<'d>,
|
||||
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
msos_descriptor: MsOsDescriptorWriter<'d>,
|
||||
}
|
||||
|
||||
impl<'d, D: Driver<'d>> Builder<'d, D> {
|
||||
@ -144,8 +149,8 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
|
||||
device_descriptor_buf: &'d mut [u8],
|
||||
config_descriptor_buf: &'d mut [u8],
|
||||
bos_descriptor_buf: &'d mut [u8],
|
||||
#[cfg(feature = "msos-descriptor")] msos_descriptor_buf: &'d mut [u8],
|
||||
control_buf: &'d mut [u8],
|
||||
handler: Option<&'d dyn DeviceStateHandler>,
|
||||
) -> Self {
|
||||
// Magic values specified in USB-IF ECN on IADs.
|
||||
if config.composite_with_iads
|
||||
@ -173,32 +178,40 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
|
||||
|
||||
Builder {
|
||||
driver,
|
||||
handler,
|
||||
config,
|
||||
interfaces: Vec::new(),
|
||||
handlers: Vec::new(),
|
||||
control_buf,
|
||||
next_string_index: STRING_INDEX_CUSTOM_START,
|
||||
|
||||
device_descriptor,
|
||||
config_descriptor,
|
||||
bos_descriptor,
|
||||
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
msos_descriptor: MsOsDescriptorWriter::new(msos_descriptor_buf),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the [`UsbDevice`] instance with the configuration in this builder.
|
||||
pub fn build(mut self) -> UsbDevice<'d, D> {
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
let msos_descriptor = self.msos_descriptor.build(&mut self.bos_descriptor);
|
||||
|
||||
self.config_descriptor.end_configuration();
|
||||
self.bos_descriptor.end_bos();
|
||||
|
||||
UsbDevice::build(
|
||||
self.driver,
|
||||
self.config,
|
||||
self.handler,
|
||||
self.handlers,
|
||||
self.device_descriptor.into_buf(),
|
||||
self.config_descriptor.into_buf(),
|
||||
self.bos_descriptor.writer.into_buf(),
|
||||
self.interfaces,
|
||||
self.control_buf,
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
msos_descriptor,
|
||||
)
|
||||
}
|
||||
|
||||
@ -215,14 +228,10 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
|
||||
///
|
||||
/// If it's not set, no IAD descriptor is added.
|
||||
pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> {
|
||||
let first_interface = InterfaceNumber::new(self.interfaces.len() as u8);
|
||||
let iface_count_index = if self.config.composite_with_iads {
|
||||
self.config_descriptor.iad(
|
||||
InterfaceNumber::new(self.interfaces.len() as _),
|
||||
0,
|
||||
class,
|
||||
subclass,
|
||||
protocol,
|
||||
);
|
||||
self.config_descriptor
|
||||
.iad(first_interface, 0, class, subclass, protocol);
|
||||
|
||||
Some(self.config_descriptor.position() - 5)
|
||||
} else {
|
||||
@ -232,8 +241,52 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
|
||||
FunctionBuilder {
|
||||
builder: self,
|
||||
iface_count_index,
|
||||
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
first_interface,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a Handler.
|
||||
///
|
||||
/// The Handler is called on some USB bus events, and to handle all control requests not already
|
||||
/// handled by the USB stack.
|
||||
pub fn handler(&mut self, handler: &'d mut dyn Handler) {
|
||||
if self.handlers.push(handler).is_err() {
|
||||
panic!(
|
||||
"embassy-usb: handler list full. Increase the `max_handler_count` compile-time setting. Current value: {}",
|
||||
MAX_HANDLER_COUNT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates a new string index.
|
||||
pub fn string(&mut self) -> StringIndex {
|
||||
let index = self.next_string_index;
|
||||
self.next_string_index += 1;
|
||||
StringIndex::new(index)
|
||||
}
|
||||
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
/// Add an MS OS 2.0 Descriptor Set.
|
||||
///
|
||||
/// Panics if called more than once.
|
||||
pub fn msos_descriptor(&mut self, windows_version: u32, vendor_code: u8) {
|
||||
self.msos_descriptor.header(windows_version, vendor_code);
|
||||
}
|
||||
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
/// Add an MS OS 2.0 Device Level Feature Descriptor.
|
||||
pub fn msos_feature<T: DeviceLevelDescriptor>(&mut self, desc: T) {
|
||||
self.msos_descriptor.device_feature(desc);
|
||||
}
|
||||
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
/// Gets the underlying [`MsOsDescriptorWriter`] to allow adding subsets and features for classes that
|
||||
/// do not add their own.
|
||||
pub fn msos_writer(&mut self) -> &mut MsOsDescriptorWriter<'d> {
|
||||
&mut self.msos_descriptor
|
||||
}
|
||||
}
|
||||
|
||||
/// Function builder.
|
||||
@ -244,6 +297,16 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
|
||||
pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> {
|
||||
builder: &'a mut Builder<'d, D>,
|
||||
iface_count_index: Option<usize>,
|
||||
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
first_interface: InterfaceNumber,
|
||||
}
|
||||
|
||||
impl<'a, 'd, D: Driver<'d>> Drop for FunctionBuilder<'a, 'd, D> {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
self.builder.msos_descriptor.end_function();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> {
|
||||
@ -257,14 +320,15 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> {
|
||||
|
||||
let number = self.builder.interfaces.len() as _;
|
||||
let iface = Interface {
|
||||
handler: None,
|
||||
current_alt_setting: 0,
|
||||
num_alt_settings: 0,
|
||||
num_strings: 0,
|
||||
};
|
||||
|
||||
if self.builder.interfaces.push(iface).is_err() {
|
||||
panic!("max interface count reached")
|
||||
panic!(
|
||||
"embassy-usb: interface list full. Increase the `max_interface_count` compile-time setting. Current value: {}",
|
||||
MAX_INTERFACE_COUNT
|
||||
)
|
||||
}
|
||||
|
||||
InterfaceBuilder {
|
||||
@ -273,6 +337,21 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> {
|
||||
next_alt_setting_number: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
/// Add an MS OS 2.0 Function Level Feature Descriptor.
|
||||
pub fn msos_feature<T: FunctionLevelDescriptor>(&mut self, desc: T) {
|
||||
if !self.builder.msos_descriptor.is_in_config_subset() {
|
||||
self.builder.msos_descriptor.configuration(0);
|
||||
}
|
||||
|
||||
if !self.builder.msos_descriptor.is_in_function_subset() {
|
||||
self.builder.msos_descriptor.function(self.first_interface);
|
||||
}
|
||||
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
self.builder.msos_descriptor.function_feature(desc);
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface builder.
|
||||
@ -288,17 +367,9 @@ impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> {
|
||||
self.interface_number
|
||||
}
|
||||
|
||||
pub fn handler(&mut self, handler: &'d mut dyn ControlHandler) {
|
||||
self.builder.interfaces[self.interface_number.0 as usize].handler = Some(handler);
|
||||
}
|
||||
|
||||
/// Allocates a new string index.
|
||||
pub fn string(&mut self) -> StringIndex {
|
||||
let index = self.builder.next_string_index;
|
||||
self.builder.next_string_index += 1;
|
||||
self.builder.interfaces[self.interface_number.0 as usize].num_strings += 1;
|
||||
|
||||
StringIndex::new(index)
|
||||
self.builder.string()
|
||||
}
|
||||
|
||||
/// Add an alternate setting to the interface and write its descriptor.
|
||||
@ -306,14 +377,25 @@ impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> {
|
||||
/// Alternate setting numbers are guaranteed to be allocated consecutively, starting from 0.
|
||||
///
|
||||
/// The first alternate setting, with number 0, is the default one.
|
||||
pub fn alt_setting(&mut self, class: u8, subclass: u8, protocol: u8) -> InterfaceAltBuilder<'_, 'd, D> {
|
||||
pub fn alt_setting(
|
||||
&mut self,
|
||||
class: u8,
|
||||
subclass: u8,
|
||||
protocol: u8,
|
||||
interface_string: Option<StringIndex>,
|
||||
) -> InterfaceAltBuilder<'_, 'd, D> {
|
||||
let number = self.next_alt_setting_number;
|
||||
self.next_alt_setting_number += 1;
|
||||
self.builder.interfaces[self.interface_number.0 as usize].num_alt_settings += 1;
|
||||
|
||||
self.builder
|
||||
.config_descriptor
|
||||
.interface_alt(self.interface_number, number, class, subclass, protocol, None);
|
||||
self.builder.config_descriptor.interface_alt(
|
||||
self.interface_number,
|
||||
number,
|
||||
class,
|
||||
subclass,
|
||||
protocol,
|
||||
interface_string,
|
||||
);
|
||||
|
||||
InterfaceAltBuilder {
|
||||
builder: self.builder,
|
||||
@ -349,11 +431,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
|
||||
self.builder.config_descriptor.write(descriptor_type, descriptor)
|
||||
}
|
||||
|
||||
fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointIn {
|
||||
fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
|
||||
let ep = self
|
||||
.builder
|
||||
.driver
|
||||
.alloc_endpoint_in(ep_type, max_packet_size, interval)
|
||||
.alloc_endpoint_in(ep_type, max_packet_size, interval_ms)
|
||||
.expect("alloc_endpoint_in failed");
|
||||
|
||||
self.builder.config_descriptor.endpoint(ep.info());
|
||||
@ -361,11 +443,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
|
||||
ep
|
||||
}
|
||||
|
||||
fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointOut {
|
||||
fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
|
||||
let ep = self
|
||||
.builder
|
||||
.driver
|
||||
.alloc_endpoint_out(ep_type, max_packet_size, interval)
|
||||
.alloc_endpoint_out(ep_type, max_packet_size, interval_ms)
|
||||
.expect("alloc_endpoint_out failed");
|
||||
|
||||
self.builder.config_descriptor.endpoint(ep.info());
|
||||
@ -393,12 +475,25 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
|
||||
///
|
||||
/// Descriptors are written in the order builder functions are called. Note that some
|
||||
/// classes care about the order.
|
||||
pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointIn {
|
||||
self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval)
|
||||
pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
|
||||
self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval_ms)
|
||||
}
|
||||
|
||||
/// Allocate a INTERRUPT OUT endpoint and write its descriptor.
|
||||
pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut {
|
||||
self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval)
|
||||
pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
|
||||
self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval_ms)
|
||||
}
|
||||
|
||||
/// Allocate a ISOCHRONOUS IN endpoint and write its descriptor.
|
||||
///
|
||||
/// Descriptors are written in the order builder functions are called. Note that some
|
||||
/// classes care about the order.
|
||||
pub fn endpoint_isochronous_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
|
||||
self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval_ms)
|
||||
}
|
||||
|
||||
/// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor.
|
||||
pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
|
||||
self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval_ms)
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
//! CDC-ACM class implementation, aka Serial over USB.
|
||||
|
||||
use core::cell::Cell;
|
||||
use core::mem::{self, MaybeUninit};
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use embassy_sync::blocking_mutex::CriticalSectionMutex;
|
||||
|
||||
use crate::control::{self, ControlHandler, InResponse, OutResponse, Request};
|
||||
use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType};
|
||||
use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
|
||||
use crate::types::*;
|
||||
use crate::Builder;
|
||||
use crate::{Builder, Handler};
|
||||
|
||||
/// This should be used as `device_class` when building the `UsbDevice`.
|
||||
pub const USB_CLASS_CDC: u8 = 0x02;
|
||||
@ -28,12 +30,14 @@ const REQ_SET_LINE_CODING: u8 = 0x20;
|
||||
const REQ_GET_LINE_CODING: u8 = 0x21;
|
||||
const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
|
||||
|
||||
/// Internal state for CDC-ACM
|
||||
pub struct State<'a> {
|
||||
control: MaybeUninit<Control<'a>>,
|
||||
shared: ControlShared,
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
/// Create a new `State`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
control: MaybeUninit::uninit(),
|
||||
@ -63,6 +67,7 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> {
|
||||
}
|
||||
|
||||
struct Control<'a> {
|
||||
comm_if: InterfaceNumber,
|
||||
shared: &'a ControlShared,
|
||||
}
|
||||
|
||||
@ -94,7 +99,7 @@ impl<'a> Control<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> ControlHandler for Control<'d> {
|
||||
impl<'d> Handler for Control<'d> {
|
||||
fn reset(&mut self) {
|
||||
let shared = self.shared();
|
||||
shared.line_coding.lock(|x| x.set(LineCoding::default()));
|
||||
@ -102,12 +107,18 @@ impl<'d> ControlHandler for Control<'d> {
|
||||
shared.rts.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse {
|
||||
fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option<OutResponse> {
|
||||
if (req.request_type, req.recipient, req.index)
|
||||
!= (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
match req.request {
|
||||
REQ_SEND_ENCAPSULATED_COMMAND => {
|
||||
// We don't actually support encapsulated commands but pretend we do for standards
|
||||
// compatibility.
|
||||
OutResponse::Accepted
|
||||
Some(OutResponse::Accepted)
|
||||
}
|
||||
REQ_SET_LINE_CODING if data.len() >= 7 => {
|
||||
let coding = LineCoding {
|
||||
@ -119,7 +130,7 @@ impl<'d> ControlHandler for Control<'d> {
|
||||
self.shared().line_coding.lock(|x| x.set(coding));
|
||||
debug!("Set line coding to: {:?}", coding);
|
||||
|
||||
OutResponse::Accepted
|
||||
Some(OutResponse::Accepted)
|
||||
}
|
||||
REQ_SET_CONTROL_LINE_STATE => {
|
||||
let dtr = (req.value & 0x0001) != 0;
|
||||
@ -130,13 +141,19 @@ impl<'d> ControlHandler for Control<'d> {
|
||||
shared.rts.store(rts, Ordering::Relaxed);
|
||||
debug!("Set dtr {}, rts {}", dtr, rts);
|
||||
|
||||
OutResponse::Accepted
|
||||
Some(OutResponse::Accepted)
|
||||
}
|
||||
_ => OutResponse::Rejected,
|
||||
_ => Some(OutResponse::Rejected),
|
||||
}
|
||||
}
|
||||
|
||||
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
|
||||
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
|
||||
if (req.request_type, req.recipient, req.index)
|
||||
!= (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
match req.request {
|
||||
// REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
|
||||
REQ_GET_LINE_CODING if req.length == 7 => {
|
||||
@ -147,9 +164,9 @@ impl<'d> ControlHandler for Control<'d> {
|
||||
buf[4] = coding.stop_bits as u8;
|
||||
buf[5] = coding.parity_type as u8;
|
||||
buf[6] = coding.data_bits;
|
||||
InResponse::Accepted(&buf[0..7])
|
||||
Some(InResponse::Accepted(&buf[0..7]))
|
||||
}
|
||||
_ => InResponse::Rejected,
|
||||
_ => Some(InResponse::Rejected),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,20 +175,15 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
|
||||
/// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For
|
||||
/// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64.
|
||||
pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, max_packet_size: u16) -> Self {
|
||||
let control = state.control.write(Control { shared: &state.shared });
|
||||
|
||||
let control_shared = &state.shared;
|
||||
|
||||
assert!(builder.control_buf_len() >= 7);
|
||||
|
||||
let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE);
|
||||
|
||||
// Control interface
|
||||
let mut iface = func.interface();
|
||||
iface.handler(control);
|
||||
let comm_if = iface.interface_number();
|
||||
let data_if = u8::from(comm_if) + 1;
|
||||
let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE);
|
||||
let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE, None);
|
||||
|
||||
alt.descriptor(
|
||||
CS_INTERFACE,
|
||||
@ -205,10 +217,20 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
|
||||
// Data interface
|
||||
let mut iface = func.interface();
|
||||
let data_if = iface.interface_number();
|
||||
let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE);
|
||||
let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE, None);
|
||||
let read_ep = alt.endpoint_bulk_out(max_packet_size);
|
||||
let write_ep = alt.endpoint_bulk_in(max_packet_size);
|
||||
|
||||
drop(func);
|
||||
|
||||
let control = state.control.write(Control {
|
||||
shared: &state.shared,
|
||||
comm_if,
|
||||
});
|
||||
builder.handler(control);
|
||||
|
||||
let control_shared = &state.shared;
|
||||
|
||||
CdcAcmClass {
|
||||
_comm_ep: comm_ep,
|
||||
_data_if: data_if,
|
||||
@ -284,10 +306,15 @@ impl From<u8> for StopBits {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ParityType {
|
||||
/// No parity bit.
|
||||
None = 0,
|
||||
/// Parity bit is 1 if the amount of `1` bits in the data byte is odd.
|
||||
Odd = 1,
|
||||
/// Parity bit is 1 if the amount of `1` bits in the data byte is even.
|
||||
Even = 2,
|
||||
/// Parity bit is always 1
|
||||
Mark = 3,
|
||||
/// Parity bit is always 0
|
||||
Space = 4,
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
//! [`embassy-net`](crates.io/crates/embassy-net) driver for the CDC-NCM class.
|
||||
use embassy_futures::select::{select, Either};
|
||||
use embassy_net_driver_channel as ch;
|
||||
use embassy_net_driver_channel::driver::LinkState;
|
||||
@ -5,11 +6,13 @@ use embassy_usb_driver::Driver;
|
||||
|
||||
use super::{CdcNcmClass, Receiver, Sender};
|
||||
|
||||
/// Internal state for the embassy-net integration.
|
||||
pub struct State<const MTU: usize, const N_RX: usize, const N_TX: usize> {
|
||||
ch_state: ch::State<MTU, N_RX, N_TX>,
|
||||
}
|
||||
|
||||
impl<const MTU: usize, const N_RX: usize, const N_TX: usize> State<MTU, N_RX, N_TX> {
|
||||
/// Create a new `State`.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
ch_state: ch::State::new(),
|
||||
@ -17,6 +20,9 @@ impl<const MTU: usize, const N_RX: usize, const N_TX: usize> State<MTU, N_RX, N_
|
||||
}
|
||||
}
|
||||
|
||||
/// Background runner for the CDC-NCM class.
|
||||
///
|
||||
/// You must call `.run()` in a background task for the class to operate.
|
||||
pub struct Runner<'d, D: Driver<'d>, const MTU: usize> {
|
||||
tx_usb: Sender<'d, D>,
|
||||
rx_usb: Receiver<'d, D>,
|
||||
@ -24,6 +30,9 @@ pub struct Runner<'d, D: Driver<'d>, const MTU: usize> {
|
||||
}
|
||||
|
||||
impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> {
|
||||
/// Run the CDC-NCM class.
|
||||
///
|
||||
/// You must call this in a background task for the class to operate.
|
||||
pub async fn run(mut self) -> ! {
|
||||
let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split();
|
||||
let rx_fut = async move {
|
||||
@ -66,9 +75,11 @@ impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> {
|
||||
|
||||
// would be cool to use a TAIT here, but it gives a "may not live long enough". rustc bug?
|
||||
//pub type Device<'d, const MTU: usize> = impl embassy_net_driver_channel::driver::Driver + 'd;
|
||||
/// Type alias for the embassy-net driver for CDC-NCM.
|
||||
pub type Device<'d, const MTU: usize> = embassy_net_driver_channel::Device<'d, MTU>;
|
||||
|
||||
impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
||||
/// Obtain a driver for using the CDC-NCM class with [`embassy-net`](crates.io/crates/embassy-net).
|
||||
pub fn into_embassy_net_device<const MTU: usize, const N_RX: usize, const N_TX: usize>(
|
||||
self,
|
||||
state: &'d mut State<MTU, N_RX, N_TX>,
|
||||
|
@ -1,25 +1,26 @@
|
||||
/// CDC-NCM, aka Ethernet over USB.
|
||||
///
|
||||
/// # Compatibility
|
||||
///
|
||||
/// Windows: NOT supported in Windows 10. Supported in Windows 11.
|
||||
///
|
||||
/// Linux: Well-supported since forever.
|
||||
///
|
||||
/// Android: Support for CDC-NCM is spotty and varies across manufacturers.
|
||||
///
|
||||
/// - On Pixel 4a, it refused to work on Android 11, worked on Android 12.
|
||||
/// - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte),
|
||||
/// it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled.
|
||||
/// This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417
|
||||
/// and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757
|
||||
//! CDC-NCM class implementation, aka Ethernet over USB.
|
||||
//!
|
||||
//! # Compatibility
|
||||
//!
|
||||
//! Windows: NOT supported in Windows 10 (though there's apparently a driver you can install?). Supported out of the box in Windows 11.
|
||||
//!
|
||||
//! Linux: Well-supported since forever.
|
||||
//!
|
||||
//! Android: Support for CDC-NCM is spotty and varies across manufacturers.
|
||||
//!
|
||||
//! - On Pixel 4a, it refused to work on Android 11, worked on Android 12.
|
||||
//! - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte),
|
||||
//! it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled.
|
||||
//! This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417
|
||||
//! and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757
|
||||
|
||||
use core::intrinsics::copy_nonoverlapping;
|
||||
use core::mem::{size_of, MaybeUninit};
|
||||
|
||||
use crate::control::{self, ControlHandler, InResponse, OutResponse, Request};
|
||||
use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType};
|
||||
use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
|
||||
use crate::types::*;
|
||||
use crate::Builder;
|
||||
use crate::{Builder, Handler};
|
||||
|
||||
pub mod embassy_net;
|
||||
|
||||
@ -114,17 +115,17 @@ fn byteify<T>(buf: &mut [u8], data: T) -> &[u8] {
|
||||
&buf[..len]
|
||||
}
|
||||
|
||||
/// Internal state for the CDC-NCM class.
|
||||
pub struct State<'a> {
|
||||
comm_control: MaybeUninit<CommControl<'a>>,
|
||||
data_control: MaybeUninit<DataControl>,
|
||||
control: MaybeUninit<Control<'a>>,
|
||||
shared: ControlShared,
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
/// Create a new `State`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
comm_control: MaybeUninit::uninit(),
|
||||
data_control: MaybeUninit::uninit(),
|
||||
control: MaybeUninit::uninit(),
|
||||
shared: Default::default(),
|
||||
}
|
||||
}
|
||||
@ -141,29 +142,55 @@ impl Default for ControlShared {
|
||||
}
|
||||
}
|
||||
|
||||
struct CommControl<'a> {
|
||||
struct Control<'a> {
|
||||
mac_addr_string: StringIndex,
|
||||
shared: &'a ControlShared,
|
||||
mac_addr_str: [u8; 12],
|
||||
comm_if: InterfaceNumber,
|
||||
data_if: InterfaceNumber,
|
||||
}
|
||||
|
||||
impl<'d> ControlHandler for CommControl<'d> {
|
||||
fn control_out(&mut self, req: control::Request, _data: &[u8]) -> OutResponse {
|
||||
impl<'d> Handler for Control<'d> {
|
||||
fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) {
|
||||
if iface != self.data_if {
|
||||
return;
|
||||
}
|
||||
|
||||
match alternate_setting {
|
||||
ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"),
|
||||
ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn control_out(&mut self, req: control::Request, _data: &[u8]) -> Option<OutResponse> {
|
||||
if (req.request_type, req.recipient, req.index)
|
||||
!= (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
match req.request {
|
||||
REQ_SEND_ENCAPSULATED_COMMAND => {
|
||||
// We don't actually support encapsulated commands but pretend we do for standards
|
||||
// compatibility.
|
||||
OutResponse::Accepted
|
||||
Some(OutResponse::Accepted)
|
||||
}
|
||||
REQ_SET_NTB_INPUT_SIZE => {
|
||||
// TODO
|
||||
OutResponse::Accepted
|
||||
Some(OutResponse::Accepted)
|
||||
}
|
||||
_ => OutResponse::Rejected,
|
||||
_ => Some(OutResponse::Rejected),
|
||||
}
|
||||
}
|
||||
|
||||
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
|
||||
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
|
||||
if (req.request_type, req.recipient, req.index)
|
||||
!= (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
match req.request {
|
||||
REQ_GET_NTB_PARAMETERS => {
|
||||
let res = NtbParameters {
|
||||
@ -184,9 +211,9 @@ impl<'d> ControlHandler for CommControl<'d> {
|
||||
max_datagram_count: 1, // We only decode 1 packet per NTB
|
||||
},
|
||||
};
|
||||
InResponse::Accepted(byteify(buf, res))
|
||||
Some(InResponse::Accepted(byteify(buf, res)))
|
||||
}
|
||||
_ => InResponse::Rejected,
|
||||
_ => Some(InResponse::Rejected),
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,18 +238,7 @@ impl<'d> ControlHandler for CommControl<'d> {
|
||||
}
|
||||
}
|
||||
|
||||
struct DataControl {}
|
||||
|
||||
impl ControlHandler for DataControl {
|
||||
fn set_alternate_setting(&mut self, alternate_setting: u8) {
|
||||
match alternate_setting {
|
||||
ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"),
|
||||
ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CDC-NCM class
|
||||
pub struct CdcNcmClass<'d, D: Driver<'d>> {
|
||||
_comm_if: InterfaceNumber,
|
||||
comm_ep: D::EndpointIn,
|
||||
@ -235,6 +251,7 @@ pub struct CdcNcmClass<'d, D: Driver<'d>> {
|
||||
}
|
||||
|
||||
impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
||||
/// Create a new CDC NCM class.
|
||||
pub fn new(
|
||||
builder: &mut Builder<'d, D>,
|
||||
state: &'d mut State<'d>,
|
||||
@ -248,13 +265,8 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
||||
// Control interface
|
||||
let mut iface = func.interface();
|
||||
let mac_addr_string = iface.string();
|
||||
iface.handler(state.comm_control.write(CommControl {
|
||||
mac_addr_string,
|
||||
shared: &state.shared,
|
||||
mac_addr_str: [0; 12],
|
||||
}));
|
||||
let comm_if = iface.interface_number();
|
||||
let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE);
|
||||
let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE, None);
|
||||
|
||||
alt.descriptor(
|
||||
CS_INTERFACE,
|
||||
@ -302,13 +314,23 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
||||
|
||||
// Data interface
|
||||
let mut iface = func.interface();
|
||||
iface.handler(state.data_control.write(DataControl {}));
|
||||
let data_if = iface.interface_number();
|
||||
let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB);
|
||||
let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB);
|
||||
let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None);
|
||||
let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None);
|
||||
let read_ep = alt.endpoint_bulk_out(max_packet_size);
|
||||
let write_ep = alt.endpoint_bulk_in(max_packet_size);
|
||||
|
||||
drop(func);
|
||||
|
||||
let control = state.control.write(Control {
|
||||
mac_addr_string,
|
||||
shared: &state.shared,
|
||||
mac_addr_str: [0; 12],
|
||||
comm_if,
|
||||
data_if,
|
||||
});
|
||||
builder.handler(control);
|
||||
|
||||
CdcNcmClass {
|
||||
_comm_if: comm_if,
|
||||
comm_ep,
|
||||
@ -319,6 +341,9 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Split the class into a sender and receiver.
|
||||
///
|
||||
/// This allows concurrently sending and receiving packets from separate tasks.
|
||||
pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) {
|
||||
(
|
||||
Sender {
|
||||
@ -334,12 +359,18 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
||||
}
|
||||
}
|
||||
|
||||
/// CDC NCM class packet sender.
|
||||
///
|
||||
/// You can obtain a `Sender` with [`CdcNcmClass::split`]
|
||||
pub struct Sender<'d, D: Driver<'d>> {
|
||||
write_ep: D::EndpointIn,
|
||||
seq: u16,
|
||||
}
|
||||
|
||||
impl<'d, D: Driver<'d>> Sender<'d, D> {
|
||||
/// Write a packet.
|
||||
///
|
||||
/// This waits until the packet is succesfully stored in the CDC-NCM endpoint buffers.
|
||||
pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> {
|
||||
let seq = self.seq;
|
||||
self.seq = self.seq.wrapping_add(1);
|
||||
@ -393,6 +424,9 @@ impl<'d, D: Driver<'d>> Sender<'d, D> {
|
||||
}
|
||||
}
|
||||
|
||||
/// CDC NCM class packet receiver.
|
||||
///
|
||||
/// You can obtain a `Receiver` with [`CdcNcmClass::split`]
|
||||
pub struct Receiver<'d, D: Driver<'d>> {
|
||||
data_if: InterfaceNumber,
|
||||
comm_ep: D::EndpointIn,
|
||||
@ -400,7 +434,9 @@ pub struct Receiver<'d, D: Driver<'d>> {
|
||||
}
|
||||
|
||||
impl<'d, D: Driver<'d>> Receiver<'d, D> {
|
||||
/// Reads a single packet from the OUT endpoint.
|
||||
/// Write a network packet.
|
||||
///
|
||||
/// This waits until a packet is succesfully received from the endpoint buffers.
|
||||
pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> {
|
||||
// Retry loop
|
||||
loop {
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! USB HID (Human Interface Device) class implementation.
|
||||
|
||||
use core::mem::MaybeUninit;
|
||||
use core::ops::Range;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
@ -7,9 +9,10 @@ use ssmarshal::serialize;
|
||||
#[cfg(feature = "usbd-hid")]
|
||||
use usbd_hid::descriptor::AsInputReport;
|
||||
|
||||
use crate::control::{ControlHandler, InResponse, OutResponse, Request, RequestType};
|
||||
use crate::control::{InResponse, OutResponse, Recipient, Request, RequestType};
|
||||
use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
|
||||
use crate::Builder;
|
||||
use crate::types::InterfaceNumber;
|
||||
use crate::{Builder, Handler};
|
||||
|
||||
const USB_CLASS_HID: u8 = 0x03;
|
||||
const USB_SUBCLASS_NONE: u8 = 0x00;
|
||||
@ -28,6 +31,7 @@ const HID_REQ_SET_REPORT: u8 = 0x09;
|
||||
const HID_REQ_GET_PROTOCOL: u8 = 0x03;
|
||||
const HID_REQ_SET_PROTOCOL: u8 = 0x0b;
|
||||
|
||||
/// Configuration for the HID class.
|
||||
pub struct Config<'d> {
|
||||
/// HID report descriptor.
|
||||
pub report_descriptor: &'d [u8],
|
||||
@ -46,11 +50,15 @@ pub struct Config<'d> {
|
||||
pub max_packet_size: u16,
|
||||
}
|
||||
|
||||
/// Report ID
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ReportId {
|
||||
/// IN report
|
||||
In(u8),
|
||||
/// OUT report
|
||||
Out(u8),
|
||||
/// Feature report
|
||||
Feature(u8),
|
||||
}
|
||||
|
||||
@ -65,12 +73,14 @@ impl ReportId {
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal state for USB HID.
|
||||
pub struct State<'d> {
|
||||
control: MaybeUninit<Control<'d>>,
|
||||
out_report_offset: AtomicUsize,
|
||||
}
|
||||
|
||||
impl<'d> State<'d> {
|
||||
/// Create a new `State`.
|
||||
pub fn new() -> Self {
|
||||
State {
|
||||
control: MaybeUninit::uninit(),
|
||||
@ -79,6 +89,7 @@ impl<'d> State<'d> {
|
||||
}
|
||||
}
|
||||
|
||||
/// USB HID reader/writer.
|
||||
pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> {
|
||||
reader: HidReader<'d, D, READ_N>,
|
||||
writer: HidWriter<'d, D, WRITE_N>,
|
||||
@ -90,18 +101,12 @@ fn build<'d, D: Driver<'d>>(
|
||||
config: Config<'d>,
|
||||
with_out_endpoint: bool,
|
||||
) -> (Option<D::EndpointOut>, D::EndpointIn, &'d AtomicUsize) {
|
||||
let control = state.control.write(Control::new(
|
||||
config.report_descriptor,
|
||||
config.request_handler,
|
||||
&state.out_report_offset,
|
||||
));
|
||||
|
||||
let len = config.report_descriptor.len();
|
||||
|
||||
let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE);
|
||||
let mut iface = func.interface();
|
||||
iface.handler(control);
|
||||
let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE);
|
||||
let if_num = iface.interface_number();
|
||||
let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None);
|
||||
|
||||
// HID descriptor
|
||||
alt.descriptor(
|
||||
@ -129,6 +134,16 @@ fn build<'d, D: Driver<'d>>(
|
||||
None
|
||||
};
|
||||
|
||||
drop(func);
|
||||
|
||||
let control = state.control.write(Control::new(
|
||||
if_num,
|
||||
config.report_descriptor,
|
||||
config.request_handler,
|
||||
&state.out_report_offset,
|
||||
));
|
||||
builder.handler(control);
|
||||
|
||||
(ep_out, ep_in, &state.out_report_offset)
|
||||
}
|
||||
|
||||
@ -180,20 +195,30 @@ impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWrit
|
||||
}
|
||||
}
|
||||
|
||||
/// USB HID writer.
|
||||
///
|
||||
/// You can obtain a `HidWriter` using [`HidReaderWriter::split`].
|
||||
pub struct HidWriter<'d, D: Driver<'d>, const N: usize> {
|
||||
ep_in: D::EndpointIn,
|
||||
}
|
||||
|
||||
/// USB HID reader.
|
||||
///
|
||||
/// You can obtain a `HidReader` using [`HidReaderWriter::split`].
|
||||
pub struct HidReader<'d, D: Driver<'d>, const N: usize> {
|
||||
ep_out: D::EndpointOut,
|
||||
offset: &'d AtomicUsize,
|
||||
}
|
||||
|
||||
/// Error when reading a HID report.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ReadError {
|
||||
/// The given buffer was too small to read the received report.
|
||||
BufferOverflow,
|
||||
/// The endpoint is disabled.
|
||||
Disabled,
|
||||
/// The report was only partially read. See [`HidReader::read`] for details.
|
||||
Sync(Range<usize>),
|
||||
}
|
||||
|
||||
@ -344,6 +369,7 @@ impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for HID-related control requests.
|
||||
pub trait RequestHandler {
|
||||
/// Reads the value of report `id` into `buf` returning the size.
|
||||
///
|
||||
@ -379,6 +405,7 @@ pub trait RequestHandler {
|
||||
}
|
||||
|
||||
struct Control<'d> {
|
||||
if_num: InterfaceNumber,
|
||||
report_descriptor: &'d [u8],
|
||||
request_handler: Option<&'d dyn RequestHandler>,
|
||||
out_report_offset: &'d AtomicUsize,
|
||||
@ -387,11 +414,13 @@ struct Control<'d> {
|
||||
|
||||
impl<'d> Control<'d> {
|
||||
fn new(
|
||||
if_num: InterfaceNumber,
|
||||
report_descriptor: &'d [u8],
|
||||
request_handler: Option<&'d dyn RequestHandler>,
|
||||
out_report_offset: &'d AtomicUsize,
|
||||
) -> Self {
|
||||
Control {
|
||||
if_num,
|
||||
report_descriptor,
|
||||
request_handler,
|
||||
out_report_offset,
|
||||
@ -417,88 +446,100 @@ impl<'d> Control<'d> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> ControlHandler for Control<'d> {
|
||||
impl<'d> Handler for Control<'d> {
|
||||
fn reset(&mut self) {
|
||||
self.out_report_offset.store(0, Ordering::Release);
|
||||
}
|
||||
|
||||
fn get_descriptor<'a>(&'a mut self, req: Request, _buf: &'a mut [u8]) -> InResponse<'a> {
|
||||
match (req.value >> 8) as u8 {
|
||||
HID_DESC_DESCTYPE_HID_REPORT => InResponse::Accepted(self.report_descriptor),
|
||||
HID_DESC_DESCTYPE_HID => InResponse::Accepted(&self.hid_descriptor),
|
||||
_ => InResponse::Rejected,
|
||||
fn control_out(&mut self, req: Request, data: &[u8]) -> Option<OutResponse> {
|
||||
if (req.request_type, req.recipient, req.index)
|
||||
!= (RequestType::Class, Recipient::Interface, self.if_num.0 as u16)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse {
|
||||
trace!("HID control_out {:?} {=[u8]:x}", req, data);
|
||||
if let RequestType::Class = req.request_type {
|
||||
match req.request {
|
||||
HID_REQ_SET_IDLE => {
|
||||
if let Some(handler) = self.request_handler {
|
||||
let id = req.value as u8;
|
||||
let id = (id != 0).then(|| ReportId::In(id));
|
||||
let dur = u32::from(req.value >> 8);
|
||||
let dur = if dur == 0 { u32::MAX } else { 4 * dur };
|
||||
handler.set_idle_ms(id, dur);
|
||||
}
|
||||
OutResponse::Accepted
|
||||
}
|
||||
HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) {
|
||||
(Ok(id), Some(handler)) => handler.set_report(id, data),
|
||||
_ => OutResponse::Rejected,
|
||||
},
|
||||
HID_REQ_SET_PROTOCOL => {
|
||||
if req.value == 1 {
|
||||
OutResponse::Accepted
|
||||
} else {
|
||||
warn!("HID Boot Protocol is unsupported.");
|
||||
OutResponse::Rejected // UNSUPPORTED: Boot Protocol
|
||||
}
|
||||
}
|
||||
_ => OutResponse::Rejected,
|
||||
}
|
||||
} else {
|
||||
OutResponse::Rejected // UNSUPPORTED: SET_DESCRIPTOR
|
||||
}
|
||||
}
|
||||
|
||||
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
|
||||
trace!("HID control_in {:?}", req);
|
||||
match req.request {
|
||||
HID_REQ_GET_REPORT => {
|
||||
let size = match ReportId::try_from(req.value) {
|
||||
Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
if let Some(size) = size {
|
||||
InResponse::Accepted(&buf[0..size])
|
||||
} else {
|
||||
InResponse::Rejected
|
||||
}
|
||||
}
|
||||
HID_REQ_GET_IDLE => {
|
||||
HID_REQ_SET_IDLE => {
|
||||
if let Some(handler) = self.request_handler {
|
||||
let id = req.value as u8;
|
||||
let id = (id != 0).then(|| ReportId::In(id));
|
||||
if let Some(dur) = handler.get_idle_ms(id) {
|
||||
let dur = u8::try_from(dur / 4).unwrap_or(0);
|
||||
buf[0] = dur;
|
||||
InResponse::Accepted(&buf[0..1])
|
||||
} else {
|
||||
InResponse::Rejected
|
||||
}
|
||||
let dur = u32::from(req.value >> 8);
|
||||
let dur = if dur == 0 { u32::MAX } else { 4 * dur };
|
||||
handler.set_idle_ms(id, dur);
|
||||
}
|
||||
Some(OutResponse::Accepted)
|
||||
}
|
||||
HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) {
|
||||
(Ok(id), Some(handler)) => Some(handler.set_report(id, data)),
|
||||
_ => Some(OutResponse::Rejected),
|
||||
},
|
||||
HID_REQ_SET_PROTOCOL => {
|
||||
if req.value == 1 {
|
||||
Some(OutResponse::Accepted)
|
||||
} else {
|
||||
InResponse::Rejected
|
||||
warn!("HID Boot Protocol is unsupported.");
|
||||
Some(OutResponse::Rejected) // UNSUPPORTED: Boot Protocol
|
||||
}
|
||||
}
|
||||
HID_REQ_GET_PROTOCOL => {
|
||||
// UNSUPPORTED: Boot Protocol
|
||||
buf[0] = 1;
|
||||
InResponse::Accepted(&buf[0..1])
|
||||
_ => Some(OutResponse::Rejected),
|
||||
}
|
||||
}
|
||||
|
||||
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
|
||||
if req.index != self.if_num.0 as u16 {
|
||||
return None;
|
||||
}
|
||||
|
||||
match (req.request_type, req.recipient) {
|
||||
(RequestType::Standard, Recipient::Interface) => match req.request {
|
||||
Request::GET_DESCRIPTOR => match (req.value >> 8) as u8 {
|
||||
HID_DESC_DESCTYPE_HID_REPORT => Some(InResponse::Accepted(self.report_descriptor)),
|
||||
HID_DESC_DESCTYPE_HID => Some(InResponse::Accepted(&self.hid_descriptor)),
|
||||
_ => Some(InResponse::Rejected),
|
||||
},
|
||||
|
||||
_ => Some(InResponse::Rejected),
|
||||
},
|
||||
(RequestType::Class, Recipient::Interface) => {
|
||||
trace!("HID control_in {:?}", req);
|
||||
match req.request {
|
||||
HID_REQ_GET_REPORT => {
|
||||
let size = match ReportId::try_from(req.value) {
|
||||
Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
if let Some(size) = size {
|
||||
Some(InResponse::Accepted(&buf[0..size]))
|
||||
} else {
|
||||
Some(InResponse::Rejected)
|
||||
}
|
||||
}
|
||||
HID_REQ_GET_IDLE => {
|
||||
if let Some(handler) = self.request_handler {
|
||||
let id = req.value as u8;
|
||||
let id = (id != 0).then(|| ReportId::In(id));
|
||||
if let Some(dur) = handler.get_idle_ms(id) {
|
||||
let dur = u8::try_from(dur / 4).unwrap_or(0);
|
||||
buf[0] = dur;
|
||||
Some(InResponse::Accepted(&buf[0..1]))
|
||||
} else {
|
||||
Some(InResponse::Rejected)
|
||||
}
|
||||
} else {
|
||||
Some(InResponse::Rejected)
|
||||
}
|
||||
}
|
||||
HID_REQ_GET_PROTOCOL => {
|
||||
// UNSUPPORTED: Boot Protocol
|
||||
buf[0] = 1;
|
||||
Some(InResponse::Accepted(&buf[0..1]))
|
||||
}
|
||||
_ => Some(InResponse::Rejected),
|
||||
}
|
||||
}
|
||||
_ => InResponse::Rejected,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
//! Implementations of well-known USB classes.
|
||||
pub mod cdc_acm;
|
||||
pub mod cdc_ncm;
|
||||
pub mod hid;
|
||||
|
@ -2,7 +2,6 @@
|
||||
use core::mem;
|
||||
|
||||
use crate::driver::Direction;
|
||||
use crate::types::StringIndex;
|
||||
|
||||
/// Control request type.
|
||||
#[repr(u8)]
|
||||
@ -126,72 +125,22 @@ impl Request {
|
||||
}
|
||||
}
|
||||
|
||||
/// Response for a CONTROL OUT request.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum OutResponse {
|
||||
/// The request was accepted.
|
||||
Accepted,
|
||||
/// The request was rejected.
|
||||
Rejected,
|
||||
}
|
||||
|
||||
/// Response for a CONTROL IN request.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum InResponse<'a> {
|
||||
/// The request was accepted. The buffer contains the response data.
|
||||
Accepted(&'a [u8]),
|
||||
/// The request was rejected.
|
||||
Rejected,
|
||||
}
|
||||
|
||||
/// Handler for control requests.
|
||||
///
|
||||
/// All methods are optional callbacks that will be called by
|
||||
/// [`UsbDevice::run()`](crate::UsbDevice::run)
|
||||
pub trait ControlHandler {
|
||||
/// Called after a USB reset after the bus reset sequence is complete.
|
||||
fn reset(&mut self) {}
|
||||
|
||||
fn set_alternate_setting(&mut self, alternate_setting: u8) {
|
||||
let _ = alternate_setting;
|
||||
}
|
||||
|
||||
/// Called when a control request is received with direction HostToDevice.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `req` - The request from the SETUP packet.
|
||||
/// * `data` - The data from the request.
|
||||
fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse {
|
||||
let _ = (req, data);
|
||||
OutResponse::Rejected
|
||||
}
|
||||
|
||||
/// Called when a control request is received with direction DeviceToHost.
|
||||
///
|
||||
/// You should write the response somewhere (usually to `buf`, but you may use another buffer
|
||||
/// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `req` - The request from the SETUP packet.
|
||||
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
|
||||
let _ = (req, buf);
|
||||
InResponse::Rejected
|
||||
}
|
||||
|
||||
/// Called when a GET DESCRIPTOR control request is received on the interface.
|
||||
///
|
||||
/// You should write the response somewhere (usually to `buf`, but you may use another buffer
|
||||
/// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `req` - The request from the SETUP packet.
|
||||
fn get_descriptor<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
|
||||
let _ = (req, buf);
|
||||
InResponse::Rejected
|
||||
}
|
||||
|
||||
/// Called when a GET_DESCRIPTOR STRING control request is received.
|
||||
fn get_string(&mut self, index: StringIndex, lang_id: u16) -> Option<&str> {
|
||||
let _ = (index, lang_id);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Utilities for writing USB descriptors.
|
||||
|
||||
use crate::builder::Config;
|
||||
use crate::driver::EndpointInfo;
|
||||
use crate::types::*;
|
||||
@ -236,7 +238,7 @@ impl<'a> DescriptorWriter<'a> {
|
||||
endpoint.ep_type as u8, // bmAttributes
|
||||
endpoint.max_packet_size as u8,
|
||||
(endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize
|
||||
endpoint.interval, // bInterval
|
||||
endpoint.interval_ms, // bInterval
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::descriptor::descriptor_type;
|
||||
use crate::driver::EndpointAddress;
|
||||
use crate::types::InterfaceNumber;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
@ -75,7 +76,7 @@ impl<'a, 'b> Iterator for DescriptorIter<'a, 'b> {
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct EndpointInfo {
|
||||
pub configuration: u8,
|
||||
pub interface: u8,
|
||||
pub interface: InterfaceNumber,
|
||||
pub interface_alt: u8,
|
||||
pub ep_address: EndpointAddress,
|
||||
}
|
||||
@ -83,7 +84,7 @@ pub struct EndpointInfo {
|
||||
pub fn foreach_endpoint(data: &[u8], mut f: impl FnMut(EndpointInfo)) -> Result<(), ReadError> {
|
||||
let mut ep = EndpointInfo {
|
||||
configuration: 0,
|
||||
interface: 0,
|
||||
interface: InterfaceNumber(0),
|
||||
interface_alt: 0,
|
||||
ep_address: EndpointAddress::from(0),
|
||||
};
|
||||
@ -96,7 +97,7 @@ pub fn foreach_endpoint(data: &[u8], mut f: impl FnMut(EndpointInfo)) -> Result<
|
||||
ep.configuration = r.read_u8()?;
|
||||
}
|
||||
descriptor_type::INTERFACE => {
|
||||
ep.interface = r.read_u8()?;
|
||||
ep.interface = InterfaceNumber(r.read_u8()?);
|
||||
ep.interface_alt = r.read_u8()?;
|
||||
}
|
||||
descriptor_type::ENDPOINT => {
|
||||
|
@ -1,6 +1,8 @@
|
||||
#![no_std]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![feature(async_fn_in_trait)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
@ -12,13 +14,20 @@ pub mod class;
|
||||
pub mod control;
|
||||
pub mod descriptor;
|
||||
mod descriptor_reader;
|
||||
pub mod msos;
|
||||
mod packed;
|
||||
pub mod types;
|
||||
|
||||
mod config {
|
||||
#![allow(unused)]
|
||||
include!(concat!(env!("OUT_DIR"), "/config.rs"));
|
||||
}
|
||||
|
||||
use embassy_futures::select::{select, Either};
|
||||
use heapless::Vec;
|
||||
|
||||
pub use crate::builder::{Builder, Config};
|
||||
use crate::config::*;
|
||||
use crate::control::*;
|
||||
use crate::descriptor::*;
|
||||
use crate::descriptor_reader::foreach_endpoint;
|
||||
@ -48,10 +57,13 @@ pub enum UsbDeviceState {
|
||||
Configured,
|
||||
}
|
||||
|
||||
/// Error returned by [`UsbDevice::remote_wakeup`].
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum RemoteWakeupError {
|
||||
/// The USB device is not suspended, or remote wakeup was not enabled.
|
||||
InvalidState,
|
||||
/// The underlying driver doesn't support remote wakeup.
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
@ -67,41 +79,95 @@ pub const CONFIGURATION_NONE: u8 = 0;
|
||||
/// The bConfiguration value for the single configuration supported by this device.
|
||||
pub const CONFIGURATION_VALUE: u8 = 1;
|
||||
|
||||
pub const MAX_INTERFACE_COUNT: usize = 4;
|
||||
|
||||
const STRING_INDEX_MANUFACTURER: u8 = 1;
|
||||
const STRING_INDEX_PRODUCT: u8 = 2;
|
||||
const STRING_INDEX_SERIAL_NUMBER: u8 = 3;
|
||||
const STRING_INDEX_CUSTOM_START: u8 = 4;
|
||||
|
||||
/// A handler trait for changes in the device state of the [UsbDevice].
|
||||
pub trait DeviceStateHandler {
|
||||
/// Handler for device events and control requests.
|
||||
///
|
||||
/// All methods are optional callbacks that will be called by
|
||||
/// [`UsbDevice::run()`](crate::UsbDevice::run)
|
||||
pub trait Handler {
|
||||
/// Called when the USB device has been enabled or disabled.
|
||||
fn enabled(&self, _enabled: bool) {}
|
||||
fn enabled(&mut self, _enabled: bool) {}
|
||||
|
||||
/// Called when the host resets the device.
|
||||
fn reset(&self) {}
|
||||
/// Called after a USB reset after the bus reset sequence is complete.
|
||||
fn reset(&mut self) {}
|
||||
|
||||
/// Called when the host has set the address of the device to `addr`.
|
||||
fn addressed(&self, _addr: u8) {}
|
||||
fn addressed(&mut self, _addr: u8) {}
|
||||
|
||||
/// Called when the host has enabled or disabled the configuration of the device.
|
||||
fn configured(&self, _configured: bool) {}
|
||||
fn configured(&mut self, _configured: bool) {}
|
||||
|
||||
/// Called when the bus has entered or exited the suspend state.
|
||||
fn suspended(&self, _suspended: bool) {}
|
||||
fn suspended(&mut self, _suspended: bool) {}
|
||||
|
||||
/// Called when remote wakeup feature is enabled or disabled.
|
||||
fn remote_wakeup_enabled(&self, _enabled: bool) {}
|
||||
fn remote_wakeup_enabled(&mut self, _enabled: bool) {}
|
||||
|
||||
/// Called when a "set alternate setting" control request is done on the interface.
|
||||
fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) {
|
||||
let _ = iface;
|
||||
let _ = alternate_setting;
|
||||
}
|
||||
|
||||
/// Called when a control request is received with direction HostToDevice.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `req` - The request from the SETUP packet.
|
||||
/// * `data` - The data from the request.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// If you didn't handle this request (for example if it's for the wrong interface), return
|
||||
/// `None`. In this case, the the USB stack will continue calling the other handlers, to see
|
||||
/// if another handles it.
|
||||
///
|
||||
/// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack
|
||||
/// respond to the control request, and stop calling other handlers.
|
||||
fn control_out(&mut self, req: Request, data: &[u8]) -> Option<OutResponse> {
|
||||
let _ = (req, data);
|
||||
None
|
||||
}
|
||||
|
||||
/// Called when a control request is received with direction DeviceToHost.
|
||||
///
|
||||
/// You should write the response somewhere (usually to `buf`, but you may use another buffer
|
||||
/// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `req` - The request from the SETUP packet.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// If you didn't handle this request (for example if it's for the wrong interface), return
|
||||
/// `None`. In this case, the the USB stack will continue calling the other handlers, to see
|
||||
/// if another handles it.
|
||||
///
|
||||
/// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack
|
||||
/// respond to the control request, and stop calling other handlers.
|
||||
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
|
||||
let _ = (req, buf);
|
||||
None
|
||||
}
|
||||
|
||||
/// Called when a GET_DESCRIPTOR STRING control request is received.
|
||||
fn get_string(&mut self, index: StringIndex, lang_id: u16) -> Option<&str> {
|
||||
let _ = (index, lang_id);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
struct Interface<'d> {
|
||||
handler: Option<&'d mut dyn ControlHandler>,
|
||||
struct Interface {
|
||||
current_alt_setting: u8,
|
||||
num_alt_settings: u8,
|
||||
num_strings: u8,
|
||||
}
|
||||
|
||||
/// Main struct for the USB device stack.
|
||||
pub struct UsbDevice<'d, D: Driver<'d>> {
|
||||
control_buf: &'d mut [u8],
|
||||
control: D::ControlPipe,
|
||||
@ -110,7 +176,6 @@ pub struct UsbDevice<'d, D: Driver<'d>> {
|
||||
|
||||
struct Inner<'d, D: Driver<'d>> {
|
||||
bus: D::Bus,
|
||||
handler: Option<&'d dyn DeviceStateHandler>,
|
||||
|
||||
config: Config<'d>,
|
||||
device_descriptor: &'d [u8],
|
||||
@ -129,19 +194,24 @@ struct Inner<'d, D: Driver<'d>> {
|
||||
/// instead of regular `accept()`.
|
||||
set_address_pending: bool,
|
||||
|
||||
interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>,
|
||||
interfaces: Vec<Interface, MAX_INTERFACE_COUNT>,
|
||||
handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>,
|
||||
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
msos_descriptor: crate::msos::MsOsDescriptorSet<'d>,
|
||||
}
|
||||
|
||||
impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
|
||||
pub(crate) fn build(
|
||||
driver: D,
|
||||
config: Config<'d>,
|
||||
handler: Option<&'d dyn DeviceStateHandler>,
|
||||
handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>,
|
||||
device_descriptor: &'d [u8],
|
||||
config_descriptor: &'d [u8],
|
||||
bos_descriptor: &'d [u8],
|
||||
interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>,
|
||||
interfaces: Vec<Interface, MAX_INTERFACE_COUNT>,
|
||||
control_buf: &'d mut [u8],
|
||||
#[cfg(feature = "msos-descriptor")] msos_descriptor: crate::msos::MsOsDescriptorSet<'d>,
|
||||
) -> UsbDevice<'d, D> {
|
||||
// Start the USB bus.
|
||||
// This prevent further allocation by consuming the driver.
|
||||
@ -153,7 +223,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
|
||||
inner: Inner {
|
||||
bus,
|
||||
config,
|
||||
handler,
|
||||
device_descriptor,
|
||||
config_descriptor,
|
||||
bos_descriptor,
|
||||
@ -165,6 +234,9 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
|
||||
address: 0,
|
||||
set_address_pending: false,
|
||||
interfaces,
|
||||
handlers,
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
msos_descriptor,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -207,7 +279,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
|
||||
self.inner.suspended = false;
|
||||
self.inner.remote_wakeup_enabled = false;
|
||||
|
||||
if let Some(h) = &self.inner.handler {
|
||||
for h in &mut self.inner.handlers {
|
||||
h.enabled(false);
|
||||
}
|
||||
}
|
||||
@ -236,7 +308,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
|
||||
self.inner.bus.remote_wakeup().await?;
|
||||
self.inner.suspended = false;
|
||||
|
||||
if let Some(h) = &self.inner.handler {
|
||||
for h in &mut self.inner.handlers {
|
||||
h.suspended(false);
|
||||
}
|
||||
|
||||
@ -347,29 +419,29 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
self.remote_wakeup_enabled = false;
|
||||
self.address = 0;
|
||||
|
||||
for iface in self.interfaces.iter_mut() {
|
||||
iface.current_alt_setting = 0;
|
||||
if let Some(h) = &mut iface.handler {
|
||||
h.reset();
|
||||
h.set_alternate_setting(0);
|
||||
}
|
||||
for h in &mut self.handlers {
|
||||
h.reset();
|
||||
}
|
||||
|
||||
if let Some(h) = &self.handler {
|
||||
h.reset();
|
||||
for (i, iface) in self.interfaces.iter_mut().enumerate() {
|
||||
iface.current_alt_setting = 0;
|
||||
|
||||
for h in &mut self.handlers {
|
||||
h.set_alternate_setting(InterfaceNumber::new(i as _), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Resume => {
|
||||
trace!("usb: resume");
|
||||
self.suspended = false;
|
||||
if let Some(h) = &self.handler {
|
||||
for h in &mut self.handlers {
|
||||
h.suspended(false);
|
||||
}
|
||||
}
|
||||
Event::Suspend => {
|
||||
trace!("usb: suspend");
|
||||
self.suspended = true;
|
||||
if let Some(h) = &self.handler {
|
||||
for h in &mut self.handlers {
|
||||
h.suspended(true);
|
||||
}
|
||||
}
|
||||
@ -378,7 +450,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
self.bus.enable().await;
|
||||
self.device_state = UsbDeviceState::Default;
|
||||
|
||||
if let Some(h) = &self.handler {
|
||||
for h in &mut self.handlers {
|
||||
h.enabled(true);
|
||||
}
|
||||
}
|
||||
@ -387,7 +459,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
self.bus.disable().await;
|
||||
self.device_state = UsbDeviceState::Unpowered;
|
||||
|
||||
if let Some(h) = &self.handler {
|
||||
for h in &mut self.handlers {
|
||||
h.enabled(false);
|
||||
}
|
||||
}
|
||||
@ -402,14 +474,14 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
(RequestType::Standard, Recipient::Device) => match (req.request, req.value) {
|
||||
(Request::CLEAR_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => {
|
||||
self.remote_wakeup_enabled = false;
|
||||
if let Some(h) = &self.handler {
|
||||
for h in &mut self.handlers {
|
||||
h.remote_wakeup_enabled(false);
|
||||
}
|
||||
OutResponse::Accepted
|
||||
}
|
||||
(Request::SET_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => {
|
||||
self.remote_wakeup_enabled = true;
|
||||
if let Some(h) = &self.handler {
|
||||
for h in &mut self.handlers {
|
||||
h.remote_wakeup_enabled(true);
|
||||
}
|
||||
OutResponse::Accepted
|
||||
@ -418,7 +490,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
self.address = addr as u8;
|
||||
self.set_address_pending = true;
|
||||
self.device_state = UsbDeviceState::Addressed;
|
||||
if let Some(h) = &self.handler {
|
||||
for h in &mut self.handlers {
|
||||
h.addressed(self.address);
|
||||
}
|
||||
OutResponse::Accepted
|
||||
@ -429,14 +501,14 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
|
||||
// Enable all endpoints of selected alt settings.
|
||||
foreach_endpoint(self.config_descriptor, |ep| {
|
||||
let iface = &self.interfaces[ep.interface as usize];
|
||||
let iface = &self.interfaces[ep.interface.0 as usize];
|
||||
self.bus
|
||||
.endpoint_set_enabled(ep.ep_address, iface.current_alt_setting == ep.interface_alt);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Notify handler.
|
||||
if let Some(h) = &self.handler {
|
||||
// Notify handlers.
|
||||
for h in &mut self.handlers {
|
||||
h.configured(true);
|
||||
}
|
||||
|
||||
@ -454,8 +526,8 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Notify handler.
|
||||
if let Some(h) = &self.handler {
|
||||
// Notify handlers.
|
||||
for h in &mut self.handlers {
|
||||
h.configured(false);
|
||||
}
|
||||
|
||||
@ -465,7 +537,8 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
_ => OutResponse::Rejected,
|
||||
},
|
||||
(RequestType::Standard, Recipient::Interface) => {
|
||||
let iface = match self.interfaces.get_mut(req.index as usize) {
|
||||
let iface_num = InterfaceNumber::new(req.index as _);
|
||||
let iface = match self.interfaces.get_mut(iface_num.0 as usize) {
|
||||
Some(iface) => iface,
|
||||
None => return OutResponse::Rejected,
|
||||
};
|
||||
@ -483,7 +556,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
|
||||
// Enable/disable EPs of this interface as needed.
|
||||
foreach_endpoint(self.config_descriptor, |ep| {
|
||||
if ep.interface == req.index as u8 {
|
||||
if ep.interface == iface_num {
|
||||
self.bus
|
||||
.endpoint_set_enabled(ep.ep_address, iface.current_alt_setting == ep.interface_alt);
|
||||
}
|
||||
@ -491,10 +564,9 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
.unwrap();
|
||||
|
||||
// TODO check it is valid (not out of range)
|
||||
// TODO actually enable/disable endpoints.
|
||||
|
||||
if let Some(handler) = &mut iface.handler {
|
||||
handler.set_alternate_setting(new_altsetting);
|
||||
for h in &mut self.handlers {
|
||||
h.set_alternate_setting(iface_num, new_altsetting);
|
||||
}
|
||||
OutResponse::Accepted
|
||||
}
|
||||
@ -514,17 +586,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
}
|
||||
_ => OutResponse::Rejected,
|
||||
},
|
||||
(RequestType::Class, Recipient::Interface) => {
|
||||
let iface = match self.interfaces.get_mut(req.index as usize) {
|
||||
Some(iface) => iface,
|
||||
None => return OutResponse::Rejected,
|
||||
};
|
||||
match &mut iface.handler {
|
||||
Some(handler) => handler.control_out(req, data),
|
||||
None => OutResponse::Rejected,
|
||||
}
|
||||
}
|
||||
_ => OutResponse::Rejected,
|
||||
_ => self.handle_control_out_delegated(req, data),
|
||||
}
|
||||
}
|
||||
|
||||
@ -569,11 +631,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
buf[0] = iface.current_alt_setting;
|
||||
InResponse::Accepted(&buf[..1])
|
||||
}
|
||||
Request::GET_DESCRIPTOR => match &mut iface.handler {
|
||||
Some(handler) => handler.get_descriptor(req, buf),
|
||||
None => InResponse::Rejected,
|
||||
},
|
||||
_ => InResponse::Rejected,
|
||||
_ => self.handle_control_in_delegated(req, buf),
|
||||
}
|
||||
}
|
||||
(RequestType::Standard, Recipient::Endpoint) => match req.request {
|
||||
@ -588,21 +646,48 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
}
|
||||
_ => InResponse::Rejected,
|
||||
},
|
||||
(RequestType::Class, Recipient::Interface) => {
|
||||
let iface = match self.interfaces.get_mut(req.index as usize) {
|
||||
Some(iface) => iface,
|
||||
None => return InResponse::Rejected,
|
||||
};
|
||||
|
||||
match &mut iface.handler {
|
||||
Some(handler) => handler.control_in(req, buf),
|
||||
None => InResponse::Rejected,
|
||||
#[cfg(feature = "msos-descriptor")]
|
||||
(RequestType::Vendor, Recipient::Device) => {
|
||||
if !self.msos_descriptor.is_empty()
|
||||
&& req.request == self.msos_descriptor.vendor_code()
|
||||
&& req.index == 7
|
||||
{
|
||||
// Index 7 retrieves the MS OS Descriptor Set
|
||||
InResponse::Accepted(self.msos_descriptor.descriptor())
|
||||
} else {
|
||||
self.handle_control_in_delegated(req, buf)
|
||||
}
|
||||
}
|
||||
_ => InResponse::Rejected,
|
||||
_ => self.handle_control_in_delegated(req, buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_control_out_delegated(&mut self, req: Request, data: &[u8]) -> OutResponse {
|
||||
for h in &mut self.handlers {
|
||||
if let Some(res) = h.control_out(req, data) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
OutResponse::Rejected
|
||||
}
|
||||
|
||||
fn handle_control_in_delegated<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
|
||||
unsafe fn extend_lifetime<'x, 'y>(r: InResponse<'x>) -> InResponse<'y> {
|
||||
core::mem::transmute(r)
|
||||
}
|
||||
|
||||
for h in &mut self.handlers {
|
||||
if let Some(res) = h.control_in(req, buf) {
|
||||
// safety: the borrow checker isn't smart enough to know this pattern (returning a
|
||||
// borrowed value from inside the loop) is sound. Workaround by unsafely extending lifetime.
|
||||
// Also, Polonius (the WIP new borrow checker) does accept it.
|
||||
|
||||
return unsafe { extend_lifetime(res) };
|
||||
}
|
||||
}
|
||||
InResponse::Rejected
|
||||
}
|
||||
|
||||
fn handle_get_descriptor<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
|
||||
let (dtype, index) = req.descriptor_type_index();
|
||||
|
||||
@ -623,30 +708,16 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
STRING_INDEX_PRODUCT => self.config.product,
|
||||
STRING_INDEX_SERIAL_NUMBER => self.config.serial_number,
|
||||
_ => {
|
||||
// Find out which iface owns this string index.
|
||||
let mut index_left = index - STRING_INDEX_CUSTOM_START;
|
||||
let mut the_iface = None;
|
||||
for iface in &mut self.interfaces {
|
||||
if index_left < iface.num_strings {
|
||||
the_iface = Some(iface);
|
||||
let mut s = None;
|
||||
for handler in &mut self.handlers {
|
||||
let index = StringIndex::new(index);
|
||||
let lang_id = req.index;
|
||||
if let Some(res) = handler.get_string(index, lang_id) {
|
||||
s = Some(res);
|
||||
break;
|
||||
}
|
||||
index_left -= iface.num_strings;
|
||||
}
|
||||
|
||||
if let Some(iface) = the_iface {
|
||||
if let Some(handler) = &mut iface.handler {
|
||||
let index = StringIndex::new(index);
|
||||
let lang_id = req.index;
|
||||
handler.get_string(index, lang_id)
|
||||
} else {
|
||||
warn!("String requested to an interface with no handler.");
|
||||
None
|
||||
}
|
||||
} else {
|
||||
warn!("String requested but didn't match to an interface.");
|
||||
None
|
||||
}
|
||||
s
|
||||
}
|
||||
};
|
||||
|
||||
|
727
embassy-usb/src/msos.rs
Normal file
727
embassy-usb/src/msos.rs
Normal file
@ -0,0 +1,727 @@
|
||||
#![cfg(feature = "msos-descriptor")]
|
||||
|
||||
//! Microsoft OS Descriptors
|
||||
//!
|
||||
//! <https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-2-0-descriptors-specification>
|
||||
|
||||
use core::mem::size_of;
|
||||
|
||||
use super::{capability_type, BosWriter};
|
||||
use crate::types::InterfaceNumber;
|
||||
|
||||
/// A serialized Microsoft OS 2.0 Descriptor set.
|
||||
///
|
||||
/// Create with [`DeviceDescriptorSetBuilder`].
|
||||
pub struct MsOsDescriptorSet<'d> {
|
||||
descriptor: &'d [u8],
|
||||
vendor_code: u8,
|
||||
}
|
||||
|
||||
impl<'d> MsOsDescriptorSet<'d> {
|
||||
/// Gets the raw bytes of the MS OS descriptor
|
||||
pub fn descriptor(&self) -> &[u8] {
|
||||
self.descriptor
|
||||
}
|
||||
|
||||
/// Gets the vendor code used by the host to retrieve the MS OS descriptor
|
||||
pub fn vendor_code(&self) -> u8 {
|
||||
self.vendor_code
|
||||
}
|
||||
|
||||
/// Returns `true` if no MS OS descriptor data is available
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.descriptor.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a Microsoft OS 2.0 Descriptor set into a buffer.
|
||||
pub struct MsOsDescriptorWriter<'d> {
|
||||
buf: &'d mut [u8],
|
||||
|
||||
position: usize,
|
||||
config_mark: Option<usize>,
|
||||
function_mark: Option<usize>,
|
||||
vendor_code: u8,
|
||||
}
|
||||
|
||||
impl<'d> MsOsDescriptorWriter<'d> {
|
||||
pub(crate) fn new(buf: &'d mut [u8]) -> Self {
|
||||
MsOsDescriptorWriter {
|
||||
buf,
|
||||
position: 0,
|
||||
config_mark: None,
|
||||
function_mark: None,
|
||||
vendor_code: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(mut self, bos: &mut BosWriter) -> MsOsDescriptorSet<'d> {
|
||||
self.end();
|
||||
|
||||
if self.is_empty() {
|
||||
MsOsDescriptorSet {
|
||||
descriptor: &[],
|
||||
vendor_code: 0,
|
||||
}
|
||||
} else {
|
||||
self.write_bos(bos);
|
||||
MsOsDescriptorSet {
|
||||
descriptor: &self.buf[..self.position],
|
||||
vendor_code: self.vendor_code,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the MS OS descriptor header has not yet been written
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.position == 0
|
||||
}
|
||||
|
||||
/// Returns `true` if a configuration subset header has been started
|
||||
pub fn is_in_config_subset(&self) -> bool {
|
||||
self.config_mark.is_some()
|
||||
}
|
||||
|
||||
/// Returns `true` if a function subset header has been started and not yet ended
|
||||
pub fn is_in_function_subset(&self) -> bool {
|
||||
self.function_mark.is_some()
|
||||
}
|
||||
|
||||
/// Write the MS OS descriptor set header.
|
||||
///
|
||||
/// - `windows_version` is an NTDDI version constant that describes a windows version. See the [`windows_version`]
|
||||
/// module.
|
||||
/// - `vendor_code` is the vendor request code used to read the MS OS descriptor set.
|
||||
pub fn header(&mut self, windows_version: u32, vendor_code: u8) {
|
||||
assert!(self.is_empty(), "You can only call MsOsDescriptorWriter::header once");
|
||||
self.write(DescriptorSetHeader::new(windows_version));
|
||||
self.vendor_code = vendor_code;
|
||||
}
|
||||
|
||||
/// Add a device level feature descriptor.
|
||||
///
|
||||
/// Note that some feature descriptors may only be used at the device level in non-composite devices.
|
||||
/// Those features must be written before the first call to [`Self::configuration`].
|
||||
pub fn device_feature<T: DeviceLevelDescriptor>(&mut self, desc: T) {
|
||||
assert!(
|
||||
!self.is_empty(),
|
||||
"device features may only be added after the header is written"
|
||||
);
|
||||
assert!(
|
||||
self.config_mark.is_none(),
|
||||
"device features must be added before the first configuration subset"
|
||||
);
|
||||
self.write(desc);
|
||||
}
|
||||
|
||||
/// Add a configuration subset.
|
||||
pub fn configuration(&mut self, config: u8) {
|
||||
assert!(
|
||||
!self.is_empty(),
|
||||
"MsOsDescriptorWriter: configuration must be called after header"
|
||||
);
|
||||
Self::end_subset::<ConfigurationSubsetHeader>(self.buf, self.position, &mut self.config_mark);
|
||||
self.config_mark = Some(self.position);
|
||||
self.write(ConfigurationSubsetHeader::new(config));
|
||||
}
|
||||
|
||||
/// Add a function subset.
|
||||
pub fn function(&mut self, first_interface: InterfaceNumber) {
|
||||
assert!(
|
||||
self.config_mark.is_some(),
|
||||
"MsOsDescriptorWriter: function subset requires a configuration subset"
|
||||
);
|
||||
self.end_function();
|
||||
self.function_mark = Some(self.position);
|
||||
self.write(FunctionSubsetHeader::new(first_interface));
|
||||
}
|
||||
|
||||
/// Add a function level feature descriptor.
|
||||
///
|
||||
/// Note that some features may only be used at the function level. Those features must be written after a call
|
||||
/// to [`Self::function`].
|
||||
pub fn function_feature<T: FunctionLevelDescriptor>(&mut self, desc: T) {
|
||||
assert!(
|
||||
self.function_mark.is_some(),
|
||||
"function features may only be added to a function subset"
|
||||
);
|
||||
self.write(desc);
|
||||
}
|
||||
|
||||
/// Ends the current function subset (if any)
|
||||
pub fn end_function(&mut self) {
|
||||
Self::end_subset::<FunctionSubsetHeader>(self.buf, self.position, &mut self.function_mark);
|
||||
}
|
||||
|
||||
fn write<T: Descriptor>(&mut self, desc: T) {
|
||||
desc.write_to(&mut self.buf[self.position..]);
|
||||
self.position += desc.size();
|
||||
}
|
||||
|
||||
fn end_subset<T: DescriptorSet>(buf: &mut [u8], position: usize, mark: &mut Option<usize>) {
|
||||
if let Some(mark) = mark.take() {
|
||||
let len = position - mark;
|
||||
let p = mark + T::LENGTH_OFFSET;
|
||||
buf[p..(p + 2)].copy_from_slice(&(len as u16).to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
fn end(&mut self) {
|
||||
if self.position > 0 {
|
||||
Self::end_subset::<FunctionSubsetHeader>(self.buf, self.position, &mut self.function_mark);
|
||||
Self::end_subset::<ConfigurationSubsetHeader>(self.buf, self.position, &mut self.config_mark);
|
||||
Self::end_subset::<DescriptorSetHeader>(self.buf, self.position, &mut Some(0));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_bos(&mut self, bos: &mut BosWriter) {
|
||||
let windows_version = &self.buf[4..8];
|
||||
let len = (self.position as u16).to_le_bytes();
|
||||
bos.capability(
|
||||
capability_type::PLATFORM,
|
||||
&[
|
||||
0, // reserved
|
||||
// platform capability UUID, Microsoft OS 2.0 platform compabitility
|
||||
0xdf,
|
||||
0x60,
|
||||
0xdd,
|
||||
0xd8,
|
||||
0x89,
|
||||
0x45,
|
||||
0xc7,
|
||||
0x4c,
|
||||
0x9c,
|
||||
0xd2,
|
||||
0x65,
|
||||
0x9d,
|
||||
0x9e,
|
||||
0x64,
|
||||
0x8a,
|
||||
0x9f,
|
||||
// Minimum compatible Windows version
|
||||
windows_version[0],
|
||||
windows_version[1],
|
||||
windows_version[2],
|
||||
windows_version[3],
|
||||
// Descriptor set length
|
||||
len[0],
|
||||
len[1],
|
||||
self.vendor_code,
|
||||
0x0, // Device does not support alternate enumeration
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Microsoft Windows version codes
|
||||
///
|
||||
/// Windows 8.1 is the minimum version allowed for MS OS 2.0 descriptors.
|
||||
pub mod windows_version {
|
||||
/// Windows 8.1 (aka `NTDDI_WINBLUE`)
|
||||
pub const WIN8_1: u32 = 0x06030000;
|
||||
/// Windows 10
|
||||
pub const WIN10: u32 = 0x0A000000;
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
use core::mem::size_of;
|
||||
|
||||
/// A trait for descriptors
|
||||
pub trait Descriptor: Sized {
|
||||
const TYPE: super::DescriptorType;
|
||||
|
||||
/// The size of the descriptor's header.
|
||||
fn size(&self) -> usize {
|
||||
size_of::<Self>()
|
||||
}
|
||||
|
||||
fn write_to(&self, buf: &mut [u8]);
|
||||
}
|
||||
|
||||
pub trait DescriptorSet: Descriptor {
|
||||
const LENGTH_OFFSET: usize;
|
||||
}
|
||||
}
|
||||
|
||||
use sealed::*;
|
||||
|
||||
/// Copies the data of `t` into `buf`.
|
||||
///
|
||||
/// # Safety
|
||||
/// The type `T` must be able to be safely cast to `&[u8]`. (e.g. it is a `#[repr(packed)]` struct)
|
||||
unsafe fn transmute_write_to<T: Sized>(t: &T, buf: &mut [u8]) {
|
||||
let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::<T>());
|
||||
assert!(buf.len() >= bytes.len(), "MS OS descriptor buffer full");
|
||||
(&mut buf[..bytes.len()]).copy_from_slice(bytes);
|
||||
}
|
||||
|
||||
/// Table 9. Microsoft OS 2.0 descriptor wDescriptorType values.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DescriptorType {
|
||||
/// MS OS descriptor set header
|
||||
SetHeaderDescriptor = 0,
|
||||
/// Configuration subset header
|
||||
SubsetHeaderConfiguration = 1,
|
||||
/// Function subset header
|
||||
SubsetHeaderFunction = 2,
|
||||
/// Compatible device ID feature descriptor
|
||||
FeatureCompatibleId = 3,
|
||||
/// Registry property feature descriptor
|
||||
FeatureRegProperty = 4,
|
||||
/// Minimum USB resume time feature descriptor
|
||||
FeatureMinResumeTime = 5,
|
||||
/// Vendor revision feature descriptor
|
||||
FeatureModelId = 6,
|
||||
/// CCGP device descriptor feature descriptor
|
||||
FeatureCcgpDevice = 7,
|
||||
/// Vendor revision feature descriptor
|
||||
FeatureVendorRevision = 8,
|
||||
}
|
||||
|
||||
/// Table 5. Descriptor set information structure.
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed(1))]
|
||||
pub struct DescriptorSetInformation {
|
||||
dwWindowsVersion: u32,
|
||||
wMSOSDescriptorSetTotalLength: u16,
|
||||
bMS_VendorCode: u8,
|
||||
bAltEnumCode: u8,
|
||||
}
|
||||
|
||||
/// Table 4. Microsoft OS 2.0 platform capability descriptor header.
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed(1))]
|
||||
pub struct PlatformDescriptor {
|
||||
bLength: u8,
|
||||
bDescriptorType: u8,
|
||||
bDevCapabilityType: u8,
|
||||
bReserved: u8,
|
||||
platformCapabilityUUID: [u8; 16],
|
||||
descriptor_set_information: DescriptorSetInformation,
|
||||
}
|
||||
|
||||
/// Table 10. Microsoft OS 2.0 descriptor set header.
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed(1))]
|
||||
pub struct DescriptorSetHeader {
|
||||
wLength: u16,
|
||||
wDescriptorType: u16,
|
||||
dwWindowsVersion: u32,
|
||||
wTotalLength: u16,
|
||||
}
|
||||
|
||||
impl DescriptorSetHeader {
|
||||
/// Creates a MS OS descriptor set header.
|
||||
///
|
||||
/// `windows_version` is the minimum Windows version the descriptor set can apply to.
|
||||
pub fn new(windows_version: u32) -> Self {
|
||||
DescriptorSetHeader {
|
||||
wLength: (size_of::<Self>() as u16).to_le(),
|
||||
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||
dwWindowsVersion: windows_version.to_le(),
|
||||
wTotalLength: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Descriptor for DescriptorSetHeader {
|
||||
const TYPE: DescriptorType = DescriptorType::SetHeaderDescriptor;
|
||||
fn write_to(&self, buf: &mut [u8]) {
|
||||
unsafe { transmute_write_to(self, buf) }
|
||||
}
|
||||
}
|
||||
|
||||
impl DescriptorSet for DescriptorSetHeader {
|
||||
const LENGTH_OFFSET: usize = 8;
|
||||
}
|
||||
|
||||
/// Table 11. Configuration subset header.
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed(1))]
|
||||
pub struct ConfigurationSubsetHeader {
|
||||
wLength: u16,
|
||||
wDescriptorType: u16,
|
||||
bConfigurationValue: u8,
|
||||
bReserved: u8,
|
||||
wTotalLength: u16,
|
||||
}
|
||||
|
||||
impl ConfigurationSubsetHeader {
|
||||
/// Creates a configuration subset header
|
||||
pub fn new(config: u8) -> Self {
|
||||
ConfigurationSubsetHeader {
|
||||
wLength: (size_of::<Self>() as u16).to_le(),
|
||||
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||
bConfigurationValue: config,
|
||||
bReserved: 0,
|
||||
wTotalLength: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Descriptor for ConfigurationSubsetHeader {
|
||||
const TYPE: DescriptorType = DescriptorType::SubsetHeaderConfiguration;
|
||||
fn write_to(&self, buf: &mut [u8]) {
|
||||
unsafe { transmute_write_to(self, buf) }
|
||||
}
|
||||
}
|
||||
|
||||
impl DescriptorSet for ConfigurationSubsetHeader {
|
||||
const LENGTH_OFFSET: usize = 6;
|
||||
}
|
||||
|
||||
/// Table 12. Function subset header.
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed(1))]
|
||||
pub struct FunctionSubsetHeader {
|
||||
wLength: u16,
|
||||
wDescriptorType: u16,
|
||||
bFirstInterface: InterfaceNumber,
|
||||
bReserved: u8,
|
||||
wSubsetLength: u16,
|
||||
}
|
||||
|
||||
impl FunctionSubsetHeader {
|
||||
/// Creates a function subset header
|
||||
pub fn new(first_interface: InterfaceNumber) -> Self {
|
||||
FunctionSubsetHeader {
|
||||
wLength: (size_of::<Self>() as u16).to_le(),
|
||||
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||
bFirstInterface: first_interface,
|
||||
bReserved: 0,
|
||||
wSubsetLength: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Descriptor for FunctionSubsetHeader {
|
||||
const TYPE: DescriptorType = DescriptorType::SubsetHeaderFunction;
|
||||
fn write_to(&self, buf: &mut [u8]) {
|
||||
unsafe { transmute_write_to(self, buf) }
|
||||
}
|
||||
}
|
||||
|
||||
impl DescriptorSet for FunctionSubsetHeader {
|
||||
const LENGTH_OFFSET: usize = 6;
|
||||
}
|
||||
|
||||
// Feature Descriptors
|
||||
|
||||
/// A marker trait for feature descriptors that are valid at the device level.
|
||||
pub trait DeviceLevelDescriptor: Descriptor {}
|
||||
|
||||
/// A marker trait for feature descriptors that are valid at the function level.
|
||||
pub trait FunctionLevelDescriptor: Descriptor {}
|
||||
|
||||
/// Table 13. Microsoft OS 2.0 compatible ID descriptor.
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed(1))]
|
||||
pub struct CompatibleIdFeatureDescriptor {
|
||||
wLength: u16,
|
||||
wDescriptorType: u16,
|
||||
compatibleId: [u8; 8],
|
||||
subCompatibleId: [u8; 8],
|
||||
}
|
||||
|
||||
impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {}
|
||||
impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor {}
|
||||
|
||||
impl Descriptor for CompatibleIdFeatureDescriptor {
|
||||
const TYPE: DescriptorType = DescriptorType::FeatureCompatibleId;
|
||||
fn write_to(&self, buf: &mut [u8]) {
|
||||
unsafe { transmute_write_to(self, buf) }
|
||||
}
|
||||
}
|
||||
|
||||
impl CompatibleIdFeatureDescriptor {
|
||||
/// Creates a compatible ID feature descriptor
|
||||
///
|
||||
/// The ids must be 8 ASCII bytes or fewer.
|
||||
pub fn new(compatible_id: &str, sub_compatible_id: &str) -> Self {
|
||||
assert!(compatible_id.len() <= 8 && sub_compatible_id.len() <= 8);
|
||||
let mut cid = [0u8; 8];
|
||||
(&mut cid[..compatible_id.len()]).copy_from_slice(compatible_id.as_bytes());
|
||||
let mut scid = [0u8; 8];
|
||||
(&mut scid[..sub_compatible_id.len()]).copy_from_slice(sub_compatible_id.as_bytes());
|
||||
Self::new_raw(cid, scid)
|
||||
}
|
||||
|
||||
fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self {
|
||||
Self {
|
||||
wLength: (size_of::<Self>() as u16).to_le(),
|
||||
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||
compatibleId: compatible_id,
|
||||
subCompatibleId: sub_compatible_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Table 14. Microsoft OS 2.0 registry property descriptor
|
||||
#[allow(non_snake_case)]
|
||||
pub struct RegistryPropertyFeatureDescriptor<'a> {
|
||||
name: &'a str,
|
||||
data: PropertyData<'a>,
|
||||
}
|
||||
|
||||
/// Data values that can be encoded into a registry property descriptor
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PropertyData<'a> {
|
||||
/// A registry property containing a string.
|
||||
Sz(&'a str),
|
||||
/// A registry property containing a string that expands environment variables.
|
||||
ExpandSz(&'a str),
|
||||
/// A registry property containing binary data.
|
||||
Binary(&'a [u8]),
|
||||
/// A registry property containing a little-endian 32-bit integer.
|
||||
DwordLittleEndian(u32),
|
||||
/// A registry property containing a big-endian 32-bit integer.
|
||||
DwordBigEndian(u32),
|
||||
/// A registry property containing a string that contains a symbolic link.
|
||||
Link(&'a str),
|
||||
/// A registry property containing multiple strings.
|
||||
RegMultiSz(&'a [&'a str]),
|
||||
}
|
||||
|
||||
fn write_bytes(val: &[u8], buf: &mut [u8]) -> usize {
|
||||
assert!(buf.len() >= val.len());
|
||||
buf[..val.len()].copy_from_slice(val);
|
||||
val.len()
|
||||
}
|
||||
|
||||
fn write_utf16(val: &str, buf: &mut [u8]) -> usize {
|
||||
let mut pos = 0;
|
||||
for c in val.encode_utf16() {
|
||||
pos += write_bytes(&c.to_le_bytes(), &mut buf[pos..]);
|
||||
}
|
||||
pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..])
|
||||
}
|
||||
|
||||
impl<'a> PropertyData<'a> {
|
||||
/// Gets the `PropertyDataType` for this property value
|
||||
pub fn kind(&self) -> PropertyDataType {
|
||||
match self {
|
||||
PropertyData::Sz(_) => PropertyDataType::Sz,
|
||||
PropertyData::ExpandSz(_) => PropertyDataType::ExpandSz,
|
||||
PropertyData::Binary(_) => PropertyDataType::Binary,
|
||||
PropertyData::DwordLittleEndian(_) => PropertyDataType::DwordLittleEndian,
|
||||
PropertyData::DwordBigEndian(_) => PropertyDataType::DwordBigEndian,
|
||||
PropertyData::Link(_) => PropertyDataType::Link,
|
||||
PropertyData::RegMultiSz(_) => PropertyDataType::RegMultiSz,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the size (in bytes) of this property value when encoded.
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => {
|
||||
core::mem::size_of::<u16>() * (val.encode_utf16().count() + 1)
|
||||
}
|
||||
PropertyData::Binary(val) => val.len(),
|
||||
PropertyData::DwordLittleEndian(val) | PropertyData::DwordBigEndian(val) => core::mem::size_of_val(val),
|
||||
PropertyData::RegMultiSz(val) => {
|
||||
core::mem::size_of::<u16>() * val.iter().map(|x| x.encode_utf16().count() + 1).sum::<usize>() + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes the data for this property value and writes it to `buf`.
|
||||
pub fn write(&self, buf: &mut [u8]) -> usize {
|
||||
match self {
|
||||
PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => write_utf16(val, buf),
|
||||
PropertyData::Binary(val) => write_bytes(val, buf),
|
||||
PropertyData::DwordLittleEndian(val) => write_bytes(&val.to_le_bytes(), buf),
|
||||
PropertyData::DwordBigEndian(val) => write_bytes(&val.to_be_bytes(), buf),
|
||||
PropertyData::RegMultiSz(val) => {
|
||||
let mut pos = 0;
|
||||
for s in *val {
|
||||
pos += write_utf16(s, &mut buf[pos..]);
|
||||
}
|
||||
pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Table 15. wPropertyDataType values for the Microsoft OS 2.0 registry property descriptor.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum PropertyDataType {
|
||||
/// A registry property containing a string.
|
||||
Sz = 1,
|
||||
/// A registry property containing a string that expands environment variables.
|
||||
ExpandSz = 2,
|
||||
/// A registry property containing binary data.
|
||||
Binary = 3,
|
||||
/// A registry property containing a little-endian 32-bit integer.
|
||||
DwordLittleEndian = 4,
|
||||
/// A registry property containing a big-endian 32-bit integer.
|
||||
DwordBigEndian = 5,
|
||||
/// A registry property containing a string that contains a symbolic link.
|
||||
Link = 6,
|
||||
/// A registry property containing multiple strings.
|
||||
RegMultiSz = 7,
|
||||
}
|
||||
|
||||
impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {}
|
||||
impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {}
|
||||
|
||||
impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> {
|
||||
const TYPE: DescriptorType = DescriptorType::FeatureRegProperty;
|
||||
|
||||
fn size(&self) -> usize {
|
||||
10 + self.name_size() + self.data.size()
|
||||
}
|
||||
|
||||
fn write_to(&self, buf: &mut [u8]) {
|
||||
assert!(buf.len() >= self.size(), "MS OS descriptor buffer full");
|
||||
|
||||
let mut pos = 0;
|
||||
pos += write_bytes(&(self.size() as u16).to_le_bytes(), &mut buf[pos..]);
|
||||
pos += write_bytes(&(Self::TYPE as u16).to_le_bytes(), &mut buf[pos..]);
|
||||
pos += write_bytes(&(self.data.kind() as u16).to_le_bytes(), &mut buf[pos..]);
|
||||
pos += write_bytes(&(self.name_size() as u16).to_le_bytes(), &mut buf[pos..]);
|
||||
pos += write_utf16(self.name, &mut buf[pos..]);
|
||||
pos += write_bytes(&(self.data.size() as u16).to_le_bytes(), &mut buf[pos..]);
|
||||
self.data.write(&mut buf[pos..]);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RegistryPropertyFeatureDescriptor<'a> {
|
||||
/// A registry property.
|
||||
pub fn new(name: &'a str, data: PropertyData<'a>) -> Self {
|
||||
Self { name, data }
|
||||
}
|
||||
|
||||
fn name_size(&self) -> usize {
|
||||
core::mem::size_of::<u16>() * (self.name.encode_utf16().count() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Table 16. Microsoft OS 2.0 minimum USB recovery time descriptor.
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed(1))]
|
||||
pub struct MinimumRecoveryTimeDescriptor {
|
||||
wLength: u16,
|
||||
wDescriptorType: u16,
|
||||
bResumeRecoveryTime: u8,
|
||||
bResumeSignalingTime: u8,
|
||||
}
|
||||
|
||||
impl DeviceLevelDescriptor for MinimumRecoveryTimeDescriptor {}
|
||||
|
||||
impl Descriptor for MinimumRecoveryTimeDescriptor {
|
||||
const TYPE: DescriptorType = DescriptorType::FeatureMinResumeTime;
|
||||
fn write_to(&self, buf: &mut [u8]) {
|
||||
unsafe { transmute_write_to(self, buf) }
|
||||
}
|
||||
}
|
||||
|
||||
impl MinimumRecoveryTimeDescriptor {
|
||||
/// Times are in milliseconds.
|
||||
///
|
||||
/// `resume_recovery_time` must be >= 0 and <= 10.
|
||||
/// `resume_signaling_time` must be >= 1 and <= 20.
|
||||
pub fn new(resume_recovery_time: u8, resume_signaling_time: u8) -> Self {
|
||||
assert!(resume_recovery_time <= 10);
|
||||
assert!(resume_signaling_time >= 1 && resume_signaling_time <= 20);
|
||||
Self {
|
||||
wLength: (size_of::<Self>() as u16).to_le(),
|
||||
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||
bResumeRecoveryTime: resume_recovery_time,
|
||||
bResumeSignalingTime: resume_signaling_time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Table 17. Microsoft OS 2.0 model ID descriptor.
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed(1))]
|
||||
pub struct ModelIdDescriptor {
|
||||
wLength: u16,
|
||||
wDescriptorType: u16,
|
||||
modelId: [u8; 16],
|
||||
}
|
||||
|
||||
impl DeviceLevelDescriptor for ModelIdDescriptor {}
|
||||
|
||||
impl Descriptor for ModelIdDescriptor {
|
||||
const TYPE: DescriptorType = DescriptorType::FeatureModelId;
|
||||
fn write_to(&self, buf: &mut [u8]) {
|
||||
unsafe { transmute_write_to(self, buf) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ModelIdDescriptor {
|
||||
/// Creates a new model ID descriptor
|
||||
///
|
||||
/// `model_id` should be a uuid that uniquely identifies a physical device.
|
||||
pub fn new(model_id: u128) -> Self {
|
||||
Self {
|
||||
wLength: (size_of::<Self>() as u16).to_le(),
|
||||
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||
modelId: model_id.to_le_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Table 18. Microsoft OS 2.0 CCGP device descriptor.
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed(1))]
|
||||
pub struct CcgpDeviceDescriptor {
|
||||
wLength: u16,
|
||||
wDescriptorType: u16,
|
||||
}
|
||||
|
||||
impl DeviceLevelDescriptor for CcgpDeviceDescriptor {}
|
||||
|
||||
impl Descriptor for CcgpDeviceDescriptor {
|
||||
const TYPE: DescriptorType = DescriptorType::FeatureCcgpDevice;
|
||||
fn write_to(&self, buf: &mut [u8]) {
|
||||
unsafe { transmute_write_to(self, buf) }
|
||||
}
|
||||
}
|
||||
|
||||
impl CcgpDeviceDescriptor {
|
||||
/// Creates a new CCGP device descriptor
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
wLength: (size_of::<Self>() as u16).to_le(),
|
||||
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Table 19. Microsoft OS 2.0 vendor revision descriptor.
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed(1))]
|
||||
pub struct VendorRevisionDescriptor {
|
||||
wLength: u16,
|
||||
wDescriptorType: u16,
|
||||
/// Revision number associated with the descriptor set. Modify it every time you add/modify a registry property or
|
||||
/// other MS OS descriptor. Shell set to greater than or equal to 1.
|
||||
VendorRevision: u16,
|
||||
}
|
||||
|
||||
impl DeviceLevelDescriptor for VendorRevisionDescriptor {}
|
||||
impl FunctionLevelDescriptor for VendorRevisionDescriptor {}
|
||||
|
||||
impl Descriptor for VendorRevisionDescriptor {
|
||||
const TYPE: DescriptorType = DescriptorType::FeatureVendorRevision;
|
||||
fn write_to(&self, buf: &mut [u8]) {
|
||||
unsafe { transmute_write_to(self, buf) }
|
||||
}
|
||||
}
|
||||
|
||||
impl VendorRevisionDescriptor {
|
||||
/// Creates a new vendor revision descriptor
|
||||
pub fn new(revision: u16) -> Self {
|
||||
assert!(revision >= 1);
|
||||
Self {
|
||||
wLength: (size_of::<Self>() as u16).to_le(),
|
||||
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||
VendorRevision: revision.to_le(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
//! USB types.
|
||||
|
||||
/// A handle for a USB interface that contains its number.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct InterfaceNumber(pub(crate) u8);
|
||||
#[repr(transparent)]
|
||||
pub struct InterfaceNumber(pub u8);
|
||||
|
||||
impl InterfaceNumber {
|
||||
pub(crate) fn new(index: u8) -> InterfaceNumber {
|
||||
@ -18,7 +21,8 @@ impl From<InterfaceNumber> for u8 {
|
||||
/// A handle for a USB string descriptor that contains its index.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct StringIndex(u8);
|
||||
#[repr(transparent)]
|
||||
pub struct StringIndex(pub u8);
|
||||
|
||||
impl StringIndex {
|
||||
pub(crate) fn new(index: u8) -> StringIndex {
|
||||
|
Reference in New Issue
Block a user