aboutsummaryrefslogtreecommitdiff
path: root/embassy-extras/src
diff options
context:
space:
mode:
authorLiam Murphy <[email protected]>2021-07-05 17:42:43 +1000
committerLiam Murphy <[email protected]>2021-07-05 17:42:43 +1000
commit744e2cbb8a0b58eccf3a9708c8c28f1ef17e4771 (patch)
treee288df991575c86e5079bdb33b7c1fd7888e96cb /embassy-extras/src
parented83b93b6dd34cf09d1f772ec32ebd036e8798a7 (diff)
extras: Fix UB in `Peripheral`
`Peripheral` assumed that interrupts can't be preempted, when they can be preempted by higher priority interrupts. So I put the interrupt handler inside a critical section, and also added checks for whether the state had been dropped before the critical section was entered. I also added a `'static` bound to `PeripheralState`, since `Pin` only guarantees that the memory it directly references will not be invalidated. It doesn't guarantee that memory its pointee references also won't be invalidated. There were already some implementations of `PeripheralState` that weren't `'static`, though, so I added an unsafe `PeripheralStateUnchecked` trait and forwarded the `unsafe` to the constructors of the implementors.
Diffstat (limited to 'embassy-extras/src')
-rw-r--r--embassy-extras/src/peripheral.rs50
-rw-r--r--embassy-extras/src/peripheral_shared.rs45
-rw-r--r--embassy-extras/src/usb/mod.rs13
3 files changed, 82 insertions, 26 deletions
diff --git a/embassy-extras/src/peripheral.rs b/embassy-extras/src/peripheral.rs
index 68972c543..e3e06d692 100644
--- a/embassy-extras/src/peripheral.rs
+++ b/embassy-extras/src/peripheral.rs
@@ -1,15 +1,38 @@
1use core::cell::UnsafeCell; 1use core::cell::UnsafeCell;
2use core::marker::{PhantomData, PhantomPinned}; 2use core::marker::{PhantomData, PhantomPinned};
3use core::pin::Pin; 3use core::pin::Pin;
4use core::ptr;
4 5
5use embassy::interrupt::{Interrupt, InterruptExt}; 6use embassy::interrupt::{Interrupt, InterruptExt};
6 7
7pub trait PeripheralState { 8/// # Safety
9/// When types implementing this trait are used with `Peripheral` or `PeripheralMutex`,
10/// their lifetime must not end without first calling `Drop` on the `Peripheral` or `PeripheralMutex`.
11pub unsafe trait PeripheralStateUnchecked {
8 type Interrupt: Interrupt; 12 type Interrupt: Interrupt;
9 fn on_interrupt(&mut self); 13 fn on_interrupt(&mut self);
10} 14}
11 15
12pub struct PeripheralMutex<S: PeripheralState> { 16// `PeripheralMutex` is safe because `Pin` guarantees that the memory it references will not be invalidated or reused
17// without calling `Drop`. However, it provides no guarantees about references contained within the state still being valid,
18// so this `'static` bound is necessary.
19pub trait PeripheralState: 'static {
20 type Interrupt: Interrupt;
21 fn on_interrupt(&mut self);
22}
23
24// SAFETY: `T` has to live for `'static` to implement `PeripheralState`, thus its lifetime cannot end.
25unsafe impl<T> PeripheralStateUnchecked for T
26where
27 T: PeripheralState,
28{
29 type Interrupt = T::Interrupt;
30 fn on_interrupt(&mut self) {
31 self.on_interrupt()
32 }
33}
34
35pub struct PeripheralMutex<S: PeripheralStateUnchecked> {
13 state: UnsafeCell<S>, 36 state: UnsafeCell<S>,
14 37
15 irq_setup_done: bool, 38 irq_setup_done: bool,
@@ -19,7 +42,7 @@ pub struct PeripheralMutex<S: PeripheralState> {
19 _pinned: PhantomPinned, 42 _pinned: PhantomPinned,
20} 43}
21 44
22impl<S: PeripheralState> PeripheralMutex<S> { 45impl<S: PeripheralStateUnchecked> PeripheralMutex<S> {
23 pub fn new(state: S, irq: S::Interrupt) -> Self { 46 pub fn new(state: S, irq: S::Interrupt) -> Self {
24 Self { 47 Self {
25 irq, 48 irq,
@@ -39,11 +62,17 @@ impl<S: PeripheralState> PeripheralMutex<S> {
39 62
40 this.irq.disable(); 63 this.irq.disable();
41 this.irq.set_handler(|p| { 64 this.irq.set_handler(|p| {
42 // Safety: it's OK to get a &mut to the state, since 65 critical_section::with(|_| {
43 // - We're in the IRQ, no one else can't preempt us 66 if p.is_null() {
44 // - We can't have preempted a with() call because the irq is disabled during it. 67 // The state was dropped, so we can't operate on it.
45 let state = unsafe { &mut *(p as *mut S) }; 68 return;
46 state.on_interrupt(); 69 }
70 // Safety: it's OK to get a &mut to the state, since
71 // - We're in a critical section, no one can preempt us (and call with())
72 // - We can't have preempted a with() call because the irq is disabled during it.
73 let state = unsafe { &mut *(p as *mut S) };
74 state.on_interrupt();
75 })
47 }); 76 });
48 this.irq 77 this.irq
49 .set_handler_context((&mut this.state) as *mut _ as *mut ()); 78 .set_handler_context((&mut this.state) as *mut _ as *mut ());
@@ -67,9 +96,12 @@ impl<S: PeripheralState> PeripheralMutex<S> {
67 } 96 }
68} 97}
69 98
70impl<S: PeripheralState> Drop for PeripheralMutex<S> { 99impl<S: PeripheralStateUnchecked> Drop for PeripheralMutex<S> {
71 fn drop(&mut self) { 100 fn drop(&mut self) {
72 self.irq.disable(); 101 self.irq.disable();
73 self.irq.remove_handler(); 102 self.irq.remove_handler();
103 // Set the context to null so that the interrupt will know we're dropped
104 // if we pre-empted it before it entered a critical section.
105 self.irq.set_handler_context(ptr::null_mut());
74 } 106 }
75} 107}
diff --git a/embassy-extras/src/peripheral_shared.rs b/embassy-extras/src/peripheral_shared.rs
index c62113396..a9fca8ca6 100644
--- a/embassy-extras/src/peripheral_shared.rs
+++ b/embassy-extras/src/peripheral_shared.rs
@@ -1,16 +1,27 @@
1use core::cell::UnsafeCell;
2use core::marker::{PhantomData, PhantomPinned}; 1use core::marker::{PhantomData, PhantomPinned};
3use core::pin::Pin; 2use core::pin::Pin;
3use core::ptr;
4 4
5use embassy::interrupt::{Interrupt, InterruptExt}; 5use embassy::interrupt::{Interrupt, InterruptExt};
6 6
7pub trait PeripheralState { 7/// # Safety
8/// When types implementing this trait are used with `Peripheral` or `PeripheralMutex`,
9/// their lifetime must not end without first calling `Drop` on the `Peripheral` or `PeripheralMutex`.
10pub unsafe trait PeripheralStateUnchecked {
8 type Interrupt: Interrupt; 11 type Interrupt: Interrupt;
9 fn on_interrupt(&self); 12 fn on_interrupt(&self);
10} 13}
11 14
12pub struct Peripheral<S: PeripheralState> { 15// `Peripheral` is safe because `Pin` guarantees that the memory it references will not be invalidated or reused
13 state: UnsafeCell<S>, 16// without calling `Drop`. However, it provides no guarantees about references contained within the state still being valid,
17// so this `'static` bound is necessary.
18pub trait PeripheralState: 'static {
19 type Interrupt: Interrupt;
20 fn on_interrupt(&self);
21}
22
23pub struct Peripheral<S: PeripheralStateUnchecked> {
24 state: S,
14 25
15 irq_setup_done: bool, 26 irq_setup_done: bool,
16 irq: S::Interrupt, 27 irq: S::Interrupt,
@@ -19,13 +30,13 @@ pub struct Peripheral<S: PeripheralState> {
19 _pinned: PhantomPinned, 30 _pinned: PhantomPinned,
20} 31}
21 32
22impl<S: PeripheralState> Peripheral<S> { 33impl<S: PeripheralStateUnchecked> Peripheral<S> {
23 pub fn new(irq: S::Interrupt, state: S) -> Self { 34 pub fn new(irq: S::Interrupt, state: S) -> Self {
24 Self { 35 Self {
25 irq, 36 irq,
26 irq_setup_done: false, 37 irq_setup_done: false,
27 38
28 state: UnsafeCell::new(state), 39 state,
29 _not_send: PhantomData, 40 _not_send: PhantomData,
30 _pinned: PhantomPinned, 41 _pinned: PhantomPinned,
31 } 42 }
@@ -39,8 +50,16 @@ impl<S: PeripheralState> Peripheral<S> {
39 50
40 this.irq.disable(); 51 this.irq.disable();
41 this.irq.set_handler(|p| { 52 this.irq.set_handler(|p| {
42 let state = unsafe { &*(p as *const S) }; 53 // We need to be in a critical section so that no one can preempt us
43 state.on_interrupt(); 54 // and drop the state after we check whether `p.is_null()`.
55 critical_section::with(|_| {
56 if p.is_null() {
57 // The state was dropped, so we can't operate on it.
58 return;
59 }
60 let state = unsafe { &*(p as *const S) };
61 state.on_interrupt();
62 });
44 }); 63 });
45 this.irq 64 this.irq
46 .set_handler_context((&this.state) as *const _ as *mut ()); 65 .set_handler_context((&this.state) as *const _ as *mut ());
@@ -49,15 +68,17 @@ impl<S: PeripheralState> Peripheral<S> {
49 this.irq_setup_done = true; 68 this.irq_setup_done = true;
50 } 69 }
51 70
52 pub fn state(self: Pin<&mut Self>) -> &S { 71 pub fn state<'a>(self: Pin<&'a mut Self>) -> &'a S {
53 let this = unsafe { self.get_unchecked_mut() }; 72 &self.into_ref().get_ref().state
54 unsafe { &*this.state.get() }
55 } 73 }
56} 74}
57 75
58impl<S: PeripheralState> Drop for Peripheral<S> { 76impl<S: PeripheralStateUnchecked> Drop for Peripheral<S> {
59 fn drop(&mut self) { 77 fn drop(&mut self) {
60 self.irq.disable(); 78 self.irq.disable();
61 self.irq.remove_handler(); 79 self.irq.remove_handler();
80 // Set the context to null so that the interrupt will know we're dropped
81 // if we pre-empted it before it entered a critical section.
82 self.irq.set_handler_context(ptr::null_mut());
62 } 83 }
63} 84}
diff --git a/embassy-extras/src/usb/mod.rs b/embassy-extras/src/usb/mod.rs
index 182cd87d0..330eb9220 100644
--- a/embassy-extras/src/usb/mod.rs
+++ b/embassy-extras/src/usb/mod.rs
@@ -9,7 +9,7 @@ use usb_device::device::UsbDevice;
9mod cdc_acm; 9mod cdc_acm;
10pub mod usb_serial; 10pub mod usb_serial;
11 11
12use crate::peripheral::{PeripheralMutex, PeripheralState}; 12use crate::peripheral::{PeripheralMutex, PeripheralStateUnchecked};
13use embassy::interrupt::Interrupt; 13use embassy::interrupt::Interrupt;
14use usb_serial::{ReadInterface, UsbSerial, WriteInterface}; 14use usb_serial::{ReadInterface, UsbSerial, WriteInterface};
15 15
@@ -55,10 +55,12 @@ where
55 } 55 }
56 } 56 }
57 57
58 pub fn start(self: Pin<&mut Self>) { 58 /// # Safety
59 let this = unsafe { self.get_unchecked_mut() }; 59 /// The `UsbDevice` passed to `Self::new` must not be dropped without calling `Drop` on this `Usb` first.
60 pub unsafe fn start(self: Pin<&mut Self>) {
61 let this = self.get_unchecked_mut();
60 let mut mutex = this.inner.borrow_mut(); 62 let mut mutex = this.inner.borrow_mut();
61 let mutex = unsafe { Pin::new_unchecked(&mut *mutex) }; 63 let mutex = Pin::new_unchecked(&mut *mutex);
62 64
63 // Use inner to register the irq 65 // Use inner to register the irq
64 mutex.register_interrupt(); 66 mutex.register_interrupt();
@@ -125,7 +127,8 @@ where
125 } 127 }
126} 128}
127 129
128impl<'bus, B, T, I> PeripheralState for State<'bus, B, T, I> 130// SAFETY: The safety contract of `PeripheralStateUnchecked` is forwarded to `Usb::start`.
131unsafe impl<'bus, B, T, I> PeripheralStateUnchecked for State<'bus, B, T, I>
129where 132where
130 B: UsbBus, 133 B: UsbBus,
131 T: ClassSet<B>, 134 T: ClassSet<B>,