Skip to main content

ld_memory/
lib.rs

1//! Create ld memory sections programmaticaly
2//!
3//! This crate can be used in build.rs scripts to replace static memory.x files
4//! often used in MCU peripheral access crates.
5//!
6//! It was first built to allow specifying a bootloader offset and splitting
7//! the remaining flash memory into "slots" for an active/passive updating
8//! scheme.
9use std::env;
10use std::num::ParseIntError;
11use std::path::Path;
12use std::result::Result;
13
14#[derive(Debug)]
15pub struct Memory {
16    sections: Vec<MemorySection>,
17}
18
19impl Memory {
20    pub fn new() -> Memory {
21        Memory {
22            sections: Vec::new(),
23        }
24    }
25
26    pub fn add_section(self, section: MemorySection) -> Memory {
27        let mut sections = self.sections;
28        sections.push(section);
29        Memory { sections }
30    }
31
32    pub fn to_ldmemory(&self) -> String {
33        let mut out = String::new();
34
35        // create symbols for each section start and length
36        out.extend(self.sections.iter().flat_map(|section| {
37            [
38                section.ldmemory_start_symbol(),
39                "\n".into(),
40                section.ldmemory_length_symbol(),
41                "\n".into(),
42            ]
43        }));
44
45        // if there was a section, add an empty line. all for pleasing human
46        // readers.
47        if !&self.sections.is_empty() {
48            out.push('\n');
49        }
50
51        out.push_str("MEMORY\n{\n");
52        for section in &self.sections {
53            out.push_str(&section.to_ldmemory());
54        }
55        out.push_str("}\n");
56        out
57    }
58
59    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()> {
60        std::fs::write(path, self.to_ldmemory())
61    }
62
63    /// Get a reference to this object's `Sections`.
64    pub fn sections(&self) -> &Vec<MemorySection> {
65        &self.sections
66    }
67
68    #[cfg(feature = "build-rs")]
69    pub fn to_cargo_outdir(&self, filename: &str) -> std::io::Result<()> {
70        use std::path::PathBuf;
71
72        let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
73        self.to_file(out.join(filename))?;
74
75        println!("cargo:rustc-link-search={}", out.display());
76        Ok(())
77    }
78}
79
80#[derive(Debug)]
81pub struct MemorySection {
82    name: String,
83    attrs: Option<String>,
84    origin: u64,
85    length: u64,
86    pagesize: u64,
87}
88
89impl Default for Memory {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95impl MemorySection {
96    pub fn new(name: impl Into<String>, origin: u64, length: u64) -> MemorySection {
97        Self {
98            name: name.into(),
99            origin,
100            length,
101            attrs: None,
102            pagesize: 1,
103        }
104    }
105
106    pub fn offset(self, offset: u64) -> MemorySection {
107        Self {
108            name: self.name,
109            origin: self.origin + offset,
110            length: self.length - offset,
111            attrs: self.attrs,
112            pagesize: self.pagesize,
113        }
114    }
115
116    pub fn pagesize(self, pagesize: u64) -> MemorySection {
117        Self {
118            name: self.name,
119            origin: self.origin,
120            length: self.length,
121            attrs: self.attrs,
122            pagesize,
123        }
124    }
125
126    /// Divide memory section into slots.
127    ///
128    /// This can be used to divide a memory section into multiple slots of equal
129    /// size, e.g., for an active / passive image scheme on MCUs.
130    ///
131    /// `slot` starts at zero for the first slot.
132    pub fn slot(self, slot: usize, num_slots: usize) -> MemorySection {
133        assert!(slot < num_slots);
134
135        fn align_add(val: u64, alignment: u64) -> u64 {
136            if val % alignment != 0 {
137                (val + alignment) - val % alignment
138            } else {
139                val
140            }
141        }
142
143        fn align_sub(mut val: u64, alignment: u64) -> u64 {
144            val -= val % alignment;
145            val
146        }
147
148        // ensure both start and end are aligned with the pagesize
149        let origin = align_add(self.origin, self.pagesize);
150        let end = align_sub(self.origin + self.length, self.pagesize);
151
152        let slot_length = align_sub((end - origin) / num_slots as u64, self.pagesize);
153        let slot_origin = origin + (slot as u64 * slot_length);
154
155        Self {
156            name: self.name,
157            origin: slot_origin,
158            length: slot_length,
159            attrs: self.attrs,
160            pagesize: self.pagesize,
161        }
162    }
163
164    /// Read options from environment
165    ///
166    /// This will evaluate the following environment variables:
167    ///
168    /// |Variable              |Default|
169    /// |----------------------|-------|
170    /// |`LDMEMORY_OFFSET`     |      0|
171    /// |`LDMEMORY_PAGESIZE`   |      1|
172    /// |`LDMEMORY_NUM_SLOTS`  |      2|
173    /// |`LDMEMORY_SLOT_OFFSET`|      0|
174    /// |`LDMEMORY_SLOT`       |   None|
175    ///
176    /// If an offset is given, the whole section will be offset and shortened
177    /// by the given value.
178    /// If a pagesize is given, the slots will start and end will be aligned at
179    /// the pagesize.
180    /// If a slot number is given, the remaining section will be divided into
181    /// `<prefix>_NUM_SLOTS` slots, aligned to `<prefix>_PAGESIZE`, and the
182    /// `<prefix>_SLOT`th (starting at 0) will be returned.
183    /// If a slot offset is given, each slot will be offset and shortened by
184    /// that value.
185    ///
186    ///
187    /// Note: `from_env_with_prefix` can be used to use a different prefix than
188    /// the default prefix `LDMEMORY_`.
189    ///
190    pub fn from_env(self) -> MemorySection {
191        self.from_env_with_prefix("LDMEMORY")
192    }
193
194    /// Read slot options from environment with custom prefix
195    ///
196    /// See `from_env()`.
197    pub fn from_env_with_prefix(self, prefix: &str) -> MemorySection {
198        use std::env::var;
199        let offset_env = &[prefix, "OFFSET"].join("_");
200        let num_slots_env = &[prefix, "NUM_SLOTS"].join("_");
201        let slot_env = &[prefix, "SLOT"].join("_");
202        let pagesize_env = &[prefix, "PAGESIZE"].join("_");
203        let slot_offset_env = &[prefix, "SLOT_OFFSET"].join("_");
204
205        let mut res = self;
206        if let Ok(offset) = var(offset_env) {
207            let offset = offset
208                .parse_dec_or_hex()
209                .unwrap_or_else(|_| panic!("parsing {}", &offset_env));
210            res = res.offset(offset);
211        }
212
213        if let Ok(pagesize) = var(pagesize_env) {
214            let pagesize = pagesize
215                .parse_dec_or_hex()
216                .unwrap_or_else(|_| panic!("parsing {}", &pagesize_env));
217            res = res.pagesize(pagesize);
218        }
219
220        if let Ok(slot) = var(slot_env) {
221            let slot: usize = slot
222                .parse::<usize>()
223                .unwrap_or_else(|_| panic!("parsing {}", slot_env));
224            let num_slots: usize = var(num_slots_env)
225                .unwrap_or("2".into())
226                .parse()
227                .unwrap_or_else(|_| panic!("parsing {}", &num_slots_env));
228            let slot_offset = var(slot_offset_env)
229                .unwrap_or("0".into())
230                .parse_dec_or_hex()
231                .unwrap_or_else(|_| panic!("parsing {}", &slot_offset_env));
232
233            res = res.slot(slot, num_slots);
234
235            if slot_offset > 0 {
236                res = res.offset(slot_offset);
237            }
238        }
239
240        // If being called by cargo, assume we're running from build.rs.
241        // Thus, print "cargo:rerun..." lines.
242        // Here we're assuming that if both CARGO and OUT_DIR is set, we're in
243        // build.rs.
244        if env::var("CARGO").is_ok() && env::var("OUT_DIR").is_ok() {
245            for var in [
246                offset_env,
247                num_slots_env,
248                slot_env,
249                slot_offset_env,
250                pagesize_env,
251            ]
252            .iter()
253            {
254                println!("cargo:rerun-if-env-changed={}", var);
255            }
256        }
257        res
258    }
259
260    pub fn attrs(self, attrs: &str) -> MemorySection {
261        Self {
262            name: self.name,
263            origin: self.origin,
264            length: self.length,
265            attrs: Some(attrs.into()),
266            pagesize: self.pagesize,
267        }
268    }
269
270    pub fn to_ldmemory(&self) -> String {
271        format!(
272            "    {} {}: ORIGIN = {:#X}, LENGTH = {:#X}\n",
273            self.name,
274            self.attrs
275                .as_ref()
276                .map_or_else(|| "".to_string(), |attrs| format!("({})", attrs)),
277            self.origin,
278            self.length
279        )
280    }
281
282    /// Creates a `String` with symbol for the start of the section
283    ///
284    /// ```
285    /// # use ld_memory::MemorySection;
286    /// let section = MemorySection::new("FLASH", 0, 4096);
287    /// assert_eq!(section.start_symbol(), "_FLASH_start");
288    /// ```
289    pub fn start_symbol(&self) -> String {
290        format!("_{}_start", self.name)
291    }
292
293    /// Creates a `String` with symbol for the length of the section
294    ///
295    /// ```
296    /// # use ld_memory::MemorySection;
297    /// let section = MemorySection::new("FLASH", 0, 4096);
298    /// assert_eq!(section.length_symbol(), "_FLASH_length");
299    /// ```
300    pub fn length_symbol(&self) -> String {
301        format!("_{}_length", self.name)
302    }
303
304    /// Creates the line for the linker script with the start of the symbol.
305    ///
306    /// ```
307    /// # use ld_memory::MemorySection;
308    /// let section = MemorySection::new("FLASH", 0, 4096);
309    /// assert_eq!(section.ldmemory_start_symbol(), "_FLASH_start = 0x0;");
310    /// ```
311    pub fn ldmemory_start_symbol(&self) -> String {
312        format!("{} = {:#X};", self.start_symbol(), self.origin)
313    }
314
315    /// Creates the line for the linker script with the length of the symbol.
316    ///
317    /// ```
318    /// # use ld_memory::MemorySection;
319    /// let section = MemorySection::new("FLASH", 0, 4096);
320    /// assert_eq!(section.ldmemory_length_symbol(), "_FLASH_length = 0x1000;");
321    /// ```
322    pub fn ldmemory_length_symbol(&self) -> String {
323        format!("{} = {:#X};", self.length_symbol(), self.length)
324    }
325
326    /// Get this section's name.
327    pub fn get_name(&self) -> &String {
328        &self.name
329    }
330
331    /// Get this section's `length`.
332    pub fn get_length(&self) -> u64 {
333        self.length
334    }
335
336    /// Get this section's `origin`.
337    pub fn get_origin(&self) -> u64 {
338        self.origin
339    }
340
341    /// Get this section's attributes.
342    pub fn get_attrs(&self) -> Option<&String> {
343        self.attrs.as_ref()
344    }
345
346    /// Get this section's pagesize.
347    pub fn get_pagesize(&self) -> u64 {
348        self.pagesize
349    }
350}
351
352/// Helper trait to parse strings to usize from both decimal or hex
353pub trait ParseDecOrHex {
354    fn parse_dec_or_hex(&self) -> Result<u64, ParseIntError>;
355}
356
357impl ParseDecOrHex for str {
358    fn parse_dec_or_hex(&self) -> Result<u64, ParseIntError> {
359        if let Some(hex) = self.strip_prefix("0x") {
360            u64::from_str_radix(hex, 16)
361        } else {
362            self.parse::<u64>()
363        }
364    }
365}
366
367#[cfg(feature = "parse")]
368pub mod parse {
369    pub fn parse_section(section_str: &str) -> std::result::Result<crate::MemorySection, String> {
370        let components = section_str.split(":").collect::<Vec<&str>>();
371        if components.len() < 3 {
372            return Err("invalid section spec (\"<NAME>:<START>:<SIZE>[:<OFFSET>]\")".into());
373        }
374
375        let (name, attrs) = parse_name_attrs(components[0]);
376        let mut section =
377            crate::MemorySection::new(name, parse_expr(components[1])?, parse_expr(components[2])?);
378
379        if !attrs.is_empty() {
380            section = section.attrs(attrs);
381        }
382
383        if components.len() == 4 {
384            section = section.offset(parse_expr(components[3])?);
385        }
386
387        Ok(section)
388    }
389
390    fn parse_name_attrs(input: &str) -> (&str, &str) {
391        let start_index = input.find('(');
392        let end_index = input.rfind(')');
393
394        if let (Some(start), Some(end)) = (start_index, end_index) {
395            let name = &input[..start];
396            let value = &input[start + 1..end];
397            (name.trim(), value)
398        } else {
399            (input, "")
400        }
401    }
402
403    fn parse_expr(expr: &str) -> Result<u64, String> {
404        if expr.is_empty() {
405            return Ok(0);
406        }
407
408        let expr = &apply_kilobyte(expr);
409
410        evalexpr::eval_int(expr)
411            .map_err(|e| e.to_string())
412            .and_then(|v| {
413                if v >= 0 {
414                    Ok(v as u64)
415                } else {
416                    Err("expression evaluates to negative integer".into())
417                }
418            })
419    }
420
421    fn apply_kilobyte(s: &str) -> String {
422        let mut result = String::new();
423        let mut chars = s.chars().peekable();
424
425        while let Some(c) = chars.next() {
426            if c.is_ascii_digit() {
427                let mut num_str = String::new();
428                num_str.push(c);
429
430                while let Some(next_c) = chars.peek() {
431                    if next_c.is_ascii_digit() {
432                        num_str.push(*next_c);
433                        chars.next();
434                    } else {
435                        break;
436                    }
437                }
438
439                if let Some('K') = chars.peek() {
440                    chars.next();
441                    let num = num_str.parse::<i32>().unwrap();
442                    let replacement = format!("({} * 1024)", num);
443                    result.push_str(&replacement);
444                } else {
445                    result.push_str(&num_str);
446                }
447            } else {
448                result.push(c);
449            }
450        }
451
452        result
453    }
454}
455
456#[cfg(test)]
457mod tests {
458    use super::{Memory, MemorySection};
459    #[test]
460    fn basic_memory() {
461        let memory = Memory::new();
462        assert_eq!(memory.to_ldmemory(), "MEMORY\n{\n}\n");
463    }
464
465    #[test]
466    fn basic_section() {
467        let section = MemorySection::new("SectionName", 0, 0xFFFF);
468        assert_eq!(
469            section.to_ldmemory(),
470            "    SectionName : ORIGIN = 0x0, LENGTH = 0xFFFF\n"
471        );
472    }
473
474    #[test]
475    fn section_offset() {
476        let section = MemorySection::new("SectionName", 0, 0x10000).offset(0x1000);
477        assert_eq!(
478            section.to_ldmemory(),
479            "    SectionName : ORIGIN = 0x1000, LENGTH = 0xF000\n"
480        );
481    }
482
483    #[test]
484    fn section_attrs() {
485        let section = MemorySection::new("SectionName", 0, 0x10000).attrs("r!w!x");
486        assert_eq!(
487            section.to_ldmemory(),
488            "    SectionName (r!w!x): ORIGIN = 0x0, LENGTH = 0x10000\n"
489        );
490    }
491
492    #[test]
493    fn complex() {
494        let memory = Memory::new().add_section(
495            MemorySection::new("SectionName", 0, 0x10000)
496                .offset(0x1000)
497                .attrs("rw!x"),
498        );
499
500        assert_eq!(
501            memory.to_ldmemory(),
502            concat!(
503                "_SectionName_start = 0x1000;\n",
504                "_SectionName_length = 0xF000;\n",
505                "\n",
506                "MEMORY\n{\n",
507                "    SectionName (rw!x): ORIGIN = 0x1000, LENGTH = 0xF000\n",
508                "}\n"
509            )
510        );
511    }
512}