Skip to content

Commit

Permalink
usb: unify ControlHandler+DeviceStateHandler, route all control reque…
Browse files Browse the repository at this point in the history
…sts to all handlers.

- Allows classes to handle vendor requests.
- Allows classes to use a single handler for multiple interfaces.
- Allows classes to access the other events (previously only `reset` was available).
  • Loading branch information
Dirbaio committed Feb 7, 2023
1 parent 1d841cc commit 3af991a
Show file tree
Hide file tree
Showing 31 changed files with 380 additions and 337 deletions.
1 change: 0 additions & 1 deletion embassy-usb-logger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ impl<const N: usize> UsbLogger<N> {
&mut state.config_descriptor,
&mut state.bos_descriptor,
&mut state.control_buf,
None,
);

// Create classes on the builder.
Expand Down
9 changes: 9 additions & 0 deletions embassy-usb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ 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]
Expand Down
1 change: 1 addition & 0 deletions embassy-usb/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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
];

Expand Down
1 change: 1 addition & 0 deletions embassy-usb/gen_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def feature(name, default, min, max, pow2=None):


feature("max_interface_count", default=4, min=1, max=8)
feature("max_handler_count", default=4, min=1, max=8)

# ========= Update Cargo.toml

Expand Down
45 changes: 27 additions & 18 deletions embassy-usb/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,12 +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))]
Expand Down Expand Up @@ -122,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,
Expand Down Expand Up @@ -151,7 +151,6 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
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
Expand Down Expand Up @@ -179,9 +178,9 @@ 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,

Expand All @@ -205,7 +204,7 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
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(),
Expand Down Expand Up @@ -248,6 +247,26 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
}
}

/// 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.
///
Expand Down Expand Up @@ -301,10 +320,8 @@ 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() {
Expand Down Expand Up @@ -350,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.
Expand Down
50 changes: 34 additions & 16 deletions embassy-usb/src/class/cdc_acm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ 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;
Expand Down Expand Up @@ -67,6 +67,7 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> {
}

struct Control<'a> {
comm_if: InterfaceNumber,
shared: &'a ControlShared,
}

Expand Down Expand Up @@ -98,20 +99,26 @@ 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()));
shared.dtr.store(false, Ordering::Relaxed);
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 {
Expand All @@ -123,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;
Expand All @@ -134,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 => {
Expand All @@ -151,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),
}
}
}
Expand All @@ -162,17 +175,12 @@ 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, None);
Expand Down Expand Up @@ -213,6 +221,16 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
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,
Expand Down
Loading

0 comments on commit 3af991a

Please sign in to comment.