aboutsummaryrefslogtreecommitdiff
path: root/embassy-stm32/src/dma/ringbuffer
diff options
context:
space:
mode:
authorAlexandros Liarokapis <[email protected]>2024-09-15 18:47:33 +0300
committerAlexandros Liarokapis <[email protected]>2024-10-15 12:29:12 +0300
commit2b10caafd488987b6cf9a2cd69820c81d0a0826e (patch)
treeef5eac794f99033898b9580623d86422b055f2d7 /embassy-stm32/src/dma/ringbuffer
parent4f08d5bc5ff0f765198665b1f37b6372f43b8567 (diff)
stm32: initial support for alternative ringbuffer implementation
Diffstat (limited to 'embassy-stm32/src/dma/ringbuffer')
-rw-r--r--embassy-stm32/src/dma/ringbuffer/mod.rs293
-rw-r--r--embassy-stm32/src/dma/ringbuffer/tests/mod.rs165
-rw-r--r--embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs50
-rw-r--r--embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs122
-rw-r--r--embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs121
5 files changed, 751 insertions, 0 deletions
diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs
new file mode 100644
index 000000000..10a9ff975
--- /dev/null
+++ b/embassy-stm32/src/dma/ringbuffer/mod.rs
@@ -0,0 +1,293 @@
1#![cfg_attr(gpdma, allow(unused))]
2
3use core::future::poll_fn;
4use core::task::{Poll, Waker};
5
6use crate::dma::word::Word;
7
8pub trait DmaCtrl {
9 /// Get the NDTR register value, i.e. the space left in the underlying
10 /// buffer until the dma writer wraps.
11 fn get_remaining_transfers(&self) -> usize;
12
13 /// Reset the transfer completed counter to 0 and return the value just prior to the reset.
14 fn reset_complete_count(&mut self) -> usize;
15
16 /// Set the waker for a running poll_fn
17 fn set_waker(&mut self, waker: &Waker);
18}
19
20#[derive(Debug, PartialEq)]
21#[cfg_attr(feature = "defmt", derive(defmt::Format))]
22pub struct OverrunError;
23
24#[derive(Debug, Clone, Copy, Default)]
25struct DmaIndex {
26 completion_count: usize,
27 pos: usize,
28}
29
30fn pos(cap: usize, dma: &impl DmaCtrl) -> usize {
31 cap - dma.get_remaining_transfers()
32}
33
34impl DmaIndex {
35 fn reset(&mut self) {
36 self.pos = 0;
37 self.completion_count = 0;
38 }
39
40 fn as_index(&self, cap: usize, offset: usize) -> usize {
41 (self.pos + offset) % cap
42 }
43
44 fn dma_sync(&mut self, cap: usize, dma: &mut impl DmaCtrl) {
45 let fst_pos = pos(cap, dma);
46 let fst_count = dma.reset_complete_count();
47 let pos = pos(cap, dma);
48
49 let wrap_count = if pos >= fst_pos {
50 fst_count
51 } else {
52 fst_count + dma.reset_complete_count()
53 };
54
55 self.pos = pos;
56 self.completion_count += wrap_count;
57 }
58
59 fn advance(&mut self, cap: usize, steps: usize) {
60 let next = self.pos + steps;
61 self.completion_count += next / cap;
62 self.pos = next % cap;
63 }
64
65 fn normalize(lhs: &mut DmaIndex, rhs: &mut DmaIndex) {
66 let min_count = lhs.completion_count.min(rhs.completion_count);
67 lhs.completion_count -= min_count;
68 rhs.completion_count -= min_count;
69 }
70
71 fn diff(&mut self, cap: usize, rhs: &mut DmaIndex) -> isize {
72 Self::normalize(self, rhs);
73 (self.completion_count * cap + self.pos) as isize - (rhs.completion_count * cap + rhs.pos) as isize
74 }
75}
76
77pub struct ReadableDmaRingBuffer<'a, W: Word> {
78 dma_buf: &'a mut [W],
79 write_index: DmaIndex,
80 read_index: DmaIndex,
81}
82
83impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> {
84 /// Construct an empty buffer.
85 pub fn new(dma_buf: &'a mut [W]) -> Self {
86 Self {
87 dma_buf,
88 write_index: Default::default(),
89 read_index: Default::default(),
90 }
91 }
92
93 /// Reset the ring buffer to its initial state
94 pub fn clear(&mut self, dma: &mut impl DmaCtrl) {
95 dma.reset_complete_count();
96 self.write_index.reset();
97 self.update_dma_index(dma);
98 self.read_index = self.write_index;
99 }
100
101 /// The capacity of the ringbuffer
102 pub const fn cap(&self) -> usize {
103 self.dma_buf.len()
104 }
105
106 /// Read elements from the ring buffer
107 /// Return a tuple of the length read and the length remaining in the buffer
108 /// If not all of the elements were read, then there will be some elements in the buffer remaining
109 /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read
110 /// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
111 pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> {
112 let readable = self.margin(dma)?.min(buf.len());
113 for i in 0..readable {
114 buf[i] = self.read_buf(i);
115 }
116 let available = self.margin(dma)?;
117 self.read_index.advance(self.cap(), readable);
118 Ok((readable, available - readable))
119 }
120
121 /// Read an exact number of elements from the ringbuffer.
122 ///
123 /// Returns the remaining number of elements available for immediate reading.
124 /// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
125 ///
126 /// Async/Wake Behavior:
127 /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point,
128 /// and when it wraps around. This means that when called with a buffer of length 'M', when this
129 /// ring buffer was created with a buffer of size 'N':
130 /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source.
131 /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning.
132 pub async fn read_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &mut [W]) -> Result<usize, OverrunError> {
133 let mut read_data = 0;
134 let buffer_len = buffer.len();
135
136 poll_fn(|cx| {
137 dma.set_waker(cx.waker());
138
139 match self.read(dma, &mut buffer[read_data..buffer_len]) {
140 Ok((len, remaining)) => {
141 read_data += len;
142 if read_data == buffer_len {
143 Poll::Ready(Ok(remaining))
144 } else {
145 Poll::Pending
146 }
147 }
148 Err(e) => Poll::Ready(Err(e)),
149 }
150 })
151 .await
152 }
153
154 fn update_dma_index(&mut self, dma: &mut impl DmaCtrl) {
155 self.write_index.dma_sync(self.cap(), dma)
156 }
157
158 fn read_buf(&self, offset: usize) -> W {
159 unsafe {
160 core::ptr::read_volatile(
161 self.dma_buf
162 .as_ptr()
163 .offset(self.read_index.as_index(self.cap(), offset) as isize),
164 )
165 }
166 }
167
168 /// Returns available dma samples
169 fn margin(&mut self, dma: &mut impl DmaCtrl) -> Result<usize, OverrunError> {
170 self.update_dma_index(dma);
171
172 let diff: usize = self
173 .write_index
174 .diff(self.cap(), &mut self.read_index)
175 .try_into()
176 .unwrap();
177
178 if diff > self.cap() {
179 Err(OverrunError)
180 } else {
181 Ok(diff)
182 }
183 }
184}
185
186pub struct WritableDmaRingBuffer<'a, W: Word> {
187 dma_buf: &'a mut [W],
188 read_index: DmaIndex,
189 write_index: DmaIndex,
190}
191
192impl<'a, W: Word> WritableDmaRingBuffer<'a, W> {
193 /// Construct a ringbuffer filled with the given buffer data.
194 pub fn new(dma_buf: &'a mut [W]) -> Self {
195 let len = dma_buf.len();
196 Self {
197 dma_buf,
198 read_index: Default::default(),
199 write_index: DmaIndex {
200 completion_count: 0,
201 pos: len,
202 },
203 }
204 }
205
206 /// Reset the ring buffer to its initial state. The buffer after the reset will be full.
207 pub fn clear(&mut self, dma: &mut impl DmaCtrl) {
208 dma.reset_complete_count();
209 self.read_index.reset();
210 self.update_dma_index(dma);
211 self.write_index = self.read_index;
212 self.write_index.advance(self.cap(), self.cap());
213 }
214
215 /// Get the capacity of the ringbuffer.
216 pub const fn cap(&self) -> usize {
217 self.dma_buf.len()
218 }
219
220 /// Append data to the ring buffer.
221 /// Returns a tuple of the data written and the remaining write capacity in the buffer.
222 pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> {
223 let writable = self.margin(dma)?.min(buf.len());
224 for i in 0..writable {
225 self.write_buf(i, buf[i]);
226 }
227 let available = self.margin(dma)?;
228 self.write_index.advance(self.cap(), writable);
229 Ok((writable, available - writable))
230 }
231
232 /// Write elements directly to the buffer.
233 pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> {
234 for (i, data) in buf.iter().enumerate() {
235 self.write_buf(i, *data)
236 }
237 let written = buf.len().min(self.cap());
238 Ok((written, self.cap() - written))
239 }
240
241 /// Write an exact number of elements to the ringbuffer.
242 pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result<usize, OverrunError> {
243 let mut written_data = 0;
244 let buffer_len = buffer.len();
245
246 poll_fn(|cx| {
247 dma.set_waker(cx.waker());
248
249 match self.write(dma, &buffer[written_data..buffer_len]) {
250 Ok((len, remaining)) => {
251 written_data += len;
252 if written_data == buffer_len {
253 Poll::Ready(Ok(remaining))
254 } else {
255 Poll::Pending
256 }
257 }
258 Err(e) => Poll::Ready(Err(e)),
259 }
260 })
261 .await
262 }
263
264 fn update_dma_index(&mut self, dma: &mut impl DmaCtrl) {
265 self.read_index.dma_sync(self.cap(), dma);
266 }
267
268 fn write_buf(&mut self, offset: usize, value: W) {
269 unsafe {
270 core::ptr::write_volatile(
271 self.dma_buf
272 .as_mut_ptr()
273 .offset(self.write_index.as_index(self.cap(), offset) as isize),
274 value,
275 )
276 }
277 }
278
279 fn margin(&mut self, dma: &mut impl DmaCtrl) -> Result<usize, OverrunError> {
280 self.update_dma_index(dma);
281
282 let diff = self.write_index.diff(self.cap(), &mut self.read_index);
283
284 if diff < 0 {
285 Err(OverrunError)
286 } else {
287 Ok(self.cap().saturating_sub(diff as usize))
288 }
289 }
290}
291
292#[cfg(test)]
293mod tests;
diff --git a/embassy-stm32/src/dma/ringbuffer/tests/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs
new file mode 100644
index 000000000..9768e1df8
--- /dev/null
+++ b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs
@@ -0,0 +1,165 @@
1use std::{cell, vec};
2
3use super::*;
4
5#[allow(dead_code)]
6#[derive(PartialEq, Debug)]
7enum TestCircularTransferRequest {
8 ResetCompleteCount(usize),
9 PositionRequest(usize),
10}
11
12struct TestCircularTransfer {
13 len: usize,
14 requests: cell::RefCell<vec::Vec<TestCircularTransferRequest>>,
15}
16
17impl DmaCtrl for TestCircularTransfer {
18 fn get_remaining_transfers(&self) -> usize {
19 match self.requests.borrow_mut().pop().unwrap() {
20 TestCircularTransferRequest::PositionRequest(pos) => {
21 let len = self.len;
22
23 assert!(len >= pos);
24
25 len - pos
26 }
27 _ => unreachable!(),
28 }
29 }
30
31 fn reset_complete_count(&mut self) -> usize {
32 match self.requests.get_mut().pop().unwrap() {
33 TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count,
34 _ => unreachable!(),
35 }
36 }
37
38 fn set_waker(&mut self, _waker: &Waker) {}
39}
40
41impl TestCircularTransfer {
42 pub fn new(len: usize) -> Self {
43 Self {
44 requests: cell::RefCell::new(vec![]),
45 len,
46 }
47 }
48
49 pub fn setup(&self, mut requests: vec::Vec<TestCircularTransferRequest>) {
50 requests.reverse();
51 self.requests.replace(requests);
52 }
53}
54
55const CAP: usize = 16;
56
57#[test]
58fn dma_index_dma_sync_syncs_position_to_last_read_if_sync_takes_place_on_same_dma_cycle() {
59 let mut dma = TestCircularTransfer::new(CAP);
60 dma.setup(vec![
61 TestCircularTransferRequest::PositionRequest(4),
62 TestCircularTransferRequest::ResetCompleteCount(0),
63 TestCircularTransferRequest::PositionRequest(7),
64 ]);
65 let mut index = DmaIndex::default();
66 index.dma_sync(CAP, &mut dma);
67 assert_eq!(index.completion_count, 0);
68 assert_eq!(index.pos, 7);
69}
70
71#[test]
72fn dma_index_dma_sync_updates_completion_count_properly_if_sync_takes_place_on_same_dma_cycle() {
73 let mut dma = TestCircularTransfer::new(CAP);
74 dma.setup(vec![
75 TestCircularTransferRequest::PositionRequest(4),
76 TestCircularTransferRequest::ResetCompleteCount(2),
77 TestCircularTransferRequest::PositionRequest(7),
78 ]);
79 let mut index = DmaIndex::default();
80 index.completion_count = 1;
81 index.dma_sync(CAP, &mut dma);
82 assert_eq!(index.completion_count, 3);
83 assert_eq!(index.pos, 7);
84}
85
86#[test]
87fn dma_index_dma_sync_syncs_to_last_position_if_reads_occur_on_different_dma_cycles() {
88 let mut dma = TestCircularTransfer::new(CAP);
89 dma.setup(vec![
90 TestCircularTransferRequest::PositionRequest(10),
91 TestCircularTransferRequest::ResetCompleteCount(1),
92 TestCircularTransferRequest::PositionRequest(5),
93 TestCircularTransferRequest::ResetCompleteCount(0),
94 ]);
95 let mut index = DmaIndex::default();
96 index.dma_sync(CAP, &mut dma);
97 assert_eq!(index.completion_count, 1);
98 assert_eq!(index.pos, 5);
99}
100
101#[test]
102fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_completion_count_occurs_on_first_cycle(
103) {
104 let mut dma = TestCircularTransfer::new(CAP);
105 dma.setup(vec![
106 TestCircularTransferRequest::PositionRequest(10),
107 TestCircularTransferRequest::ResetCompleteCount(1),
108 TestCircularTransferRequest::PositionRequest(5),
109 TestCircularTransferRequest::ResetCompleteCount(1),
110 ]);
111 let mut index = DmaIndex::default();
112 index.completion_count = 1;
113 index.dma_sync(CAP, &mut dma);
114 assert_eq!(index.completion_count, 3);
115 assert_eq!(index.pos, 5);
116}
117
118#[test]
119fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_completion_count_occurs_on_later_cycle(
120) {
121 let mut dma = TestCircularTransfer::new(CAP);
122 dma.setup(vec![
123 TestCircularTransferRequest::PositionRequest(10),
124 TestCircularTransferRequest::ResetCompleteCount(2),
125 TestCircularTransferRequest::PositionRequest(5),
126 TestCircularTransferRequest::ResetCompleteCount(0),
127 ]);
128 let mut index = DmaIndex::default();
129 index.completion_count = 1;
130 index.dma_sync(CAP, &mut dma);
131 assert_eq!(index.completion_count, 3);
132 assert_eq!(index.pos, 5);
133}
134
135#[test]
136fn dma_index_as_index_returns_index_mod_cap_by_default() {
137 let index = DmaIndex::default();
138 assert_eq!(index.as_index(CAP, 0), 0);
139 assert_eq!(index.as_index(CAP, 1), 1);
140 assert_eq!(index.as_index(CAP, 2), 2);
141 assert_eq!(index.as_index(CAP, 3), 3);
142 assert_eq!(index.as_index(CAP, 4), 4);
143 assert_eq!(index.as_index(CAP, CAP), 0);
144 assert_eq!(index.as_index(CAP, CAP + 1), 1);
145}
146
147#[test]
148fn dma_index_advancing_increases_as_index() {
149 let mut index = DmaIndex::default();
150 assert_eq!(index.as_index(CAP, 0), 0);
151 index.advance(CAP, 1);
152 assert_eq!(index.as_index(CAP, 0), 1);
153 index.advance(CAP, 1);
154 assert_eq!(index.as_index(CAP, 0), 2);
155 index.advance(CAP, 1);
156 assert_eq!(index.as_index(CAP, 0), 3);
157 index.advance(CAP, 1);
158 assert_eq!(index.as_index(CAP, 0), 4);
159 index.advance(CAP, CAP - 4);
160 assert_eq!(index.as_index(CAP, 0), 0);
161 index.advance(CAP, 1);
162 assert_eq!(index.as_index(CAP, 0), 1);
163}
164
165mod prop_test;
diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs
new file mode 100644
index 000000000..661fb1728
--- /dev/null
+++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs
@@ -0,0 +1,50 @@
1use std::task::Waker;
2
3use proptest::prop_oneof;
4use proptest::strategy::{self, BoxedStrategy, Strategy as _};
5use proptest_state_machine::{prop_state_machine, ReferenceStateMachine, StateMachineTest};
6
7use super::*;
8
9const CAP: usize = 128;
10
11#[derive(Debug, Default)]
12struct DmaMock {
13 pos: usize,
14 wraps: usize,
15}
16
17impl DmaMock {
18 pub fn advance(&mut self, steps: usize) {
19 let next = self.pos + steps;
20 self.pos = next % CAP;
21 self.wraps += next / CAP;
22 }
23}
24
25impl DmaCtrl for DmaMock {
26 fn get_remaining_transfers(&self) -> usize {
27 CAP - self.pos
28 }
29
30 fn reset_complete_count(&mut self) -> usize {
31 core::mem::replace(&mut self.wraps, 0)
32 }
33
34 fn set_waker(&mut self, _waker: &Waker) {}
35}
36
37#[derive(Debug, Clone)]
38enum Status {
39 Available(usize),
40 Failed,
41}
42
43impl Status {
44 pub fn new(capacity: usize) -> Self {
45 Self::Available(capacity)
46 }
47}
48
49mod reader;
50mod writer;
diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs
new file mode 100644
index 000000000..6555ebfb0
--- /dev/null
+++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs
@@ -0,0 +1,122 @@
1use core::fmt::Debug;
2
3use super::*;
4
5#[derive(Debug, Clone)]
6enum ReaderTransition {
7 Write(usize),
8 Clear,
9 ReadUpTo(usize),
10}
11
12struct ReaderSM;
13
14impl ReferenceStateMachine for ReaderSM {
15 type State = Status;
16 type Transition = ReaderTransition;
17
18 fn init_state() -> BoxedStrategy<Self::State> {
19 strategy::Just(Status::new(0)).boxed()
20 }
21
22 fn transitions(_state: &Self::State) -> BoxedStrategy<Self::Transition> {
23 prop_oneof![
24 (1..50_usize).prop_map(ReaderTransition::Write),
25 (1..50_usize).prop_map(ReaderTransition::ReadUpTo),
26 strategy::Just(ReaderTransition::Clear),
27 ]
28 .boxed()
29 }
30
31 fn apply(status: Self::State, transition: &Self::Transition) -> Self::State {
32 match (status, transition) {
33 (_, ReaderTransition::Clear) => Status::Available(0),
34 (Status::Available(x), ReaderTransition::Write(y)) => {
35 if x + y > CAP {
36 Status::Failed
37 } else {
38 Status::Available(x + y)
39 }
40 }
41 (Status::Available(x), ReaderTransition::ReadUpTo(y)) => Status::Available(x.saturating_sub(*y)),
42 (Status::Failed, _) => Status::Failed,
43 }
44 }
45}
46
47struct ReaderSut {
48 status: Status,
49 buffer: *mut [u8],
50 producer: DmaMock,
51 consumer: ReadableDmaRingBuffer<'static, u8>,
52}
53
54impl Debug for ReaderSut {
55 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
56 <DmaMock as Debug>::fmt(&self.producer, f)
57 }
58}
59
60struct ReaderTest;
61
62impl StateMachineTest for ReaderTest {
63 type SystemUnderTest = ReaderSut;
64 type Reference = ReaderSM;
65
66 fn init_test(ref_status: &<Self::Reference as ReferenceStateMachine>::State) -> Self::SystemUnderTest {
67 let buffer = Box::into_raw(Box::new([0; CAP]));
68 ReaderSut {
69 status: ref_status.clone(),
70 buffer,
71 producer: DmaMock::default(),
72 consumer: ReadableDmaRingBuffer::new(unsafe { &mut *buffer }),
73 }
74 }
75
76 fn teardown(state: Self::SystemUnderTest) {
77 unsafe {
78 let _ = Box::from_raw(state.buffer);
79 };
80 }
81
82 fn apply(
83 mut sut: Self::SystemUnderTest,
84 ref_state: &<Self::Reference as ReferenceStateMachine>::State,
85 transition: <Self::Reference as ReferenceStateMachine>::Transition,
86 ) -> Self::SystemUnderTest {
87 match transition {
88 ReaderTransition::Write(x) => sut.producer.advance(x),
89 ReaderTransition::Clear => {
90 sut.consumer.clear(&mut sut.producer);
91 }
92 ReaderTransition::ReadUpTo(x) => {
93 let status = sut.status;
94 let ReaderSut {
95 ref mut producer,
96 ref mut consumer,
97 ..
98 } = sut;
99 let mut buf = vec![0; x];
100 let res = consumer.read(producer, &mut buf);
101 match status {
102 Status::Available(n) => {
103 let readable = x.min(n);
104
105 assert_eq!(res.unwrap().0, readable);
106 }
107 Status::Failed => assert!(res.is_err()),
108 }
109 }
110 }
111
112 ReaderSut {
113 status: ref_state.clone(),
114 ..sut
115 }
116 }
117}
118
119prop_state_machine! {
120 #[test]
121 fn reader_state_test(sequential 1..20 => ReaderTest);
122}
diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs
new file mode 100644
index 000000000..15f54c672
--- /dev/null
+++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs
@@ -0,0 +1,121 @@
1use core::fmt::Debug;
2
3use super::*;
4
5#[derive(Debug, Clone)]
6enum WriterTransition {
7 Read(usize),
8 WriteUpTo(usize),
9 Clear,
10}
11
12struct WriterSM;
13
14impl ReferenceStateMachine for WriterSM {
15 type State = Status;
16 type Transition = WriterTransition;
17
18 fn init_state() -> BoxedStrategy<Self::State> {
19 strategy::Just(Status::new(CAP)).boxed()
20 }
21
22 fn transitions(_state: &Self::State) -> BoxedStrategy<Self::Transition> {
23 prop_oneof![
24 (1..50_usize).prop_map(WriterTransition::Read),
25 (1..50_usize).prop_map(WriterTransition::WriteUpTo),
26 strategy::Just(WriterTransition::Clear),
27 ]
28 .boxed()
29 }
30
31 fn apply(status: Self::State, transition: &Self::Transition) -> Self::State {
32 match (status, transition) {
33 (_, WriterTransition::Clear) => Status::Available(CAP),
34 (Status::Available(x), WriterTransition::Read(y)) => {
35 if x < *y {
36 Status::Failed
37 } else {
38 Status::Available(x - y)
39 }
40 }
41 (Status::Available(x), WriterTransition::WriteUpTo(y)) => Status::Available((x + *y).min(CAP)),
42 (Status::Failed, _) => Status::Failed,
43 }
44 }
45}
46
47struct WriterSut {
48 status: Status,
49 buffer: *mut [u8],
50 producer: WritableDmaRingBuffer<'static, u8>,
51 consumer: DmaMock,
52}
53
54impl Debug for WriterSut {
55 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
56 <DmaMock as Debug>::fmt(&self.consumer, f)
57 }
58}
59
60struct WriterTest;
61
62impl StateMachineTest for WriterTest {
63 type SystemUnderTest = WriterSut;
64 type Reference = WriterSM;
65
66 fn init_test(ref_status: &<Self::Reference as ReferenceStateMachine>::State) -> Self::SystemUnderTest {
67 let buffer = Box::into_raw(Box::new([0; CAP]));
68 WriterSut {
69 status: ref_status.clone(),
70 buffer,
71 producer: WritableDmaRingBuffer::new(unsafe { &mut *buffer }),
72 consumer: DmaMock::default(),
73 }
74 }
75
76 fn teardown(state: Self::SystemUnderTest) {
77 unsafe {
78 let _ = Box::from_raw(state.buffer);
79 };
80 }
81
82 fn apply(
83 mut sut: Self::SystemUnderTest,
84 ref_status: &<Self::Reference as ReferenceStateMachine>::State,
85 transition: <Self::Reference as ReferenceStateMachine>::Transition,
86 ) -> Self::SystemUnderTest {
87 match transition {
88 WriterTransition::Read(x) => sut.consumer.advance(x),
89 WriterTransition::Clear => {
90 sut.producer.clear(&mut sut.consumer);
91 }
92 WriterTransition::WriteUpTo(x) => {
93 let status = sut.status;
94 let WriterSut {
95 ref mut producer,
96 ref mut consumer,
97 ..
98 } = sut;
99 let mut buf = vec![0; x];
100 let res = producer.write(consumer, &mut buf);
101 match status {
102 Status::Available(n) => {
103 let writable = x.min(CAP - n.min(CAP));
104 assert_eq!(res.unwrap().0, writable);
105 }
106 Status::Failed => assert!(res.is_err()),
107 }
108 }
109 }
110
111 WriterSut {
112 status: ref_status.clone(),
113 ..sut
114 }
115 }
116}
117
118prop_state_machine! {
119 #[test]
120 fn writer_state_test(sequential 1..20 => WriterTest);
121}