Skip to main content

xous_tools/
xous_arguments.rs

1use std::fmt;
2use std::io::{Cursor, Result, Seek, Write};
3pub type XousArgumentCode = u32;
4pub type XousSize = u32;
5use crc::{Hasher16, crc16};
6
7#[macro_export]
8macro_rules! make_type {
9    ($fcc:expr) => {{
10        let mut c: [u8; 4] = Default::default();
11        c.copy_from_slice($fcc.as_bytes());
12        u32::from_le_bytes(c)
13    }};
14}
15
16pub trait XousArgument: fmt::Display {
17    /// A fourcc code of this tag
18    fn code(&self) -> XousArgumentCode;
19
20    fn name(&self) -> String {
21        let tag_name_bytes = self.code().to_le_bytes();
22        String::from_utf8_lossy(&tag_name_bytes).to_string()
23    }
24
25    /// The total size of this argument, not including the code and the length.
26    fn length(&self) -> XousSize;
27
28    /// Called immediately before serializing.  Returns the amount of data
29    /// to reserve.
30    fn finalize(&mut self, _offset: usize) -> usize { 0 }
31
32    /// Write the contents of this argument to the specified writer.
33    /// Return the number of bytes written.
34    fn serialize(&self, output: &mut dyn Write) -> Result<usize>;
35
36    /// Any last data that needs to be written.
37    fn last_data(&self) -> &[u8] { &[] }
38
39    fn alignment_offset(&self) -> usize { 0 }
40
41    fn load_offset(&self) -> usize { 0 }
42}
43
44pub struct XousArguments {
45    pub ram_start: XousSize,
46    pub ram_length: XousSize,
47    ram_name: u32,
48    pub arguments: Vec<Box<dyn XousArgument>>,
49    pub detached_offset: Option<XousSize>,
50}
51
52impl fmt::Display for XousArguments {
53    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54        writeln!(f, "Xous Arguments with {} parameters", self.arguments.len())?;
55
56        let tag_name_bytes = self.ram_name.to_le_bytes();
57        let tag_name = String::from_utf8_lossy(&tag_name_bytes);
58        writeln!(
59            f,
60            "    Main RAM \"{}\" ({:08x}): {:08x} - {:08x}",
61            tag_name,
62            self.ram_name,
63            self.ram_start,
64            self.ram_start + self.ram_length
65        )?;
66
67        for (index, arg) in self.arguments.iter().enumerate() {
68            write!(f, "{:2}{}", index + 1, arg)?;
69        }
70        Ok(())
71    }
72}
73
74impl XousArguments {
75    pub fn new(ram_start: XousSize, ram_length: XousSize, ram_name: u32) -> XousArguments {
76        XousArguments { ram_start, ram_length, ram_name, arguments: vec![], detached_offset: None }
77    }
78
79    pub fn set_detached_offset(&mut self, offset: XousSize) { self.detached_offset = Some(offset); }
80
81    pub fn finalize(&mut self) {
82        let mut running_offset =
83            crate::tags::align_size_up(self.len() as usize, 0) + self.detached_offset.unwrap_or(0) as usize;
84        // println!("offset: {:x}, alignment_offset: {:x}", running_offset, 0);
85        for arg in &mut self.arguments {
86            running_offset = crate::tags::align_size_up(running_offset, arg.alignment_offset());
87            // println!("offset: {:x}", running_offset);
88            running_offset += arg.finalize(running_offset);
89        }
90    }
91
92    pub fn add<T: 'static>(&mut self, arg: T)
93    where
94        T: XousArgument + Sized,
95    {
96        self.arguments.push(Box::new(arg));
97    }
98
99    pub fn write<T>(&mut self, mut w: T) -> Result<()>
100    where
101        T: Write + Seek,
102    {
103        let total_length = self.len();
104
105        // Finalize the arguments.  This lets any tags update their offsets
106        // based on the size of the entire array.
107        self.finalize();
108
109        // XArg tag contents
110        let mut tag_data = Cursor::new(Vec::new());
111        tag_data.write_all(&((total_length / 4) as u32).to_le_bytes())?;
112        tag_data.write_all(&1u32.to_le_bytes())?; // Version
113        tag_data.write_all(&(self.ram_start as u32).to_le_bytes())?;
114        tag_data.write_all(&(self.ram_length as u32).to_le_bytes())?;
115        tag_data.write_all(&(self.ram_name as u32).to_le_bytes())?;
116
117        assert!(tag_data.get_ref().len().trailing_zeros() >= 2, "tag data was not a multiple of 4 bytes!");
118
119        let mut digest = crc16::Digest::new(crc16::X25);
120
121        // store the header offset
122        let header_offset = w.stream_position()?;
123
124        // XArg tag header
125        w.write_all(&u32::from_le_bytes(*b"XArg").to_le_bytes())?;
126        digest.write(tag_data.get_ref());
127        w.write_all(&digest.sum16().to_le_bytes())?; // CRC16
128        w.write_all(&((tag_data.get_ref().len() / 4) as u16).to_le_bytes())?; // Size (in words)
129        w.write_all(tag_data.get_ref())?;
130
131        // Write out each subsequent argument
132        for arg in &self.arguments {
133            let mut tag_data = Cursor::new(Vec::new());
134            let advertised_len = arg.length() as u32;
135            let actual_len = arg.serialize(&mut tag_data)? as u32;
136            assert_eq!(
137                advertised_len,
138                actual_len,
139                "argument {} advertised it would write {} bytes, but it wrote {} bytes",
140                arg.name(),
141                advertised_len,
142                actual_len
143            );
144            assert_eq!(
145                tag_data.get_ref().len() as u32,
146                actual_len,
147                "argument {} said it wrote {} bytes, but it actually wrote {} bytes",
148                arg.name(),
149                actual_len,
150                tag_data.get_ref().len()
151            );
152
153            let mut digest = crc16::Digest::new(crc16::X25);
154            // XArg tag header
155            w.write_all(&arg.code().to_le_bytes())?;
156            digest.write(tag_data.get_ref());
157            w.write_all(&digest.sum16().to_le_bytes())?; // CRC16
158            w.write_all(&((tag_data.get_ref().len() / 4) as u16).to_le_bytes())?; // Size (in words)
159            w.write_all(tag_data.get_ref())?;
160        }
161
162        // Write any pending data, such as payloads
163        for arg in &self.arguments {
164            // align for FLASH mapping
165            let pos = w.stream_position()?;
166            // find the next padding that allows us to align our data such that page sizes align.
167            let pad_len = crate::tags::align_size_up(pos as usize, arg.alignment_offset()) - pos as usize;
168            // println!("padding from {:x}, align {:x}, with {:x} bytes", pos, arg.alignment_offset(),
169            // pad_len);
170            let pad = vec![0u8; pad_len];
171            w.write_all(&pad)?;
172
173            // only do this check on the IniS section (swap generation). Rationale: the
174            // arg.load_offset() function is only implemented for IniS. It may be trivial to
175            // copy this to the other sections, but, the original idea was to make a targeted check of
176            // the swap format at this point in the image creation cycle.
177            if arg.code() == u32::from_le_bytes(*b"IniS") {
178                // println!("header_offset: {:x}", header_offset);
179                // - 0x0 is the valid offset in the case that we are being called by the encrypted partition
180                //   writer, since it computes offsets from the start of the encrypted partition.
181                // - 0x1000 is the expected length of the header when writing the full plaintext version for
182                //   sanity checking
183                assert!(
184                    header_offset == 0x1000 || header_offset == 0x0,
185                    "Header offset assumption for IniS was not met: loader hard-codes this size"
186                );
187                /*
188                println!(
189                    "load offset: {:x}, pos: {:x}",
190                    arg.load_offset(),
191                    w.stream_position()? - header_offset
192                ); */
193                // Debugging tips
194                // If this assert fails, what's happened is that the position of the loader stream
195                // is offset from where it's expected to be. There is a "just so" arrangement that isn't
196                // strictly enforced: the first IniS happens to load at 0x1000; and the XArgs block
197                // will generally fit within a size constraint of 0x1000. If either of these assumptions
198                // break, then, this assert will trigger.
199                assert!(
200                    arg.load_offset() as u64 == w.stream_position()? - header_offset,
201                    "IniS alignment assumption not satisfied, did XArgs overflow? \
202                    Did the IniS section trigger an alignment edge case with align_size_up()?"
203                );
204            }
205            // println!("position: {:x}", w.stream_position()?);
206            w.write_all(arg.last_data()).expect("couldn't write extra arg data");
207        }
208
209        Ok(())
210    }
211
212    pub fn len(&self) -> u32 {
213        let mut total_length = 20 + self.header_len() as u32; // 'XArg' plus tag length total length
214        for arg in &self.arguments {
215            total_length += arg.length() + 8;
216        }
217        total_length
218    }
219
220    pub fn is_empty(&self) -> bool { false }
221
222    pub fn header_len(&self) -> usize { 8 }
223}