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
//! # Device Service Unit
//!
//! This module allows users to interact with a DSU peripheral.
//!
//! - Run a CRC32 checksum over memory
#![warn(missing_docs)]

use crate::pac::{DSU, PAC};

/// Device Service Unit
pub struct Dsu {
    /// PAC peripheral
    dsu: DSU,
}

/// Errors from hardware
#[derive(Debug)]
pub enum PeripheralError {
    /// Usually misaligned address of length
    BusError,
}

/// Error from within the DSU
#[derive(Debug)]
pub enum Error {
    /// Address or length was not word aligned
    AlignmentError,
    /// The PAC would not unlock the DSU for us
    PacUnlockFailed,
    /// CRC32 operation failed
    CrcFailed,
    /// Hardware-generated errors
    Peripheral(PeripheralError),
}

/// NVM result type
pub type Result<T> = core::result::Result<T, Error>;

impl Dsu {
    /// Unlock the DSU and instantiate peripheral
    #[inline]
    pub fn new(dsu: DSU, pac: &PAC) -> Result<Self> {
        // Attempt to unlock DSU
        pac.wrctrl
            .write(|w| unsafe { w.perid().bits(33).key().clr() });

        // Check if DSU was unlocked
        if pac.statusb.read().dsu_().bit_is_set() {
            Err(Error::PacUnlockFailed)
        } else {
            Ok(Self { dsu })
        }
    }

    /// Clear bus error bit
    fn clear_bus_error(&mut self) {
        self.dsu.statusa.write(|w| w.berr().set_bit());
    }

    /// Read bus error bit
    fn bus_error(&mut self) -> bool {
        self.dsu.statusa.read().berr().bit()
    }

    /// Check if operation is done
    fn is_done(&self) -> bool {
        self.dsu.statusa.read().done().bit_is_set()
    }

    /// Check if an operation has failed
    fn has_failed(&self) -> bool {
        self.dsu.statusa.read().fail().bit_is_set()
    }

    /// Set target address given as number of words offset
    fn set_address(&mut self, address: u32) -> Result<()> {
        self.dsu.addr.write(|w| unsafe { w.addr().bits(address) });
        Ok(())
    }

    /// Set length given as number of words
    fn set_length(&mut self, length: u32) -> Result<()> {
        self.dsu
            .length
            .write(|w| unsafe { w.length().bits(length) });
        Ok(())
    }

    /// Seed CRC32
    fn seed(&mut self, data: u32) {
        self.dsu.data.write(|w| unsafe { w.data().bits(data) });
    }

    /// Calculate CRC32 of a memory region
    ///
    /// - `address` is an address within a flash; must be word-aligned
    /// - `length` is a length of memory region that is being processed. Must be
    ///   word-aligned
    #[inline]
    pub fn crc32(&mut self, address: u32, length: u32) -> Result<u32> {
        // The algorithm employed is the industry standard CRC32 algorithm using the
        // generator polynomial 0xEDB88320
        // (reversed representation of 0x04C11DB7).
        //
        // https://crccalc.com/, Hex input same as memory contents, Calc CRC-32
        // but output is reversed

        if address % 4 != 0 {
            return Err(Error::AlignmentError);
        }

        if length % 4 != 0 {
            return Err(Error::AlignmentError);
        }

        let num_words = length / 4;

        // Calculate target flash address
        let flash_address = address / 4;

        // Set the ADDR of where to start calculation, as number of words
        self.set_address(flash_address)?;

        // Amount of words to check
        self.set_length(num_words)?;

        // Set CRC32 seed
        self.seed(0xffff_ffff);

        // Clear the status flags indicating termination of the operation
        self.dsu
            .statusa
            .write(|w| w.done().set_bit().fail().set_bit());

        // Initialize CRC calculation
        self.dsu.ctrl.write(|w| w.crc().set_bit());

        // Wait until done or failed
        while !self.is_done() && !self.has_failed() {}
        if self.has_failed() {
            return Err(Error::CrcFailed);
        }

        // CRC startup generated a bus error
        // Generally misaligned length or address
        if self.bus_error() {
            // Return the reported bus error and clear it
            self.clear_bus_error();
            Err(Error::Peripheral(PeripheralError::BusError))
        } else {
            // Return the calculated CRC32 (complement of data register)
            Ok(!self.dsu.data.read().data().bits())
        }
    }
}