Skip to main content

cotton_usb_host/
async_pool.rs

1use crate::bitset::BitSet;
2use core::cell::{Cell, RefCell};
3use core::future::Future;
4use core::pin::Pin;
5use core::task::{Context, Poll, Waker};
6#[cfg(feature = "std")]
7use std::fmt::{self, Display};
8
9/// Managing access to N equivalent resources
10///
11/// Callers who wish to access a resource (but don't care which one of the N)
12/// can call the async function [`Pool::alloc`] which will return (awakening
13/// the task) as soon as a resource is available.
14///
15/// Once the returned resource (represented as a [`Pooled`]) is
16/// finished with, the caller can then drop it (i.e., let it go out of
17/// scope) and it will be returned to the pool for another user.
18///
19/// Not quite the same as async_semaphore, because callers need to know
20/// *which* of the N devices they were allotted.
21///
22/// # Example
23/// ```rust
24/// use cotton_usb_host::async_pool::Pool;
25/// let mut pool = Pool::new(2); // this pool has two resources
26/// let res = pool.try_alloc().unwrap(); // obtain a resource
27/// println!("I got resource {}", res.which());
28/// {
29///     let res2 = pool.try_alloc().unwrap(); // obtain a resource
30///     println!("I got resource {}", res.which());
31///     let res3 = pool.try_alloc();
32///     assert!(res3.is_none());   // oh dear, no resources available
33///     // But now res2 goes out of scope (i.e., back into the pool)
34/// }
35/// let res4 = pool.try_alloc().unwrap();
36/// println!("I got resource {}", res.which()); // success!
37/// ```
38///
39/// For a larger example, see how the RP2040 USB host-controller driver
40/// shares out its USB endpoints.
41pub struct Pool {
42    total: u8,
43    allocated: Cell<BitSet>,
44    waker: RefCell<Option<Waker>>,
45}
46
47/// Representing ownership of one of the resources in a [`Pool`]
48pub struct Pooled<'a> {
49    n: u8,
50    pool: &'a Pool,
51}
52
53impl Pooled<'_> {
54    /// Returns which one of the [`Pool`]'s N resources is owned by this `Pooled`
55    pub fn which(&self) -> u8 {
56        self.n
57    }
58}
59
60#[cfg(feature = "std")]
61impl Display for Pooled<'_> {
62    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63        write!(f, "Pooled({})", self.n)
64    }
65}
66
67#[cfg(feature = "defmt")]
68impl defmt::Format for Pooled<'_> {
69    fn format(&self, f: defmt::Formatter) {
70        defmt::write!(f, "Pooled({})", self.n);
71    }
72}
73
74impl Drop for Pooled<'_> {
75    fn drop(&mut self) {
76        self.pool.dealloc_internal(self.n);
77    }
78}
79
80struct PoolFuture<'a> {
81    pool: &'a Pool,
82}
83
84impl<'a> Future for PoolFuture<'a> {
85    type Output = Pooled<'a>;
86
87    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
88        self.pool.waker.replace(Some(cx.waker().clone()));
89
90        if let Some(n) = self.pool.alloc_internal() {
91            Poll::Ready(Pooled { n, pool: self.pool })
92        } else {
93            Poll::Pending
94        }
95    }
96}
97
98impl Pool {
99    /// Create a new Pool, sharing out a number of equivalent resources
100    ///
101    /// # Parameters
102    /// - `total`: The number of resources (must be 0-32)
103    ///
104    /// # Panics
105    /// Will panic if `total`>32.
106    pub const fn new(total: u8) -> Self {
107        assert!(total <= 32);
108        Self {
109            total,
110            allocated: Cell::new(BitSet::new()),
111            waker: RefCell::new(None),
112        }
113    }
114
115    fn alloc_internal(&self) -> Option<u8> {
116        let mut bits = self.allocated.get();
117        let n = bits.set_any()?;
118        if n >= self.total {
119            None
120        } else {
121            self.allocated.replace(bits);
122            Some(n)
123        }
124    }
125
126    fn dealloc_internal(&self, n: u8) {
127        let mut bits = self.allocated.get();
128        debug_assert!(bits.contains(n));
129        bits.clear(n);
130        self.allocated.replace(bits);
131
132        if let Some(w) = self.waker.take() {
133            w.wake();
134        }
135    }
136
137    /// Obtain one of the resources
138    ///
139    /// This asynchronous function will return immediately if any of
140    /// the resources is currently idle (unused). Otherwise, it will
141    /// wait until a resource is available. Once it has returned, the
142    /// caller has ownership of the resource (represented by ownership
143    /// of the [`Pooled`] object) until the `Pooled` is dropped -- for instance,
144    /// at the end of a scope.
145    ///
146    /// It is not unsafe or unsound (in the Rust sense) to keep hold
147    /// of a `Pooled` indefinitely, nor to `mem::forget` it -- but it is
148    /// inadvisable, as this constitutes a denial-of-service against
149    /// other potential resource users.
150    ///
151    /// # See also
152    /// [`Pool::try_alloc()`] for a synchronous version
153    pub async fn alloc(&self) -> Pooled<'_> {
154        let fut = PoolFuture { pool: self };
155        fut.await
156    }
157
158    /// Obtain a resource if one is immediately available
159    ///
160    /// Returns `Some` if any of the resources is currently idle (unused).
161    /// Otherwise, returns `None`.
162    ///
163    /// # See also
164    /// [`Pool::alloc()`] for an asynchronous version
165    pub fn try_alloc(&self) -> Option<Pooled<'_>> {
166        Some(Pooled {
167            n: self.alloc_internal()?,
168            pool: self,
169        })
170    }
171}
172
173#[cfg(all(test, feature = "std"))]
174#[path = "tests/async_pool.rs"]
175mod tests;