imxrt_dcd/
lib.rs

1#![doc = include_str!("../README.md")]
2use itertools::Itertools;
3
4#[cfg(feature = "ral")]
5mod macros;
6
7/// A DCD command.
8#[derive(Default, Clone, Debug, Eq, PartialEq)]
9pub enum Command {
10    /// Dummy command --- may behave as a small delay.
11    #[default]
12    Nop,
13    /// DCD command for writing a value to an address; [`Write`].
14    Write(Write),
15    /// DCD command for polling an address until the value matches a given bitmask condition; [`Check`].
16    Check(Check),
17}
18
19/*
20// TODO(summivox): not ready
21impl Command {
22    pub fn with_count(self, count: u32) -> Self {
23        match self {
24            Self::Check(check) => Self::Check(check.with_count(count)),
25            _ => panic!("`with_count` can only be called on a `Check` command."),
26        }
27    }
28}
29 */
30
31/// DCD command for writing a value to an address.
32#[derive(Default, Clone, Debug, Eq, PartialEq)]
33pub struct Write {
34    /// Width of the bus write.
35    pub width: Width,
36    /// Writing operation --- see [`WriteOp`].
37    pub op: WriteOp,
38    /// Address to be written to. Note that the ROM may enforce valid address ranges.
39    pub address: u32,
40    pub value: u32,
41}
42
43/// DCD command for polling an address until the value matches a given bitmask condition.
44#[derive(Default, Clone, Debug, Eq, PartialEq)]
45pub struct Check {
46    /// Width of the bus read.
47    pub width: Width,
48    /// Condition to check --- see [`CheckCond`].
49    pub cond: CheckCond,
50    /// Address to read from. Unlike [`Write::address`], any address is valid.
51    pub address: u32,
52    /// Bitmask to check the value against --- see [`CheckCond`].
53    pub mask: u32,
54    /// Optional poll count:
55    /// - `None` => poll indefinitely
56    /// - `Some(0)` => equivalent to [`Command::Nop`]
57    /// - `Some(x) if x > 0` => poll at most `x` times; if the condition still is not satisfied,
58    ///   the boot ROM will abandon interpreting the rest of the DCD.
59    pub count: Option<u32>,
60}
61
62/*
63// TODO(summivox): not ready
64impl Check {
65    pub fn with_count(self, count: u32) -> Self {
66        Self { count: Some(count), ..self }
67    }
68}
69 */
70
71/// Byte width of the bus read/write.
72#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
73#[repr(u8)]
74pub enum Width {
75    /// 1 byte / 8 bit
76    B1 = 0b001u8,
77    /// 2 bytes / 16 bit
78    B2 = 0b010u8,
79    /// 4 bytes / 32 bit
80    #[default]
81    B4 = 0b100u8,
82}
83
84impl Width {
85    /// ```
86    /// # use imxrt_dcd::Width;
87    /// assert_eq!(Width::from_num_bytes(1), Width::B1);
88    /// assert_eq!(Width::from_num_bytes(2), Width::B2);
89    /// assert_eq!(Width::from_num_bytes(4), Width::B4);
90    /// ```
91    pub const fn from_num_bytes(num_bytes: usize) -> Self {
92        match num_bytes {
93            1 => Self::B1,
94            2 => Self::B2,
95            4 => Self::B4,
96            _ => panic!("invalid width"),
97        }
98    }
99
100    /// Returns the width of the given RAL register instance, i.e. `&RWRegister<u32>` and friends.
101    ///
102    /// Since `RWRegister<T>` is a newtype wrapper of `T`, this works on primitives too.
103    /// ```
104    /// # use imxrt_dcd::Width;
105    /// let a = 1u8;
106    /// let b = 2u16;
107    /// let c = 3i32;
108    /// assert_eq!(Width::from_reg(&a), Width::B1);
109    /// assert_eq!(Width::from_reg(&b), Width::B2);
110    /// assert_eq!(Width::from_reg(&c), Width::B4);
111    /// ```
112    pub const fn from_reg<T>(_: &T) -> Self {
113        Self::from_num_bytes(core::mem::size_of::<T>())
114    }
115}
116
117/// [`Write`] operation variants.
118#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
119#[repr(u8)]
120pub enum WriteOp {
121    /// `*address = value` --- direct write
122    #[default]
123    Write = 0b00_000u8,
124    /// `*address &= !value` --- clear bits (read-modify-write)
125    Clear = 0b01_000u8,
126    /// `*address |= value` --- set bits (read-modify-write)
127    Set = 0b11_000u8,
128}
129
130/// [`Check`] condition variants.
131#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
132#[repr(u8)]
133pub enum CheckCond {
134    #[default]
135    /// `(*address & mask) == 0` --- All masked bits are 0 in value
136    AllClear = 0b00_000u8,
137    /// `(*address & mask) != mask` --- Some masked bits are 0 in value
138    AnyClear = 0b01_000u8,
139    /// `(*address & mask) == mask` --- All masked bits are 1 in value
140    AllSet = 0b10_000u8,
141    /// `(*address & mask) != 0` --- Some masked bits are 1 in value
142    AnySet = 0b11_000u8,
143}
144
145///////////////////////////////////////////////////////////////////////////
146
147fn dcd_header(byte_len: u16) -> [u8; 4] {
148    let mut header = [0xD2, 0x00, 0x00, 0x41];
149    header[1..=2].copy_from_slice(&byte_len.to_be_bytes()[0..=1]);
150    header
151}
152
153const NOP_HEADER: [u8; 4] = [0xC0, 0x00, 0x04, 0x00];
154
155impl Write {
156    fn byte_len(group_size: usize) -> u16 {
157        let n = 4 + group_size * 8;
158        assert!(n <= u16::MAX as usize);
159        n as u16
160    }
161    fn header(&self, group_size: usize) -> [u8; 4] {
162        let mut header = [0xCC, 0x00, 0x00, self.width as u8 | self.op as u8];
163        header[1..=2].copy_from_slice(&Self::byte_len(group_size).to_be_bytes()[0..=1]);
164        header
165    }
166    fn payload(&self) -> [u8; 8] {
167        let mut payload = [0u8; 8];
168        payload[0..4].copy_from_slice(&self.address.to_be_bytes()[0..4]);
169        payload[4..8].copy_from_slice(&self.value.to_be_bytes()[0..4]);
170        payload
171    }
172}
173
174impl Check {
175    fn byte_len(&self) -> u16 {
176        if self.count.is_some() {
177            16
178        } else {
179            12
180        }
181    }
182    fn header(&self) -> [u8; 4] {
183        let mut header = [0xCF, 0x00, 0x00, self.width as u8 | self.cond as u8];
184        header[1..=2].copy_from_slice(&self.byte_len().to_be_bytes()[0..=1]);
185        header
186    }
187    fn payload(&self) -> [u8; 8] {
188        let mut payload = [0u8; 8];
189        payload[0..4].copy_from_slice(&self.address.to_be_bytes()[0..4]);
190        payload[4..8].copy_from_slice(&self.mask.to_be_bytes()[0..4]);
191        payload
192    }
193    fn payload_with_count(&self) -> [u8; 12] {
194        let mut payload = [0u8; 12];
195        payload[0..4].copy_from_slice(&self.address.to_be_bytes()[0..4]);
196        payload[4..8].copy_from_slice(&self.mask.to_be_bytes()[0..4]);
197        payload[8..12].copy_from_slice(&self.count.unwrap().to_be_bytes()[0..4]);
198        payload
199    }
200}
201
202fn group_key(index: usize, command: &Command) -> (usize, Width, WriteOp) {
203    match command {
204        &Command::Write(Write {
205            width, op, ..
206        }) => (usize::MAX, width, op),
207        _ => (index, Width::default(), WriteOp::default()),
208    }
209}
210
211///////////////////////////////////////////////////////////////////////////
212
213/// Serializes given commands as a complete DCD block into a byte stream.
214/// Consecutive write commands with the same width and op are automatically combined.
215///
216/// While the ROM may enforce tighter byte size limits, this
217///
218/// Returns the number of bytes written or error.
219///
220/// # Examples
221///
222/// See [crate-level doc](crate).
223///
224pub fn serialize(mut w: impl std::io::Write, commands: &[Command]) -> std::io::Result<usize> {
225    if commands.is_empty() {
226        return Ok(0);
227    }
228    // count num of bytes first
229    let mut byte_len: usize = 4; // DCD header
230    for (_, mut group) in &commands
231        .into_iter()
232        .enumerate()
233        .group_by(|&(index, command)| group_key(index, command))
234    {
235        let Some((_, head)) = group.next() else { continue; };
236        match head {
237            Command::Nop => {
238                byte_len += 4;
239            }
240            Command::Check(check) => {
241                byte_len += check.byte_len() as usize;
242            }
243            Command::Write(_) => {
244                byte_len += Write::byte_len(group.count() + 1) as usize;
245            }
246        }
247    }
248    if byte_len > u16::MAX as usize {
249        return Err(std::io::Error::new(
250            std::io::ErrorKind::InvalidInput,
251            "DCD byte length too large",
252        ));
253    }
254    w.write_all(&dcd_header(byte_len as u16))?;
255    for (_, mut group) in &commands
256        .into_iter()
257        .enumerate()
258        .group_by(|&(index, command)| group_key(index, command))
259    {
260        let Some((_, head)) = group.next() else { continue; };
261        match head {
262            Command::Nop => {
263                w.write_all(&NOP_HEADER)?;
264            }
265            Command::Check(check) => {
266                w.write_all(&check.header())?;
267                if check.count.is_some() {
268                    w.write_all(&check.payload_with_count())?;
269                } else {
270                    w.write_all(&check.payload())?;
271                }
272            }
273            Command::Write(write) => {
274                let (counter, rest) = group.tee();
275                w.write_all(&write.header(counter.count() + 1))?;
276                w.write_all(&write.payload())?;
277                for (_, command) in rest {
278                    if let Command::Write(write) = command {
279                        w.write_all(&write.payload())?;
280                    }
281                }
282            }
283        }
284    }
285    Ok(byte_len)
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    #[rustfmt::skip]
294    fn serialize_simple() {
295        let mut buf = vec![];
296        let byte_len = serialize(
297            &mut buf,
298            &[
299                Command::Nop,
300                Command::Write(Write {
301                    width: Width::B4,
302                    op: WriteOp::Write,
303                    address: 0x01234567,
304                    value: 0xdeadbeef,
305                }),
306                Command::Check(Check {
307                    width: Width::B2,
308                    cond: CheckCond::AnySet,
309                    address: 0x89abcdef,
310                    mask: 0x55aa55aa,
311                    count: Some(16),
312                }),
313                Command::Check(Check {
314                    width: Width::B1,
315                    cond: CheckCond::AnyClear,
316                    address: 0x89abcdef,
317                    mask: 0x55aa55aa,
318                    count: None,
319                }),
320            ],
321        )
322        .expect("IO failure");
323        assert_eq!(byte_len, 48);
324        assert_eq!(
325            &buf,
326            &[
327                // DCD header
328                0xD2, 0, 48, 0x41,
329                // nop
330                0xC0, 0x00, 0x04, 0x00,
331                // write
332                0xCC, 0, 12, 0x04, 0x01, 0x23, 0x45, 0x67, 0xde, 0xad, 0xbe, 0xef,
333                // check with count
334                0xCF, 0, 16, 0x1a, 0x89, 0xab, 0xcd, 0xef, 0x55, 0xaa, 0x55, 0xaa, 0, 0, 0, 16,
335                // check without
336                0xCF, 0, 12, 0x09, 0x89, 0xab, 0xcd, 0xef, 0x55, 0xaa, 0x55, 0xaa,
337            ]
338        );
339    }
340
341    #[test]
342    #[rustfmt::skip]
343    fn serialize_merge() {
344        let mut buf = vec![];
345        let byte_len = serialize(
346            &mut buf,
347            &[
348                // the following 3 writes should be merged
349                Command::Write(Write {
350                    width: Width::B4,
351                    op: WriteOp::Write,
352                    address: 0x01234567,
353                    value: 0xdeadbeef,
354                }),
355                Command::Write(Write {
356                    width: Width::B4,
357                    op: WriteOp::Write,
358                    address: 0x89abcdef,
359                    value: 0x13370000,
360                }),
361                Command::Write(Write {
362                    width: Width::B4,
363                    op: WriteOp::Write,
364                    address: 0x55aa55aa,
365                    value: 0xaa55aa55,
366                }),
367                Command::Nop,
368                // this is not merged because of the NOP in the middle
369                Command::Write(Write {
370                    width: Width::B4,
371                    op: WriteOp::Write,
372                    address: 0x89abcdef,
373                    value: 0x13370000,
374                }),
375                // this is not merged with the previous because they differ in width
376                Command::Write(Write {
377                    width: Width::B2,
378                    op: WriteOp::Write,
379                    address: 0x89abcdef,
380                    value: 0x13370000,
381                }),
382                // this is not merged (ditto)
383                Command::Write(Write {
384                    width: Width::B4,
385                    op: WriteOp::Write,
386                    address: 0x55aa55aa,
387                    value: 0xaa55aa55,
388                }),
389            ],
390        ).expect("IO failure");
391        assert_eq!(byte_len, 72);
392        assert_eq!(
393            &buf,
394            &[
395                // DCD header
396                0xD2, 0, 72, 0x41,
397                // write header
398                0xCC, 0, 28, 0x04,
399                // write
400                0x01, 0x23, 0x45, 0x67, 0xde, 0xad, 0xbe, 0xef,
401                // write
402                0x89, 0xab, 0xcd, 0xef, 0x13, 0x37, 0x00, 0x00,
403                // write
404                0x55, 0xaa, 0x55, 0xaa, 0xaa, 0x55, 0xaa, 0x55,
405                // nop
406                0xC0, 0x00, 0x04, 0x00,
407                // write header
408                0xCC, 0, 12, 0x04,
409                // write
410                0x89, 0xab, 0xcd, 0xef, 0x13, 0x37, 0x00, 0x00,
411                // write header
412                0xCC, 0, 12, 0x02,
413                // write
414                0x89, 0xab, 0xcd, 0xef, 0x13, 0x37, 0x00, 0x00,
415                // write header
416                0xCC, 0, 12, 0x04,
417                // write
418                0x55, 0xaa, 0x55, 0xaa, 0xaa, 0x55, 0xaa, 0x55,
419            ]
420        );
421    }
422}