ld_memory/
lib.rs

1use std::env;
2///! Create ld memory sections programmaticaly
3///
4/// This crate can be used in build.rs scripts to replace static memory.x files
5/// often used in MCU peripheral access crates.
6///
7/// It was first built to allow specifying a bootloader offset and splitting
8/// the remaining flash memory into "slots" for an active/passive updating
9/// scheme.
10///
11use std::num::ParseIntError;
12use std::path::Path;
13use std::result::Result;
14
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_string(&self) -> String {
33        let mut out = String::new();
34
35        // create symbols for each section start and length
36        for section in &self.sections {
37            out.push_str(&format!(
38                "_{}_start = {:#X};\n",
39                section.name, section.origin
40            ));
41            out.push_str(&format!(
42                "_{}_length = {:#X};\n",
43                section.name, section.length
44            ));
45        }
46
47        // if there was a section, add an empty line. all for pleasing human
48        // readers.
49        if !&self.sections.is_empty() {
50            out.push_str("\n");
51        }
52
53        out.push_str("MEMORY\n{\n");
54        for section in &self.sections {
55            out.push_str(&section.to_string());
56        }
57        out.push_str("}\n");
58        out
59    }
60
61    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()> {
62        std::fs::write(path, self.to_string())
63    }
64
65    #[cfg(feature = "build-rs")]
66    pub fn to_cargo_outdir(&self, filename: &str) -> std::io::Result<()> {
67        use std::path::PathBuf;
68
69        let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
70        self.to_file(out.join(filename))?;
71
72        println!("cargo:rustc-link-search={}", out.display());
73        Ok(())
74    }
75}
76
77pub struct MemorySection {
78    name: String,
79    attrs: Option<String>,
80    origin: u64,
81    length: u64,
82    pagesize: u64,
83}
84
85impl MemorySection {
86    pub fn new(name: &str, origin: u64, length: u64) -> MemorySection {
87        Self {
88            name: name.into(),
89            origin,
90            length,
91            attrs: None,
92            pagesize: 1,
93        }
94    }
95
96    pub fn offset(self, offset: u64) -> MemorySection {
97        Self {
98            name: self.name,
99            origin: self.origin + offset,
100            length: self.length - offset,
101            attrs: self.attrs,
102            pagesize: self.pagesize,
103        }
104    }
105
106    pub fn pagesize(self, pagesize: u64) -> MemorySection {
107        Self {
108            name: self.name,
109            origin: self.origin,
110            length: self.length,
111            attrs: self.attrs,
112            pagesize,
113        }
114    }
115
116    /// Divide memory section into slots.
117    ///
118    /// This can be used to divide a memory section into multiple slots of equal
119    /// size, e.g., for an active / passive image scheme on MCUs.
120    ///
121    /// `slot` starts at zero for the first slot.
122    pub fn slot(self, slot: usize, num_slots: usize) -> MemorySection {
123        assert!(slot < num_slots);
124
125        fn align_add(val: u64, alignment: u64) -> u64 {
126            if val % alignment != 0 {
127                (val + alignment) - val % alignment
128            } else {
129                val
130            }
131        }
132
133        fn align_sub(mut val: u64, alignment: u64) -> u64 {
134            val -= val % alignment;
135            val
136        }
137
138        // ensure both start and end are aligned with the pagesize
139        let origin = align_add(self.origin, self.pagesize);
140        let end = align_sub(self.origin + self.length, self.pagesize);
141
142        let slot_length = align_sub((end - origin) / num_slots as u64, self.pagesize);
143        let slot_origin = origin + (slot as u64 * slot_length);
144
145        Self {
146            name: self.name,
147            origin: slot_origin,
148            length: slot_length,
149            attrs: self.attrs,
150            pagesize: self.pagesize,
151        }
152    }
153
154    /// Read options from environment
155    ///
156    /// This will evaluate the following environment variables:
157    ///
158    /// |Variable              |Default|
159    /// |----------------------|-------|
160    /// |`LDMEMORY_OFFSET`     |      0|
161    /// |`LDMEMORY_PAGESIZE`   |      1|
162    /// |`LDMEMORY_NUM_SLOTS`  |      2|
163    /// |`LDMEMORY_SLOT_OFFSET`|      0|
164    /// |`LDMEMORY_SLOT`       |   None|
165    ///
166    /// If an offset is given, the whole section will be offset and shortened
167    /// by the given value.
168    /// If a pagesize is given, the slots will start and end will be aligned at
169    /// the pagesize.
170    /// If a slot number is given, the remaining section will be divided into
171    /// `<prefix>_NUM_SLOTS` slots, aligned to `<prefix>_PAGESIZE`, and the
172    /// `<prefix>_SLOT`th (starting at 0) will be returned.
173    /// If a slot offset is given, each slot will be offset and shortened by
174    /// that value.
175    ///
176    ///
177    /// Note: `from_env_with_prefix` can be used to use a different prefix than
178    /// the default prefix `LDMEMORY_`.
179    ///
180    pub fn from_env(self) -> MemorySection {
181        self.from_env_with_prefix("LDMEMORY")
182    }
183
184    /// Read slot options from environment with custom prefix
185    ///
186    /// See `from_env()`.
187    pub fn from_env_with_prefix(self, prefix: &str) -> MemorySection {
188        use std::env::var;
189        let offset_env = &[prefix, "OFFSET"].join("_");
190        let num_slots_env = &[prefix, "NUM_SLOTS"].join("_");
191        let slot_env = &[prefix, "SLOT"].join("_");
192        let pagesize_env = &[prefix, "PAGESIZE"].join("_");
193        let slot_offset_env = &[prefix, "SLOT_OFFSET"].join("_");
194
195        let mut res = self;
196        if let Ok(offset) = var(offset_env) {
197            let offset = offset
198                .parse_dec_or_hex()
199                .expect(&format!("parsing {}", &offset_env));
200            res = res.offset(offset);
201        }
202
203        if let Ok(pagesize) = var(pagesize_env) {
204            let pagesize = pagesize
205                .parse_dec_or_hex()
206                .expect(&format!("parsing {}", &pagesize_env));
207            res = res.pagesize(pagesize);
208        }
209
210        if let Ok(slot) = var(slot_env) {
211            let slot: usize = slot
212                .parse::<usize>()
213                .expect(&format!("parsing {}", slot_env));
214            let num_slots: usize = var(num_slots_env)
215                .unwrap_or("2".into())
216                .parse()
217                .expect(&format!("parsing {}", &num_slots_env));
218            let slot_offset = var(slot_offset_env)
219                .unwrap_or("0".into())
220                .parse_dec_or_hex()
221                .expect(&format!("parsing {}", &slot_offset_env));
222
223            res = res.slot(slot, num_slots);
224
225            if slot_offset > 0 {
226                res = res.offset(slot_offset);
227            }
228        }
229
230        // If being called by cargo, assume we're running from build.rs.
231        // Thus, print "cargo:rerun..." lines.
232        // Here we're assuming that if both CARGO and OUT_DIR is set, we're in
233        // build.rs.
234        if env::var("CARGO").is_ok() && env::var("OUT_DIR").is_ok() {
235            for var in [
236                offset_env,
237                num_slots_env,
238                slot_env,
239                slot_offset_env,
240                pagesize_env,
241            ]
242            .iter()
243            {
244                println!("cargo:rerun-if-env-changed={}", var);
245            }
246        }
247        res
248    }
249
250    pub fn attrs(self, attrs: &str) -> MemorySection {
251        Self {
252            name: self.name,
253            origin: self.origin,
254            length: self.length,
255            attrs: Some(attrs.into()),
256            pagesize: self.pagesize,
257        }
258    }
259
260    pub fn to_string(&self) -> String {
261        format!(
262            "    {} {}: ORIGIN = {:#X}, LENGTH = {:#X}\n",
263            self.name,
264            self.attrs
265                .as_ref()
266                .map_or_else(|| "".to_string(), |attrs| format!("({})", attrs)),
267            self.origin,
268            self.length
269        )
270    }
271}
272
273/// Helper trait to parse strings to usize from both decimal or hex
274pub trait ParseDecOrHex {
275    fn parse_dec_or_hex(&self) -> Result<u64, ParseIntError>;
276}
277
278impl ParseDecOrHex for str {
279    fn parse_dec_or_hex(&self) -> Result<u64, ParseIntError> {
280        if self.starts_with("0x") {
281            u64::from_str_radix(&self[2..], 16)
282        } else {
283            u64::from_str_radix(self, 10)
284        }
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::{Memory, MemorySection};
291    #[test]
292    fn basic_memory() {
293        let memory = Memory::new();
294        assert_eq!(memory.to_string(), "MEMORY\n{\n}\n");
295    }
296
297    #[test]
298    fn basic_section() {
299        let section = MemorySection::new("SectionName", 0, 0xFFFF);
300        assert_eq!(
301            section.to_string(),
302            "    SectionName : ORIGIN = 0x0, LENGTH = 0xFFFF\n"
303        );
304    }
305
306    #[test]
307    fn section_offset() {
308        let section = MemorySection::new("SectionName", 0, 0x10000).offset(0x1000);
309        assert_eq!(
310            section.to_string(),
311            "    SectionName : ORIGIN = 0x1000, LENGTH = 0xF000\n"
312        );
313    }
314
315    #[test]
316    fn section_attrs() {
317        let section = MemorySection::new("SectionName", 0, 0x10000).attrs("r!w!x");
318        assert_eq!(
319            section.to_string(),
320            "    SectionName (r!w!x): ORIGIN = 0x0, LENGTH = 0x10000\n"
321        );
322    }
323
324    #[test]
325    fn complex() {
326        let memory = Memory::new().add_section(
327            MemorySection::new("SectionName", 0, 0x10000)
328                .offset(0x1000)
329                .attrs("rw!x"),
330        );
331
332        assert_eq!(
333            memory.to_string(),
334            concat!(
335                "_SectionName_start = 0x1000;\n",
336                "_SectionName_length = 0xF000;\n",
337                "\n",
338                "MEMORY\n{\n",
339                "    SectionName (rw!x): ORIGIN = 0x1000, LENGTH = 0xF000\n",
340                "}\n"
341            )
342        );
343    }
344}