amebazii/types/flash.rs
1// Section 5.2 - Layout
2// ┌──────────────────┐
3// │ Partition Table │ 0x00000020 - 0x00001000
4// ├──────────────────┤
5// │ System Data │ 0x00001000 - 0x00002000
6// ├──────────────────┤
7// │ Calibration Data │ 0x00002000 - 0x00003000
8// ├──────────────────┤
9// │ Reserved │ 0x00003000 - 0x00004000
10// ├──────────────────┤
11// │ │
12// │ Boot Image │ 0x00004000 - 0x0000C000
13// │ │
14// ├──────────────────┤
15// │ │
16// │ │
17// │ Firmware 1 │ 0x0000C000
18// │ │
19// │ │
20// ├──────────────────┤
21// │ │
22// │ │
23// │ Firmware 2 │
24// │ │
25// │ │
26// ├──────────────────┤
27// │ │
28// │ User Data │
29// │ │
30// └──────────────────┘
31
32use std::{collections::HashMap, io};
33
34use crate::{
35 error::Error,
36 read_padding,
37 types::{image::EncryptedOr, PartTab},
38 util::write_fill,
39 write_aligned, write_padding,
40};
41
42use super::{
43 enums::PartitionType,
44 from_stream,
45 image::{
46 boot,
47 ota::{self},
48 pt::{self, Record},
49 RawImage,
50 },
51 sysctrl::SystemData,
52 FromStream, ToStream,
53};
54
55/// Represents different types of partitions in a flash image.
56#[derive(Debug)]
57pub enum Partition {
58 PartitionTable(pt::PartitionTableImage),
59 Bootloader(boot::BootImage),
60 Calibration,
61 Fw1(ota::OTAImage),
62 Fw2(ota::OTAImage),
63 Reserved,
64 Var(RawImage),
65 System(SystemData),
66 User(RawImage),
67 Mp(RawImage),
68}
69
70impl Partition {
71 /// Reads the raw image data from the reader based on the provided record size.
72 ///
73 /// # Parameters:
74 /// - `reader`: The input stream to read from.
75 /// - `record_size`: The size of the record to read.
76 ///
77 /// # Returns:
78 /// - A `RawImage` containing the raw data read from the stream.
79 fn read_raw_image<R>(reader: &mut R, record_size: u32) -> Result<RawImage, Error>
80 where
81 R: io::Read + io::Seek,
82 {
83 let mut buffer = Vec::with_capacity(record_size as usize);
84 reader.read_to_end(&mut buffer)?;
85 Ok(buffer)
86 }
87
88 /// Creates a `Partition` from a `Record` and a reader stream.
89 ///
90 /// This function reads the appropriate partition data based on the partition type from the
91 /// provided record and reader. The partition type is matched and the corresponding partition
92 /// image is created from the reader.
93 ///
94 /// # Parameters:
95 /// - `record`: The partition record containing metadata (e.g., partition type and length).
96 /// - `reader`: The input stream to read the partition data from.
97 ///
98 /// # Returns:
99 /// - A `Partition` variant matching the partition type from the `record`.
100 pub fn from_record<R>(record: &Record, reader: &mut R) -> Result<Self, Error>
101 where
102 R: io::Read + io::Seek,
103 {
104 match &record.part_type {
105 PartitionType::PartTab => Ok(Partition::PartitionTable(from_stream(reader)?)),
106 PartitionType::Boot => Ok(Partition::Bootloader(from_stream(reader)?)),
107 PartitionType::Fw1 => Ok(Partition::Fw1(from_stream(reader)?)),
108 PartitionType::Fw2 => Ok(Partition::Fw2(from_stream(reader)?)),
109 PartitionType::Cal => Ok(Partition::Calibration),
110 PartitionType::Sys => Ok(Partition::System(from_stream(reader)?)),
111 PartitionType::User => Ok(Partition::User(Self::read_raw_image(
112 reader,
113 record.length,
114 )?)),
115 PartitionType::Var => Ok(Partition::Var(Self::read_raw_image(reader, record.length)?)),
116 PartitionType::MP => Ok(Partition::Mp(Self::read_raw_image(reader, record.length)?)),
117 PartitionType::Rdp => Ok(Partition::Reserved),
118 PartitionType::Unknown => Err(Error::InvalidEnumValue(format!(
119 "Invalid partition type: {:?}",
120 record.part_type
121 ))),
122 }
123 }
124}
125
126impl ToStream for Partition {
127 fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
128 where
129 W: io::Write + io::Seek,
130 {
131 match self {
132 Partition::PartitionTable(pt) => pt.write_to(writer)?,
133 Partition::Bootloader(bt) => bt.write_to(writer)?,
134 Partition::Calibration => (),
135 Partition::Fw1(fw1) => fw1.write_to(writer)?,
136 Partition::Fw2(fw2) => fw2.write_to(writer)?,
137 Partition::Reserved => (),
138 Partition::Var(var) => writer.write_all(var)?,
139 Partition::System(sys) => sys.write_to(writer)?,
140 Partition::User(user) => writer.write_all(user)?,
141 Partition::Mp(mp) => writer.write_all(mp)?,
142 }
143
144 Ok(())
145 }
146}
147
148/// Represents a flash image, including calibration data and partitions.
149pub struct Flash {
150 /// A 16-byte calibration pattern used for calibration data.
151 calibration_pattern: [u8; 16],
152
153 /// A `HashMap` storing partitions indexed by their `PartitionType`.
154 partitions: HashMap<PartitionType, Partition>,
155}
156
157impl Default for Flash {
158 fn default() -> Self {
159 Self {
160 calibration_pattern: [0; 16],
161 partitions: HashMap::new(),
162 }
163 }
164}
165
166impl Flash {
167 /// Returns a reference to the calibration pattern.
168 ///
169 /// This function is used to retrieve the 16-byte calibration pattern for the flash image.
170 ///
171 /// # Returns:
172 /// - A reference to the 16-byte array holding the calibration pattern.
173 pub fn get_calibration_pattern(&self) -> &[u8; 16] {
174 &self.calibration_pattern
175 }
176
177 /// Returns a mutable reference to the calibration pattern.
178 ///
179 /// This function allows modification of the calibration pattern for the flash image.
180 ///
181 /// # Returns:
182 /// - A mutable reference to the 16-byte array holding the calibration pattern.
183 pub fn get_calibration_pattern_mut(&mut self) -> &mut [u8; 16] {
184 &mut self.calibration_pattern
185 }
186
187 /// Retrieves a partition by its type.
188 ///
189 /// # Parameters:
190 /// - `part_type`: The partition type to search for in the flash.
191 ///
192 /// # Returns:
193 /// - `Some(&Partition)` if the partition exists, otherwise `None`.
194 pub fn get_partition(&self, part_type: PartitionType) -> Option<&Partition> {
195 self.partitions.get(&part_type)
196 }
197
198 /// Checks whether a partition of the specified type exists.
199 ///
200 /// # Parameters:
201 /// - `part_type`: The partition type to check.
202 ///
203 /// # Returns:
204 /// - `true` if the partition exists, otherwise `false`.
205 pub fn has_partition(&self, part_type: PartitionType) -> bool {
206 self.partitions.contains_key(&part_type)
207 }
208
209 /// Sets the partition for the specified type.
210 ///
211 /// # Parameters:
212 /// - `part_type`: The type of partition to set.
213 /// - `partition`: The partition data to store.
214 pub fn set_partition(&mut self, part_type: PartitionType, partition: Partition) {
215 self.partitions.insert(part_type, partition);
216 }
217
218 /// Sets the system partition with the provided system data.
219 ///
220 /// # Arguments
221 ///
222 /// * `system_data` - The data to be written to the system partition, represented as `SystemData`.
223 pub fn set_system_partition(&mut self, system_data: SystemData) {
224 self.set_partition(PartitionType::Sys, Partition::System(system_data));
225 }
226
227 /// Sets the boot partition with the given boot image.
228 ///
229 /// # Arguments
230 ///
231 /// * `boot_image` - A `BootImage` object containing the bootloader image data.
232 pub fn set_boot_partition(&mut self, boot_image: boot::BootImage) {
233 self.set_partition(PartitionType::Boot, Partition::Bootloader(boot_image));
234 }
235
236 /// Sets the first firmware partition with the provided firmware image.
237 ///
238 /// This method configures the first firmware partition (`Fw1`) by passing an `OTAImage` object
239 /// representing the firmware image to the internal `set_partition` method.
240 ///
241 /// # Arguments
242 ///
243 /// * `fw1_image` - An `OTAImage` object containing the first firmware image to be stored.
244 pub fn set_fw1(&mut self, fw1_image: ota::OTAImage) {
245 self.set_partition(PartitionType::Fw1, Partition::Fw1(fw1_image));
246 }
247
248 /// Sets the second firmware partition with the given firmware image.
249 ///
250 /// This method configures the second firmware partition (`Fw2`) by passing an `OTAImage` object
251 /// representing the firmware image. It ensures that the firmware data is correctly placed in the
252 /// `Fw2` partition.
253 ///
254 /// # Arguments
255 ///
256 /// * `fw2_image` - An `OTAImage` object containing the second firmware image to be stored.
257 pub fn set_fw2(&mut self, fw2_image: ota::OTAImage) {
258 self.set_partition(PartitionType::Fw2, Partition::Fw2(fw2_image));
259 }
260
261 /// Sets the partition table with the provided partition table image.
262 ///
263 /// # Arguments
264 ///
265 /// * `pt_image` - A `PartitionTableImage` object representing the partition table to be stored.
266 pub fn set_partition_table(&mut self, pt_image: pt::PartitionTableImage) {
267 self.set_partition(PartitionType::PartTab, Partition::PartitionTable(pt_image));
268 }
269}
270
271impl FromStream for Flash {
272 /// Reads the flash image from the provided reader.
273 ///
274 /// This function reads the entire flash image, including the calibration pattern, partitions,
275 /// and partition records. It populates the `Flash` struct with the data read from the stream.
276 ///
277 /// # Parameters:
278 /// - `reader`: The input stream from which the flash image is read.
279 ///
280 /// # Returns:
281 /// - `Ok(())` if the flash image was successfully read and parsed.
282 /// - `Err(Error)` if there was an issue reading the flash image.
283 fn read_from<R>(&mut self, reader: &mut R) -> Result<(), Error>
284 where
285 R: io::Read + io::Seek,
286 {
287 reader.read_exact(&mut self.calibration_pattern)?;
288 read_padding!(reader, 16);
289
290 let pt_image: pt::PartitionTableImage = from_stream(reader)?;
291 if let EncryptedOr::Plain(pt) = &pt_image.pt {
292 for record in pt.get_records() {
293 reader.seek(io::SeekFrom::Start(record.start_addr as u64))?;
294 self.set_partition(record.part_type, Partition::from_record(record, reader)?);
295 }
296 }
297 self.set_partition(PartitionType::PartTab, Partition::PartitionTable(pt_image));
298 Ok(())
299 }
300}
301
302impl ToStream for Flash {
303 /// Writes the flash image to the provided writer.
304 ///
305 /// This function writes the entire flash image, including the calibration pattern, partitions,
306 /// and partition records. It populates the `Flash` struct with the data read from the stream.
307 ///
308 /// # Parameters:
309 /// - `writer`: The output stream to which the flash image is written.
310 ///
311 /// # Returns:
312 /// - `Ok(())` if the flash image was successfully written.
313 /// - `Err(Error)` if there was an issue writing the flash image.
314 fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
315 where
316 W: io::Write + io::Seek,
317 {
318 writer.write_all(&self.calibration_pattern)?;
319 write_padding!(writer, 16);
320
321 let pt_image = self.partitions.get(&PartitionType::PartTab);
322 if pt_image.is_none() {
323 return Err(Error::InvalidState("Partition table not found".to_string()));
324 }
325
326 let pt_image = pt_image.unwrap();
327 pt_image.write_to(writer)?;
328 write_aligned!(writer, 0x1000);
329
330 // system partition is mandatory
331 let system = self.partitions.get(&PartitionType::Sys);
332 if system.is_none() {
333 return Err(Error::InvalidState(
334 "System partition not found".to_string(),
335 ));
336 }
337 system.unwrap().write_to(writer)?;
338 // we don't have to align here, because the system partition already fills up
339 // the space
340
341 // calibration data: reserved
342 // reserved (backup sector for write operations)
343 write_padding!(writer, 0x2000);
344
345 // even though the next sections are mandatory, we use the records within the
346 // partition table to populate the flash image
347 if let Partition::PartitionTable(pt_image) = pt_image {
348 let pt = &pt_image.pt;
349 if pt.is_encrypted() {
350 return Err(Error::NotImplemented(
351 "Encrypted partition table is not supported".to_string(),
352 ));
353 }
354
355 // the order must be preserved (BUT it is not checked here)
356 let pt: &PartTab = pt.as_ref();
357 self.write_partition(writer, pt, PartitionType::Boot)?;
358 self.write_partition(writer, pt, PartitionType::Fw1)?;
359 self.write_partition(writer, pt, PartitionType::Fw2)?;
360 self.write_partition(writer, pt, PartitionType::User)?;
361 }
362 Ok(())
363 }
364}
365
366impl Flash {
367 /// Fills the stream with padding up to the specified offset.
368 ///
369 /// # Arguments:
370 /// - `writer`: A mutable reference to a writer that implements the `std::io::Write` and
371 /// `std::io::Seek` traits.
372 /// - `offset`: The offset to fill up to.
373 ///
374 /// # Returns:
375 /// - `Ok(())` if the write operation is successful.
376 /// - `Err(Error)` if there is an error during the write operation.
377 fn fill_to_offset<W>(&self, writer: &mut W, offset: u64) -> Result<(), Error>
378 where
379 W: io::Write + io::Seek,
380 {
381 let pos = writer.stream_position()?;
382 if pos > offset {
383 return Err(Error::InvalidState(format!(
384 "Cannot fill to offset {}, current position is {}",
385 offset, pos
386 )));
387 }
388
389 write_padding!(writer, offset - pos);
390 Ok(())
391 }
392
393 /// Writes a partition to the stream (if present).
394 ///
395 /// # Arguments:
396 /// - `writer`: A mutable reference to a writer that implements the `std::io::Write` and
397 /// `std::io::Seek` traits.
398 /// - `pt`: A reference to the partition table.
399 /// - `part_type`: The type of the partition to write.
400 ///
401 /// # Returns:
402 /// - `Ok(())` if the write operation is successful.
403 /// - `Err(Error)` if there is an error during the write operation.
404 fn write_partition<W>(
405 &self,
406 writer: &mut W,
407 pt: &PartTab,
408 part_type: PartitionType,
409 ) -> Result<(), Error>
410 where
411 W: io::Write + io::Seek,
412 {
413 if let Some(record) = pt.get_record(part_type) {
414 self.fill_to_offset(writer, record.start_addr as u64)?;
415 if let Some(partition) = self.partitions.get(&record.part_type) {
416 partition.write_to(writer)?;
417 } else {
418 return Err(Error::InvalidState(format!(
419 "Partition with type '{:?}' not found (is mandatory)",
420 record.part_type
421 )));
422 }
423 }
424 Ok(())
425 }
426}