diff --git a/Cargo.toml b/Cargo.toml index d582a0f..3d5fbb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,12 @@ rusb = "0.8.0" rand = "0.6.1" [features] +# Enable support for using this library across multiple threads. +# +# Made optional / opt-in for compatibility with AVR targets +# which are single-core and do not have atomic operations. +sync = [] + # Use a 256 byte buffer for control transfers instead of 128. control-buffer-256 = [] diff --git a/src/bus.rs b/src/bus.rs index cd176d3..90fdc50 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -3,8 +3,83 @@ use crate::{Result, UsbDirection, UsbError}; use core::cell::RefCell; use core::mem; use core::ptr; + +#[cfg(feature = "sync")] use core::sync::atomic::{AtomicPtr, Ordering}; +#[cfg(not(feature = "sync"))] +use core::cell::Cell; + +pub(crate) struct BusPtr { + #[cfg(feature = "sync")] + inner: AtomicPtr, + + #[cfg(not(feature = "sync"))] + inner: Cell<*mut B>, +} + +impl BusPtr { + fn new() -> Self { + Self { + #[cfg(feature = "sync")] + inner: AtomicPtr::new(ptr::null_mut()), + + #[cfg(not(feature = "sync"))] + inner: Cell::new(ptr::null_mut()), + } + } + + fn set(&self, ptr: *mut B) { + #[cfg(feature = "sync")] + { + self.inner.store(ptr, Ordering::SeqCst); + } + #[cfg(not(feature = "sync"))] + { + self.inner.set(ptr); + } + } + + pub(crate) fn get(&self) -> *mut B { + #[cfg(feature = "sync")] + { + self.inner.load(Ordering::SeqCst) + } + + #[cfg(not(feature = "sync"))] + { + self.inner.get() + } + } +} + +/// A trait with a `Sync` bound if the `sync` feature is enabled. +/// +/// This is necessary because bounds can't be made conditional based on feature flags. Otherwise, +/// the definition of `UsbBus` could be simpler, like: +/// +/// ```compile_fail +/// pub trait UsbBus: #[cfg(feature = "sync")] Sync + Sized { +/// /* ... */ +/// } +/// ``` +// +// Another alternative would be to duplicate the `UsbBus` definition, one with the `Sync` bound and +// one without, and conditionally select them based on the `sync` feature. But that's not feasible +// to maintain by hand, because of how complex the `UsbBus` trait is. However, it may be possible +// to do with a macro to automatically generate both definitions from one body. +#[cfg(feature = "sync")] +pub trait ConditionalSync: Sync {} + +#[cfg(feature = "sync")] +impl ConditionalSync for T {} + +#[cfg(not(feature = "sync"))] +pub trait ConditionalSync {} + +#[cfg(not(feature = "sync"))] +impl ConditionalSync for T {} + /// A trait for device-specific USB peripherals. Implement this to add support for a new hardware /// platform. /// @@ -14,7 +89,7 @@ use core::sync::atomic::{AtomicPtr, Ordering}; /// take place before [`enable`](UsbBus::enable) is called. After the bus is enabled, in practice /// most access won't mutate the object itself but only endpoint-specific registers and buffers, the /// access to which is mostly arbitrated by endpoint handles. -pub trait UsbBus: Sync + Sized { +pub trait UsbBus: ConditionalSync + Sized { /// Allocates an endpoint and specified endpoint parameters. This method is called by the device /// and class implementations to allocate endpoints, and can only be called before /// [`enable`](UsbBus::enable) is called. @@ -148,7 +223,7 @@ struct AllocatorState { /// Helper type used for UsbBus resource allocation and initialization. pub struct UsbBusAllocator { bus: RefCell, - bus_ptr: AtomicPtr, + bus_ptr: BusPtr, state: RefCell, } @@ -158,7 +233,7 @@ impl UsbBusAllocator { pub fn new(bus: B) -> UsbBusAllocator { UsbBusAllocator { bus: RefCell::new(bus), - bus_ptr: AtomicPtr::new(ptr::null_mut()), + bus_ptr: BusPtr::new(), state: RefCell::new(AllocatorState { next_interface_number: 0, next_string_index: 4, @@ -179,7 +254,7 @@ impl UsbBusAllocator { // in the RefCell. let mut bus_ref = self.bus.borrow_mut(); let bus_ptr_v = &mut *bus_ref as *mut B; - self.bus_ptr.store(bus_ptr_v, Ordering::SeqCst); + self.bus_ptr.set(bus_ptr_v); // And then leave the RefCell borrowed permanently so that it cannot be borrowed mutably // anymore. diff --git a/src/endpoint.rs b/src/endpoint.rs index 520bccb..f004342 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -1,8 +1,7 @@ -use crate::bus::UsbBus; +use crate::bus::{BusPtr, UsbBus}; use crate::{Result, UsbDirection}; use core::marker::PhantomData; use core::ptr; -use core::sync::atomic::{AtomicPtr, Ordering}; /// Trait for endpoint type markers. pub trait EndpointDirection { @@ -49,7 +48,7 @@ pub enum EndpointType { /// Handle for a USB endpoint. The endpoint direction is constrained by the `D` type argument, which /// must be either `In` or `Out`. pub struct Endpoint<'a, B: UsbBus, D: EndpointDirection> { - bus_ptr: &'a AtomicPtr, + bus_ptr: &'a BusPtr, address: EndpointAddress, ep_type: EndpointType, max_packet_size: u16, @@ -59,7 +58,7 @@ pub struct Endpoint<'a, B: UsbBus, D: EndpointDirection> { impl Endpoint<'_, B, D> { pub(crate) fn new<'a>( - bus_ptr: &'a AtomicPtr, + bus_ptr: &'a BusPtr, address: EndpointAddress, ep_type: EndpointType, max_packet_size: u16, @@ -76,7 +75,7 @@ impl Endpoint<'_, B, D> { } fn bus(&self) -> &B { - let bus_ptr = self.bus_ptr.load(Ordering::SeqCst); + let bus_ptr = self.bus_ptr.get(); if bus_ptr == ptr::null_mut() { panic!("UsbBus initialization not complete"); } diff --git a/src/lib.rs b/src/lib.rs index d7744c9..706de98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -192,6 +192,10 @@ pub mod class_prelude { pub use crate::UsbError; } +#[cfg(all(feature = "sync", target_arch = "avr"))] +compile_error!("`sync` feature is not supported on AVR targets."); + +#[cfg(feature = "sync")] fn _ensure_sync() { use crate::bus::{PollResult, UsbBus, UsbBusAllocator}; use crate::class_prelude::*;