1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
//! Transactional I/O adapter.

use core::marker::PhantomData;
use core::sync::atomic::{AtomicUsize, Ordering};

use expander::pin::{ExpanderIO, PortPin};
use expander::Expander;
use interface::ExpanderInterface;
use mutex::IOMutex;
use registers::valid_port;

/// Control how `TransactionalIO::write_back` will batch writes to modified pins.
pub enum Strategy {
    /// This strategy will issue writes such that only the ports that have had output values
    /// explicitly set through the `OutputPin` impl will be altered.
    ///
    /// This is the safest but least efficient write back strategy.
    Exact,

    /// This strategy will relax the write back batching so that it may overwrite any port that had
    /// its state read and cached in the most recent `refresh` call.
    ///
    /// This means that some port registers may be "stomped" by writing values that match the
    /// values they had when `refresh` was called. This is true regardless of whether the port is
    /// configured as an input or output pin, however ports configured as inputs will remain in
    /// input mode and the superfluous write to that port is not observable.
    StompClean,

    /// This strategy will further relax the write-back batching so that may potentially overwrite
    /// *any* port, even if the previous value was not read in a `refresh`.
    ///
    /// Any ports that were not read in a `refresh` will be overwritten with an undefined value.
    /// This strategy makes most efficient use of the bus when most pins are output pins, but is
    /// only usable if you call `port_pin` for every port you care about, and *either*
    ///
    /// * Explicitly set every pin whose value you care about before each `write_back` call, or
    /// * Call `refresh` first before setting any pins.
    StompAny,
}

/// This I/O adapter captures the `Expander` and provides a factory for generating GPIO pins that
/// implement `InputPin` and `OutputPin` traits backed by a transactional write-back cache.
///
/// Each such pin will read its input state from a cached batch read at the beginning of a
/// transaction, and will write its input state into a write-back buffer that is committed with a
/// batch write at the end of the transaction. This reduces bus traffic due to the MAX7301's
/// support for reading or writing 8 consecutive ports in a single operation.
pub struct TransactionalIO<M, EI>
where
    M: IOMutex<Expander<EI>>,
    EI: ExpanderInterface + Send,
{
    expander: M,
    issued: AtomicUsize,
    cache: AtomicUsize,
    dirty: AtomicUsize,
    fresh: AtomicUsize,
    _ei: PhantomData<EI>,
}

impl<M, EI> TransactionalIO<M, EI>
where
    M: IOMutex<Expander<EI>>,
    EI: ExpanderInterface + Send,
{
    pub(crate) fn new(expander: Expander<EI>) -> Self {
        TransactionalIO {
            expander: M::new(expander),
            issued: AtomicUsize::default(),
            cache: AtomicUsize::default(),
            dirty: AtomicUsize::default(),
            fresh: AtomicUsize::default(),
            _ei: PhantomData,
        }
    }

    /// Create a `PortPin` corresponding to one of the ports on the MAX7301.
    ///
    /// The returned `PortPin` implements `InputPin` and `OutputPin`, and using any of the methods
    /// from these traits on the returned `PortPin` will read or write the value of the I/O port
    /// from a local write-back cache. Refreshing or writing back the cache is controlled by
    /// `refresh` and `write_back`.
    pub fn port_pin<'io>(&'io self, port: u8) -> PortPin<'io, Self> {
        self.issued
            .fetch_or(1 << valid_port(port), Ordering::Relaxed);
        PortPin::new(self, port)
    }

    /// Refresh the local cache by reading the port values from any outstanding `PortPin`s issued
    /// from this adapter, updating the values read through their `InputPin` impls.
    ///
    /// This is done using batch registers of MAX7301 to reduce bus traffic. All pending
    /// `OutputPin` operations are discarded.
    pub fn refresh(&self) -> Result<(), ()> {
        self.dirty.store(0, Ordering::Release);
        let mut load_buffer = 0usize;
        let mut fresh_buffer = 0usize;
        let mut start_port = 4;
        let mut ports_to_read = self.issued.load(Ordering::Relaxed) >> start_port;
        while ports_to_read != 0 {
            let skip = ports_to_read.trailing_zeros();
            ports_to_read >>= skip;
            start_port += skip;
            let port_values = self.expander.lock(|ex| ex.read_ports(start_port as u8))?;
            load_buffer |= (port_values as usize) << start_port;
            fresh_buffer |= 0xFFusize << start_port;
            ports_to_read &= !0xFFusize;
        }
        self.cache.store(load_buffer, Ordering::Relaxed);
        self.fresh.store(fresh_buffer, Ordering::Relaxed);
        Ok(())
    }

    /// Write back any pending `OutputPin` operations to the MAX7301.
    ///
    /// The strategy used to do this is controlled by `strategy` (see [`Strategy`] docs for a
    /// description of the available strategies).
    pub fn write_back(&self, strategy: Strategy) -> Result<(), ()> {
        let mut start_port = 0;
        let mut ports_to_write = self.dirty.load(Ordering::Acquire);
        let mut ok_to_write = match strategy {
            Strategy::Exact => ports_to_write,
            Strategy::StompClean => self.fresh.load(Ordering::Acquire),
            Strategy::StompAny => 0xFFFFFFFC,
        };
        let cache = self.cache.load(Ordering::Acquire);
        while ports_to_write != 0 {
            let skip = ports_to_write.trailing_zeros();
            ports_to_write >>= skip;
            ok_to_write >>= skip;
            start_port += skip;
            if ok_to_write & 0xFF == 0xFF {
                let port_values = (cache >> start_port) as u8;
                self.expander
                    .lock(|ex| ex.write_ports(start_port as u8, port_values))?;
                ports_to_write &= !0xFFusize;
            } else {
                let port_value = cache & (1 << start_port) != 0;
                self.expander
                    .lock(|ex| ex.write_port(start_port as u8, port_value))?;
                ports_to_write &= !0x01usize;
            }
        }
        self.dirty.store(0, Ordering::Release);
        Ok(())
    }
}

impl<M, EI> ExpanderIO for TransactionalIO<M, EI>
where
    M: IOMutex<Expander<EI>>,
    EI: ExpanderInterface + Send,
{
    fn write_port(&self, port: u8, bit: bool) {
        let or_bit = 1 << port;
        if bit {
            self.cache.fetch_or(or_bit, Ordering::Release);
        } else {
            self.cache.fetch_and(!or_bit, Ordering::Release);
        }
        self.dirty.fetch_or(or_bit, Ordering::Relaxed);
        self.fresh.fetch_or(or_bit, Ordering::Relaxed);
    }
    fn read_port(&self, port: u8) -> bool {
        if self.fresh.load(Ordering::Relaxed) & (1 << port) == 0 {
            panic!("Read of un-refreshed port;}")
        }
        self.cache.load(Ordering::Relaxed) & (1 << port) != 0
    }
}

#[cfg(test)]
mod tests {
    use super::Strategy;
    use expander::Expander;
    use hal::digital::{InputPin, OutputPin};
    use interface::test_spy::{SemanticTestSpyInterface, TestPort};
    use mutex::DefaultMutex;
    use proptest::collection::vec;
    use proptest::prelude::*;

    proptest! {
        #![proptest_config(ProptestConfig::with_cases(2000))]

        #[test]
        fn prop_read_unrefreshed_panics(
            reset in vec(any::<bool>(), 32 - 4),
            pin in 4..=31u8
        ) {
            assert!(std::panic::catch_unwind(|| {
                let ei = SemanticTestSpyInterface::new(reset);
                let io = Expander::new(ei.split()).into_transactional::<DefaultMutex<_>>();
                let any_pin = io.port_pin(pin);

                any_pin.is_high();
            })
            .is_err());
        }

        #[test]
        fn prop_read_refreshed_ok(
            reset in vec(any::<bool>(), 32 - 4),
            pins in vec(4..=31u8, 0..=28)
        ) {
            let ei = SemanticTestSpyInterface::new(reset.clone());
            let io = Expander::new(ei.split()).into_transactional::<DefaultMutex<_>>();
            let some_pins = pins
                .iter()
                .cloned()
                .map(|p| io.port_pin(p))
                .collect::<Vec<_>>();

            assert!(io.refresh().is_ok());
            for (idx, pin_nr) in pins.iter().enumerate() {
                assert_eq!(some_pins[idx].is_high(), reset[*pin_nr as usize - 4]);
            }
        }

        // Precisely the pins that were modified will be written back using blind writes. No other
        // ports should be written.
        #[test]
        fn prop_write_exact_no_refresh(
            reset in vec(any::<bool>(), 32 - 4),
            pins_and_bits in vec((4..=31u8, any::<bool>()), 0..=28),
        ) {
            let ei = SemanticTestSpyInterface::new(reset.clone());
            let io = Expander::new(ei.split()).into_transactional::<DefaultMutex<_>>();
            let mut some_pins = pins_and_bits
                .iter()
                .cloned()
                .map(|(p, b)| (p, io.port_pin(p), b))
                .collect::<Vec<_>>();
            let mut expect = (4..=31)
                .into_iter()
                .map(|p| TestPort::Reset(reset[p as usize - 4]))
                .collect::<Vec<_>>();

            for (port, pin, bit) in some_pins.iter_mut() {
                if *bit {
                    pin.set_high()
                } else {
                    pin.set_low()
                }
                expect[*port as usize - 4] = TestPort::BlindWrite(*bit);
            }
            assert!(io.write_back(Strategy::Exact).is_ok());
            assert_eq!(expect, ei.peek_all());
        }

        // Precisely the pins that were modified will be written back using read-writes. No other
        // ports should be written.
        #[test]
        fn prop_write_exact_with_refresh(
            reset in vec(any::<bool>(), 32 - 4),
            pins_and_bits in vec((4..=31u8, any::<bool>()), 0..=28),
        ) {
            let ei = SemanticTestSpyInterface::new(reset.clone());
            let io = Expander::new(ei.split()).into_transactional::<DefaultMutex<_>>();
            let mut some_pins = pins_and_bits
                .iter()
                .cloned()
                .map(|(p, b)| (p, io.port_pin(p), b))
                .collect::<Vec<_>>();
            let mut expect = (4..=31)
                .into_iter()
                .map(|p| TestPort::Reset(reset[p as usize - 4]))
                .collect::<Vec<_>>();

            assert!(io.refresh().is_ok());
            for (port, pin, bit) in some_pins.iter_mut() {
                if *bit {
                    pin.set_high()
                } else {
                    pin.set_low()
                }
                expect[*port as usize - 4] = TestPort::ReadWrite(*bit);
            }
            assert!(io.write_back(Strategy::Exact).is_ok());
            assert_eq!(expect, ei.peek_all());
        }

        // StompClean strategy should behave identically to Exact strategy if no refresh has
        // occurred, using blind writes to write exactly the ports whose pin was modified.
        #[test]
        fn prop_write_clean_no_refresh(
            reset in vec(any::<bool>(), 32 - 4),
            pins_and_bits in vec((4..=31u8, any::<bool>()), 0..=28),
        ) {
            let ei = SemanticTestSpyInterface::new(reset.clone());
            let io = Expander::new(ei.split()).into_transactional::<DefaultMutex<_>>();
            let mut some_pins = pins_and_bits
                .iter()
                .cloned()
                .map(|(p, b)| (p, io.port_pin(p), b))
                .collect::<Vec<_>>();
            let mut expect = (4..=31)
                .into_iter()
                .map(|p| TestPort::Reset(reset[p as usize - 4]))
                .collect::<Vec<_>>();

            for (port, pin, bit) in some_pins.iter_mut() {
                if *bit {
                    pin.set_high()
                } else {
                    pin.set_low()
                }
                expect[*port as usize - 4] = TestPort::BlindWrite(*bit);
            }
            assert!(io.write_back(Strategy::StompClean).is_ok());
            assert_eq!(expect, ei.peek_all());
        }

        // StompClean strategy will preserve all reset values that do not correspond to pins that
        // were written to. Ports for written pins should contain the new bit. No blind writes
        // should occur.
        #[test]
        fn prop_write_clean_with_refresh(
            reset in vec(any::<bool>(), 32 - 4),
            pins_and_bits in vec((4..=31u8, any::<bool>()), 0..=28),
        ) {
            let ei = SemanticTestSpyInterface::new(reset.clone());
            let io = Expander::new(ei.split()).into_transactional::<DefaultMutex<_>>();
            let mut some_pins = pins_and_bits
                .iter()
                .cloned()
                .map(|(p, b)| (p, io.port_pin(p), b))
                .collect::<Vec<_>>();
            let mut expect = reset.clone();

            assert!(io.refresh().is_ok());
            for (port, pin, bit) in some_pins.iter_mut() {
                if *bit {
                    pin.set_high()
                } else {
                    pin.set_low()
                }
                expect[*port as usize - 4] = *bit;
            }
            assert!(io.write_back(Strategy::StompClean).is_ok());
            assert_eq!(expect, ei.peek_bits());
            assert!(
                !ei.peek_all().iter().any(|p| match p {
                    TestPort::BlindWrite(_) => true,
                    _ => false,
                }),
                "{:?}",
                ei.peek_all()
            );
        }

        // StompAny strategy when no refresh is occurred may use blind writes on any ports. The
        // ports that were written will have the new bits, no other guarantees are given.
        #[test]
        fn prop_write_any_no_refresh(
            reset in vec(any::<bool>(), 32 - 4),
            pins_and_bits in vec((4..=31u8, any::<bool>()), 0..=28),
        ) {
            let ei = SemanticTestSpyInterface::new(reset.clone());
            let io = Expander::new(ei.split()).into_transactional::<DefaultMutex<_>>();
            let mut some_pins = pins_and_bits
                .iter()
                .cloned()
                .map(|(p, b)| (p, io.port_pin(p), b))
                .collect::<Vec<_>>();
            let mut expect = std::collections::BTreeMap::<u8, bool>::new();

            for (port, pin, bit) in some_pins.iter_mut() {
                if *bit {
                    pin.set_high()
                } else {
                    pin.set_low()
                }
                expect.insert(*port, *bit);
            }
            assert!(io.write_back(Strategy::StompAny).is_ok());
            for (port, bit) in expect.iter() {
                assert_eq!(
                    ei.peek_all()[*port as usize - 4],
                    TestPort::BlindWrite(*bit)
                );
            }
        }

        // StompAny strategy when a refresh has occurred may use blind writes on any ports *that
        // were not read during the refresh*. Ports that were written will have the new bits, and
        // no active pin's port will be blindly written. No guarantees are given about other ports.
        #[test]
        fn prop_write_any_with_refresh(
            reset in vec(any::<bool>(), 32 - 4),
            pins_and_bits in vec((4..=31u8, any::<bool>()), 0..=28),
        ) {
            let ei = SemanticTestSpyInterface::new(reset.clone());
            let io = Expander::new(ei.split()).into_transactional::<DefaultMutex<_>>();
            let mut some_pins = pins_and_bits
                .iter()
                .cloned()
                .map(|(p, b)| (p, io.port_pin(p), b))
                .collect::<Vec<_>>();
            let mut expect = std::collections::BTreeMap::<u8, bool>::new();

            assert!(io.refresh().is_ok());
            for (port, pin, bit) in some_pins.iter_mut() {
                if *bit {
                    pin.set_high()
                } else {
                    pin.set_low()
                }
                expect.insert(*port, *bit);
            }
            assert!(io.write_back(Strategy::StompAny).is_ok());
            for (port, bit) in expect.iter() {
                assert_eq!(
                    ei.peek_all()[*port as usize - 4],
                    TestPort::ReadWrite(*bit)
                );
            }
        }
    }
}