1
//! Utils
2
//!
3
//! # Overview
4
//!
5
//! Collection of struct which helps you write to registers.
6

            
7
#[cfg(any(ethernet_driver_supported, mipi_dsi_driver_supported))]
8
mod volatile_cell {
9
    use core::cell::UnsafeCell;
10

            
11
    /// An interior-mutable cell that always performs volatile reads and writes.
12
    ///
13
    /// This ensures that the compiler never caches descriptor field values in
14
    /// registers, which is necessary because both the CPU and the DMA engine
15
    /// may update descriptor words concurrently.
16
    #[repr(transparent)]
17
    pub(crate) struct VolatileCell<T: Copy>(UnsafeCell<T>);
18

            
19
    impl<T: Copy> VolatileCell<T> {
20
        /// Creates a new `VolatileCell` with the given initial value.
21
        pub(crate) const fn new(v: T) -> Self {
22
            Self(UnsafeCell::new(v))
23
        }
24

            
25
        /// Reads the cell value using a volatile load.
26
        #[inline]
27
        pub(crate) fn get(&self) -> T {
28
            unsafe { self.0.get().read_volatile() }
29
        }
30

            
31
        /// Writes `v` to the cell using a volatile store.
32
        #[inline]
33
        pub(crate) fn set(&self, v: T) {
34
            unsafe { self.0.get().write_volatile(v) }
35
        }
36
    }
37

            
38
    // SAFETY: Descriptor rings and LLI arrays are only ever accessed from one
39
    // execution context at a time (guarded at the driver level).
40
    unsafe impl<T: Copy + Send> Send for VolatileCell<T> {}
41
    unsafe impl<T: Copy + Sync> Sync for VolatileCell<T> {}
42
}
43
#[cfg(any(ethernet_driver_supported, mipi_dsi_driver_supported))]
44
pub(crate) use volatile_cell::*;
45

            
46
#[cfg(any(hmac_driver_supported, sha_driver_supported))]
47
mod alignment_helper {
48
    use core::marker::PhantomData;
49

            
50
    const U32_ALIGN_SIZE: usize = core::mem::size_of::<u32>();
51

            
52
    pub(crate) trait EndianessConverter {
53
        fn u32_from_bytes(bytes: [u8; 4]) -> u32;
54
        fn u32_to_bytes(word: u32) -> [u8; 4];
55
    }
56

            
57
    /// Use BE for ESP32, NE otherwise
58
    #[derive(Debug, Clone)]
59
    pub(crate) struct SocDependentEndianess;
60

            
61
    #[cfg(not(esp32))]
62
    impl EndianessConverter for SocDependentEndianess {
63
        fn u32_from_bytes(bytes: [u8; 4]) -> u32 {
64
            u32::from_ne_bytes(bytes)
65
        }
66

            
67
        fn u32_to_bytes(word: u32) -> [u8; 4] {
68
            u32::to_ne_bytes(word)
69
        }
70
    }
71

            
72
    #[cfg(esp32)]
73
    impl EndianessConverter for SocDependentEndianess {
74
        fn u32_from_bytes(bytes: [u8; 4]) -> u32 {
75
            u32::from_be_bytes(bytes)
76
        }
77

            
78
        fn u32_to_bytes(word: u32) -> [u8; 4] {
79
            u32::to_be_bytes(word)
80
        }
81
    }
82

            
83
    // The alignment helper helps you write to registers that only accept u32
84
    // using regular u8s (bytes). It keeps a write buffer of 4 u8 (could in theory
85
    // be 3 but less convenient). And if the incoming data is not convertable to u32
86
    // (i.e not a multiple of 4 in length) it will store the remainder in the
87
    // buffer until the next call.
88
    //
89
    // It assumes incoming `dst` are aligned to desired layout (in future
90
    // ptr.is_aligned can be used). It also assumes that writes are done in FIFO
91
    // order.
92
13
    #[derive(Debug, Clone)]
93
    pub(crate) struct AlignmentHelper<E: EndianessConverter> {
94
        buf: [u8; U32_ALIGN_SIZE],
95
        buf_fill: usize,
96
        phantom: PhantomData<E>,
97
    }
98

            
99
    impl AlignmentHelper<SocDependentEndianess> {
100
        pub fn default() -> AlignmentHelper<SocDependentEndianess> {
101
            AlignmentHelper {
102
                buf: [0u8; U32_ALIGN_SIZE],
103
                buf_fill: 0,
104
                phantom: PhantomData,
105
            }
106
        }
107
    }
108

            
109
    impl<E: EndianessConverter> AlignmentHelper<E> {
110
        pub fn reset(&mut self) {
111
1266
            self.buf_fill = 0;
112
        }
113

            
114
        // This function will write any remaining buffer to dst and return the
115
        // amount of *bytes* written (0 means no write). If the buffer is not
116
        // aligned to the size of the register destination, it will append the '0'
117
        // value.
118
        pub fn flush_to(&mut self, dst_ptr: *mut u32, offset: usize) -> usize {
119
            let offset = offset / U32_ALIGN_SIZE;
120
1266
            if self.buf_fill != 0 {
121
                for i in self.buf_fill..U32_ALIGN_SIZE {
122
966
                    self.buf[i] = 0;
123
                }
124

            
125
                unsafe {
126
                    dst_ptr
127
                        .add(offset)
128
966
                        .write_volatile(E::u32_from_bytes(self.buf));
129
                }
130

            
131
                // We return the **extra** bytes appended besides those already written into the
132
                // buffer.
133
966
                let ret = U32_ALIGN_SIZE - self.buf_fill;
134
966
                self.buf_fill = 0;
135

            
136
                ret
137
            } else {
138
                0
139
            }
140
        }
141

            
142
        // This function is similar to `volatile_set_memory` but will prepend data that
143
        // was previously ingested and ensure aligned (u32) writes.
144
1338
        pub fn volatile_write(&mut self, dst_ptr: *mut u32, val: u8, count: usize, offset: usize) {
145
            let count = count.div_ceil(U32_ALIGN_SIZE);
146
            let offset = offset / U32_ALIGN_SIZE;
147

            
148
            let dst_ptr = unsafe { dst_ptr.add(offset) };
149

            
150
1338
            let mut cursor = if self.buf_fill != 0 {
151
                for i in self.buf_fill..U32_ALIGN_SIZE {
152
                    self.buf[i] = val;
153
                }
154

            
155
                unsafe {
156
                    dst_ptr.write_volatile(E::u32_from_bytes(self.buf));
157
                }
158

            
159
                self.buf_fill = 0;
160

            
161
                1
162
            } else {
163
                0
164
            };
165

            
166
10062
            while cursor < count {
167
                unsafe {
168
                    dst_ptr
169
                        .add(cursor)
170
                        .write_volatile(E::u32_from_bytes([0_u8; 4]));
171
                }
172
                cursor += 1;
173
            }
174
1338
        }
175

            
176
        // This function is similar to `volatile_copy_nonoverlapping_memory`,
177
        // however it buffers up to a u32 in order to always write to registers in
178
        // an aligned way. Additionally it will keep stop writing when the end of
179
        // the register (defined by `dst_bound` relative to `dst`) and returns the
180
        // remaining data (if not possible to write everything), and if it wrote
181
        // till dst_bound or exited early (due to lack of data).
182
5191
        pub fn aligned_volatile_copy<'a>(
183
            &mut self,
184
            dst_ptr: *mut u32,
185
            src: &'a [u8],
186
            dst_bound: usize,
187
            offset: usize,
188
        ) -> (&'a [u8], bool) {
189
            let dst_bound = dst_bound / U32_ALIGN_SIZE;
190
            let offset = offset / U32_ALIGN_SIZE;
191

            
192
            assert!(dst_bound > 0);
193

            
194
            let dst_ptr = unsafe { dst_ptr.add(offset) };
195

            
196
            let mut nsrc = src;
197
            let mut cursor = 0;
198

            
199
5191
            if self.buf_fill != 0 {
200
                // First prepend existing data
201
919
                let max_fill = U32_ALIGN_SIZE - self.buf_fill;
202
                let (nbuf, src) = src.split_at(core::cmp::min(src.len(), max_fill));
203
                nsrc = src;
204

            
205
                for i in 0..max_fill {
206
                    match nbuf.get(i) {
207
                        Some(v) => {
208
916
                            self.buf[self.buf_fill] = *v;
209
916
                            self.buf_fill += 1;
210
                        }
211
619
                        None => return (&[], false), /* Used up entire buffer before filling
212
                                                      * buff_fil */
213
                    }
214
                }
215

            
216
                unsafe {
217
300
                    dst_ptr.write_volatile(E::u32_from_bytes(self.buf));
218
                }
219
                cursor += 1;
220

            
221
300
                self.buf_fill = 0;
222
            }
223

            
224
4272
            if dst_bound <= offset + cursor {
225
18
                return (nsrc, true);
226
            }
227

            
228
            let (to_write, remaining) = nsrc.split_at(core::cmp::min(
229
4554
                (dst_bound - offset - cursor) * U32_ALIGN_SIZE,
230
4554
                (nsrc.len() / U32_ALIGN_SIZE) * U32_ALIGN_SIZE,
231
            ));
232

            
233
            if !to_write.is_empty() {
234
                let (chunks, _) = to_write.as_chunks::<U32_ALIGN_SIZE>();
235
                for (i, v) in chunks.iter().enumerate() {
236
                    unsafe {
237
                        dst_ptr
238
                            .add(i + cursor)
239
34560
                            .write_volatile(E::u32_from_bytes(*v));
240
                    }
241
                }
242
            }
243

            
244
            // If it's data we can't store we don't need to try and align it, just wait for
245
            // next write Generally this applies when (src/4*4) != src
246
4554
            let was_bounded = (offset + cursor + to_write.len() / U32_ALIGN_SIZE) == dst_bound;
247

            
248
4554
            if !remaining.is_empty() && remaining.len() < 4 {
249
                self.buf[..remaining.len()].copy_from_slice(remaining);
250
1265
                self.buf_fill = remaining.len();
251

            
252
1265
                return (&[], was_bounded);
253
            }
254

            
255
3289
            (remaining, was_bounded)
256
5191
        }
257

            
258
        #[cfg(all(sha_driver_supported, not(esp32)))]
259
        pub fn volatile_write_regset(&mut self, dst_ptr: *mut u32, src: &[u8], dst_bound: usize) {
260
            let dst_bound = dst_bound / U32_ALIGN_SIZE;
261
            assert!(dst_bound > 0);
262
            assert!(src.len() <= dst_bound * 4);
263

            
264
            if !src.is_empty() {
265
                let (chunks, _) = src.as_chunks::<U32_ALIGN_SIZE>();
266
                for (i, v) in chunks.iter().enumerate() {
267
                    unsafe {
268
13
                        dst_ptr.add(i).write_volatile(E::u32_from_bytes(*v));
269
                    }
270
                }
271
            }
272
        }
273

            
274
        pub fn volatile_read_regset(&self, src_ptr: *const u32, dst: &mut [u8], dst_bound: usize) {
275
            let dst_bound = dst_bound / U32_ALIGN_SIZE;
276
1266
            assert!(dst.len() >= dst_bound * 4);
277

            
278
            let (chunks, _) = dst.as_chunks_mut::<U32_ALIGN_SIZE>();
279
            for (i, chunk) in chunks.iter_mut().enumerate() {
280
                let read_val: [u8; U32_ALIGN_SIZE] =
281
                    unsafe { E::u32_to_bytes(src_ptr.add(i).read_volatile()) };
282
8433
                *chunk = read_val;
283
            }
284
        }
285
    }
286
}
287

            
288
#[cfg(any(hmac_driver_supported, sha_driver_supported))]
289
pub(crate) use alignment_helper::*;