usb: add add_class to builder, so that FooBarClass::new(&mut builder) can set up everything.

This commit is contained in:
Dario Nieuwenhuis 2022-03-28 02:20:01 +02:00
parent a062baae38
commit a2f5763a67
5 changed files with 110 additions and 131 deletions

View File

@ -10,3 +10,4 @@ defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
cortex-m = "0.7.3"
num-traits = { version = "0.2.14", default-features = false }
heapless = "0.7.10"

View File

@ -1,8 +1,11 @@
use heapless::Vec;
use super::class::UsbClass;
use super::descriptor::{BosWriter, DescriptorWriter};
use super::driver::{Driver, EndpointAllocError};
use super::types::*;
use super::UsbDevice;
use super::MAX_CLASS_COUNT;
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -116,6 +119,7 @@ impl<'a> Config<'a> {
/// Used to build new [`UsbDevice`]s.
pub struct UsbDeviceBuilder<'d, D: Driver<'d>> {
config: Config<'d>,
classes: Vec<&'d mut dyn UsbClass, MAX_CLASS_COUNT>,
bus: D,
next_interface_number: u8,
@ -165,6 +169,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> {
UsbDeviceBuilder {
bus,
config,
classes: Vec::new(),
next_interface_number: 0,
next_string_index: 4,
@ -175,7 +180,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> {
}
/// Creates the [`UsbDevice`] instance with the configuration in this builder.
pub fn build(mut self, classes: &'d mut [&'d mut dyn UsbClass]) -> UsbDevice<'d, D> {
pub fn build(mut self) -> UsbDevice<'d, D> {
self.config_descriptor.end_configuration();
self.bos_descriptor.end_bos();
@ -185,10 +190,16 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> {
self.device_descriptor.into_buf(),
self.config_descriptor.into_buf(),
self.bos_descriptor.writer.into_buf(),
classes,
self.classes,
)
}
pub fn add_class(&mut self, class: &'d mut dyn UsbClass) {
if self.classes.push(class).is_err() {
panic!("max class count reached")
}
}
/// Allocates a new interface number.
pub fn alloc_interface(&mut self) -> InterfaceNumber {
let number = self.next_interface_number;

View File

@ -14,6 +14,7 @@ pub mod types;
mod util;
use class::ControlInRequestStatus;
use heapless::Vec;
use self::class::{RequestStatus, UsbClass};
use self::control::*;
@ -53,6 +54,8 @@ pub const CONFIGURATION_VALUE: u8 = 1;
/// The default value for bAlternateSetting for all interfaces.
pub const DEFAULT_ALTERNATE_SETTING: u8 = 0;
pub const MAX_CLASS_COUNT: usize = 4;
pub struct UsbDevice<'d, D: Driver<'d>> {
bus: D::Bus,
control: D::ControlPipe,
@ -67,7 +70,7 @@ pub struct UsbDevice<'d, D: Driver<'d>> {
self_powered: bool,
pending_address: u8,
classes: &'d mut [&'d mut dyn UsbClass],
classes: Vec<&'d mut dyn UsbClass, MAX_CLASS_COUNT>,
}
impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
@ -77,7 +80,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
device_descriptor: &'d [u8],
config_descriptor: &'d [u8],
bos_descriptor: &'d [u8],
classes: &'d mut [&'d mut dyn UsbClass],
classes: Vec<&'d mut dyn UsbClass, MAX_CLASS_COUNT>,
) -> Self {
let control = driver
.alloc_control_pipe(config.max_packet_size_0 as u16)

View File

@ -1,5 +1,8 @@
use core::mem;
use core::cell::{Cell, UnsafeCell};
use core::mem::{self, MaybeUninit};
use core::sync::atomic::{AtomicBool, Ordering};
use defmt::info;
use embassy::blocking_mutex::CriticalSectionMutex;
use embassy_usb::class::{ControlInRequestStatus, RequestStatus, UsbClass};
use embassy_usb::control::{self, Request};
use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError};
@ -25,6 +28,18 @@ const REQ_SET_LINE_CODING: u8 = 0x20;
const REQ_GET_LINE_CODING: u8 = 0x21;
const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
pub struct State {
control: MaybeUninit<Control>,
}
impl State {
pub fn new() -> Self {
Self {
control: MaybeUninit::uninit(),
}
}
}
/// Packet level implementation of a CDC-ACM serial port.
///
/// This class can be used directly and it has the least overhead due to directly reading and
@ -45,21 +60,32 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> {
pub data_if: InterfaceNumber,
pub read_ep: D::EndpointOut,
pub write_ep: D::EndpointIn,
pub control: CdcAcmControl,
control: &'d ControlShared,
}
pub struct CdcAcmControl {
pub comm_if: InterfaceNumber,
pub line_coding: LineCoding,
pub dtr: bool,
pub rts: bool,
struct Control {
comm_if: InterfaceNumber,
shared: UnsafeCell<ControlShared>,
}
impl UsbClass for CdcAcmControl {
/// Shared data between Control and CdcAcmClass
struct ControlShared {
line_coding: CriticalSectionMutex<Cell<LineCoding>>,
dtr: AtomicBool,
rts: AtomicBool,
}
impl Control {
fn shared(&mut self) -> &ControlShared {
unsafe { &*(self.shared.get() as *const _) }
}
}
impl UsbClass for Control {
fn reset(&mut self) {
self.line_coding = LineCoding::default();
self.dtr = false;
self.rts = false;
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]) -> RequestStatus {
@ -77,18 +103,25 @@ impl UsbClass for CdcAcmControl {
RequestStatus::Accepted
}
REQ_SET_LINE_CODING if data.len() >= 7 => {
self.line_coding.data_rate = u32::from_le_bytes(data[0..4].try_into().unwrap());
self.line_coding.stop_bits = data[4].into();
self.line_coding.parity_type = data[5].into();
self.line_coding.data_bits = data[6];
info!("Set line coding to: {:?}", self.line_coding);
let coding = LineCoding {
data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()),
stop_bits: data[4].into(),
parity_type: data[5].into(),
data_bits: data[6],
};
self.shared().line_coding.lock(|x| x.set(coding));
info!("Set line coding to: {:?}", coding);
RequestStatus::Accepted
}
REQ_SET_CONTROL_LINE_STATE => {
self.dtr = (req.value & 0x0001) != 0;
self.rts = (req.value & 0x0002) != 0;
info!("Set dtr {}, rts {}", self.dtr, self.rts);
let dtr = (req.value & 0x0001) != 0;
let rts = (req.value & 0x0002) != 0;
let shared = self.shared();
shared.dtr.store(dtr, Ordering::Relaxed);
shared.rts.store(rts, Ordering::Relaxed);
info!("Set dtr {}, rts {}", dtr, rts);
RequestStatus::Accepted
}
@ -112,11 +145,12 @@ impl UsbClass for CdcAcmControl {
// REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
REQ_GET_LINE_CODING if req.length == 7 => {
info!("Sending line coding");
let coding = self.shared().line_coding.lock(|x| x.get());
let mut data = [0; 7];
data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes());
data[4] = self.line_coding.stop_bits as u8;
data[5] = self.line_coding.parity_type as u8;
data[6] = self.line_coding.data_bits;
data[0..4].copy_from_slice(&coding.data_rate.to_le_bytes());
data[4] = coding.stop_bits as u8;
data[5] = coding.parity_type as u8;
data[6] = coding.data_bits;
control.accept(&data)
}
_ => control.reject(),
@ -127,7 +161,11 @@ impl UsbClass for CdcAcmControl {
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 UsbDeviceBuilder<'d, D>, max_packet_size: u16) -> Self {
pub fn new(
builder: &mut UsbDeviceBuilder<'d, D>,
state: &'d mut State,
max_packet_size: u16,
) -> Self {
let comm_if = builder.alloc_interface();
let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255);
let data_if = builder.alloc_interface();
@ -207,22 +245,29 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
builder.config_descriptor.endpoint(write_ep.info()).unwrap();
builder.config_descriptor.endpoint(read_ep.info()).unwrap();
let control = state.control.write(Control {
comm_if,
shared: UnsafeCell::new(ControlShared {
dtr: AtomicBool::new(false),
rts: AtomicBool::new(false),
line_coding: CriticalSectionMutex::new(Cell::new(LineCoding {
stop_bits: StopBits::One,
data_bits: 8,
parity_type: ParityType::None,
data_rate: 8_000,
})),
}),
});
let control_shared = unsafe { &*(control.shared.get() as *const _) };
builder.add_class(control);
CdcAcmClass {
comm_ep,
data_if,
read_ep,
write_ep,
control: CdcAcmControl {
comm_if,
dtr: false,
rts: false,
line_coding: LineCoding {
stop_bits: StopBits::One,
data_bits: 8,
parity_type: ParityType::None,
data_rate: 8_000,
},
},
control: control_shared,
}
}
@ -234,18 +279,18 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
/// Gets the current line coding. The line coding contains information that's mainly relevant
/// for USB to UART serial port emulators, and can be ignored if not relevant.
pub fn line_coding(&self) -> &LineCoding {
&self.control.line_coding
pub fn line_coding(&self) -> LineCoding {
self.control.line_coding.lock(|x| x.get())
}
/// Gets the DTR (data terminal ready) state
pub fn dtr(&self) -> bool {
self.control.dtr
self.control.dtr.load(Ordering::Relaxed)
}
/// Gets the RTS (request to send) state
pub fn rts(&self) -> bool {
self.control.rts
self.control.rts.load(Ordering::Relaxed)
}
/// Writes a single packet into the IN endpoint.
@ -264,88 +309,6 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
}
}
/*
impl<B: UsbBus> UsbClass<B> for CdcAcmClass<'_, B> {
fn get_configuration_descriptors(&self, builder.config_descriptor: &mut Descriptorbuilder.config_descriptor) -> Result<()> {
Ok(())
}
fn reset(&mut self) {
self.line_coding = LineCoding::default();
self.dtr = false;
self.rts = false;
}
fn control_in(&mut self, xfer: ControlIn<B>) {
let req = xfer.request();
if !(req.request_type == control::RequestType::Class
&& req.recipient == control::Recipient::Interface
&& req.index == u8::from(self.comm_if) as u16)
{
return;
}
match req.request {
// REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
REQ_GET_LINE_CODING if req.length == 7 => {
xfer.accept(|data| {
data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes());
data[4] = self.line_coding.stop_bits as u8;
data[5] = self.line_coding.parity_type as u8;
data[6] = self.line_coding.data_bits;
Ok(7)
})
.ok();
}
_ => {
xfer.reject().ok();
}
}
}
fn control_out(&mut self, xfer: ControlOut<B>) {
let req = xfer.request();
if !(req.request_type == control::RequestType::Class
&& req.recipient == control::Recipient::Interface
&& req.index == u8::from(self.comm_if) as u16)
{
return;
}
match req.request {
REQ_SEND_ENCAPSULATED_COMMAND => {
// We don't actually support encapsulated commands but pretend we do for standards
// compatibility.
xfer.accept().ok();
}
REQ_SET_LINE_CODING if xfer.data().len() >= 7 => {
self.line_coding.data_rate =
u32::from_le_bytes(xfer.data()[0..4].try_into().unwrap());
self.line_coding.stop_bits = xfer.data()[4].into();
self.line_coding.parity_type = xfer.data()[5].into();
self.line_coding.data_bits = xfer.data()[6];
xfer.accept().ok();
}
REQ_SET_CONTROL_LINE_STATE => {
self.dtr = (req.value & 0x0001) != 0;
self.rts = (req.value & 0x0002) != 0;
xfer.accept().ok();
}
_ => {
xfer.reject().ok();
}
};
}
}
*/
/// Number of stop bits for LineCoding
#[derive(Copy, Clone, PartialEq, Eq, defmt::Format)]
pub enum StopBits {
@ -393,7 +356,7 @@ impl From<u8> for ParityType {
///
/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can
/// be ignored if you don't plan to interface with a physical UART.
#[derive(defmt::Format)]
#[derive(Clone, Copy, defmt::Format)]
pub struct LineCoding {
stop_bits: StopBits,
data_bits: u8,

View File

@ -16,12 +16,11 @@ use embassy_nrf::interrupt;
use embassy_nrf::pac;
use embassy_nrf::usb::Driver;
use embassy_nrf::Peripherals;
use embassy_usb::class::UsbClass;
use embassy_usb::driver::{EndpointIn, EndpointOut};
use embassy_usb::{Config, UsbDeviceBuilder};
use futures::future::join3;
use crate::cdc_acm::CdcAcmClass;
use crate::cdc_acm::{CdcAcmClass, State};
#[embassy::main]
async fn main(_spawner: Spawner, p: Peripherals) {
@ -48,6 +47,9 @@ async fn main(_spawner: Spawner, p: Peripherals) {
let mut device_descriptor = [0; 256];
let mut config_descriptor = [0; 256];
let mut bos_descriptor = [0; 256];
let mut state = State::new();
let mut builder = UsbDeviceBuilder::new(
driver,
config,
@ -57,11 +59,10 @@ async fn main(_spawner: Spawner, p: Peripherals) {
);
// Create classes on the builder.
let mut class = CdcAcmClass::new(&mut builder, 64);
let mut class = CdcAcmClass::new(&mut builder, &mut state, 64);
// Build the builder.
let mut classes: [&mut dyn UsbClass; 1] = [&mut class.control];
let mut usb = builder.build(&mut classes);
let mut usb = builder.build();
// Run the USB device.
let fut1 = usb.run();