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