Skip to content

Commit 99e2b3f

Browse files
committed
Support blank offsets in partition tables
1 parent 90884cb commit 99e2b3f

File tree

1 file changed

+124
-12
lines changed

1 file changed

+124
-12
lines changed

espflash/src/partition_table.rs

Lines changed: 124 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,19 @@ impl PartitionTable {
197197
.trim(csv::Trim::All)
198198
.from_reader(data.trim().as_bytes());
199199

200+
// Default offset is 0x8000 in esp-idf, partition table size is 0x1000
201+
let mut offset = 0x9000;
200202
let mut partitions = Vec::with_capacity(data.lines().count());
201203
for record in reader.records() {
202204
let record = record.map_err(|e| CSVError::new(e, data.clone()))?;
203205
let position = record.position();
204-
let mut partition: Partition = record
206+
let mut partition: DeserializedPartition = record
205207
.deserialize(None)
206208
.map_err(|e| CSVError::new(e, data.clone()))?;
209+
210+
partition.fixup_offset(&mut offset);
211+
212+
let mut partition = Partition::from(partition);
207213
partition.line = position.map(|pos| pos.line() as usize);
208214
partitions.push(partition);
209215
}
@@ -310,17 +316,63 @@ impl PartitionTable {
310316
const PARTITION_SIZE: usize = 32;
311317

312318
#[derive(Debug, Deserialize)]
313-
pub struct Partition {
319+
pub struct DeserializedPartition {
314320
#[serde(deserialize_with = "deserialize_partition_name")]
315321
name: String,
316322
ty: Type,
317323
sub_type: SubType,
318-
#[serde(deserialize_with = "deserialize_partition_offset_or_size")]
324+
#[serde(deserialize_with = "deserialize_partition_offset")]
325+
offset: Option<u32>,
326+
#[serde(deserialize_with = "deserialize_partition_size")]
327+
size: u32,
328+
flags: Option<u32>,
329+
}
330+
331+
impl DeserializedPartition {
332+
fn align(offset: u32, ty: Type) -> u32 {
333+
let pad = match ty {
334+
Type::App => 0x10000,
335+
Type::Data => 4,
336+
};
337+
338+
if offset % pad != 0 {
339+
offset + pad - (offset % pad)
340+
} else {
341+
offset
342+
}
343+
}
344+
345+
fn fixup_offset(&mut self, offset: &mut u32) {
346+
if self.offset.is_none() {
347+
self.offset = Some(Self::align(*offset, self.ty));
348+
}
349+
350+
*offset = self.offset.unwrap() + self.size;
351+
}
352+
}
353+
354+
impl From<DeserializedPartition> for Partition {
355+
fn from(part: DeserializedPartition) -> Self {
356+
Partition {
357+
name: part.name,
358+
ty: part.ty,
359+
sub_type: part.sub_type,
360+
offset: part.offset.unwrap(),
361+
size: part.size,
362+
flags: part.flags,
363+
line: None,
364+
}
365+
}
366+
}
367+
368+
#[derive(Debug)]
369+
pub struct Partition {
370+
name: String,
371+
ty: Type,
372+
sub_type: SubType,
319373
offset: u32,
320-
#[serde(deserialize_with = "deserialize_partition_offset_or_size")]
321374
size: u32,
322375
flags: Option<u32>,
323-
#[serde(skip)]
324376
line: Option<usize>,
325377
}
326378

@@ -393,7 +445,7 @@ where
393445
Ok(maybe_truncated)
394446
}
395447

396-
fn deserialize_partition_offset_or_size<'de, D>(deserializer: D) -> Result<u32, D::Error>
448+
fn deserialize_partition_offset_or_size<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
397449
where
398450
D: Deserializer<'de>,
399451
{
@@ -404,17 +456,17 @@ where
404456

405457
// NOTE: Partitions of type 'app' must be placed at offsets aligned to 0x10000
406458
// (64K).
407-
// TODO: The specification states that offsets may be left blank, however that
408-
// is not presently supported in this implementation.
409-
if buf.starts_with("0x") {
459+
if buf.trim().is_empty() {
460+
Ok(None)
461+
} else if buf.starts_with("0x") {
410462
// Hexadecimal format
411463
let src = buf.trim_start_matches("0x");
412464
let size = u32::from_str_radix(src, 16).unwrap();
413465

414-
Ok(size)
466+
Ok(Some(size))
415467
} else if let Ok(size) = buf.parse::<u32>() {
416468
// Decimal format
417-
Ok(size)
469+
Ok(Some(size))
418470
} else if let Some(captures) = re.captures(&buf) {
419471
// Size multiplier format (1k, 2M, etc.)
420472
let digits = captures.get(1).unwrap().as_str().parse::<u32>().unwrap();
@@ -424,12 +476,28 @@ where
424476
_ => unreachable!(),
425477
};
426478

427-
Ok(digits * multiplier)
479+
Ok(Some(digits * multiplier))
428480
} else {
429481
Err(Error::custom("invalid partition size/offset format"))
430482
}
431483
}
432484

485+
fn deserialize_partition_offset<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
486+
where
487+
D: Deserializer<'de>,
488+
{
489+
deserialize_partition_offset_or_size(deserializer)
490+
}
491+
492+
fn deserialize_partition_size<'de, D>(deserializer: D) -> Result<u32, D::Error>
493+
where
494+
D: Deserializer<'de>,
495+
{
496+
use serde::de::Error;
497+
let deserialized = deserialize_partition_offset_or_size(deserializer)?;
498+
deserialized.ok_or_else(|| Error::custom("invalid partition size/offset format"))
499+
}
500+
433501
struct HashWriter<W: Write> {
434502
inner: W,
435503
hasher: Context,
@@ -480,6 +548,22 @@ phy_init, data, phy, 0xf000, 0x1000,
480548
factory, app, factory, 0x10000, 1M,
481549
ota_0, app, ota_0, 0x110000, 1M,
482550
ota_1, app, ota_1, 0x210000, 1M,
551+
";
552+
553+
const PTABLE_2: &str = "
554+
# ESP-IDF Partition Table
555+
# Name, Type, SubType, Offset, Size, Flags
556+
nvs, data, nvs, , 0x4000,
557+
phy_init, data, phy, , 0x1000,
558+
factory, app, factory, , 1M,
559+
";
560+
561+
const PTABLE_3: &str = "
562+
# ESP-IDF Partition Table
563+
# Name, Type, SubType, Offset, Size, Flags
564+
nvs, data, nvs, 0x10000, 0x4000,
565+
phy_init, data, phy, , 0x1000,
566+
factory, app, factory, , 1M,
483567
";
484568

485569
#[test]
@@ -517,4 +601,32 @@ ota_1, app, ota_1, 0x210000, 1M,
517601
let pt1 = PartitionTable::try_from_str(PTABLE_1);
518602
assert!(pt1.is_ok());
519603
}
604+
605+
#[test]
606+
fn blank_offsets_are_filled_in() {
607+
let pt2 = PartitionTable::try_from_str(PTABLE_2).expect("Failed to parse partition table with blank offsets");
608+
609+
assert_eq!(3, pt2.partitions.len());
610+
assert_eq!(0x4000, pt2.partitions[0].size);
611+
assert_eq!(0x1000, pt2.partitions[1].size);
612+
assert_eq!(0x100000, pt2.partitions[2].size);
613+
614+
assert_eq!(0x9000, pt2.partitions[0].offset);
615+
assert_eq!(0xd000, pt2.partitions[1].offset);
616+
assert_eq!(0x10000, pt2.partitions[2].offset);
617+
}
618+
619+
#[test]
620+
fn first_offsets_are_respected() {
621+
let pt3 = PartitionTable::try_from_str(PTABLE_3).expect("Failed to parse partition table with blank offsets");
622+
623+
assert_eq!(3, pt3.partitions.len());
624+
assert_eq!(0x4000, pt3.partitions[0].size);
625+
assert_eq!(0x1000, pt3.partitions[1].size);
626+
assert_eq!(0x100000, pt3.partitions[2].size);
627+
628+
assert_eq!(0x10000, pt3.partitions[0].offset);
629+
assert_eq!(0x14000, pt3.partitions[1].offset);
630+
assert_eq!(0x20000, pt3.partitions[2].offset);
631+
}
520632
}

0 commit comments

Comments
 (0)