Skip to main content

st_mem/
memory.rs

1use std::fs;
2use std::path::Path;
3
4/// A single memory region (FLASH or RAM).
5#[derive(Debug, Clone)]
6pub struct MemoryRegion {
7    pub name: String,
8    pub origin: u64,
9    pub length: u64,
10}
11
12/// Parsed memory configuration from a linker script (memory.x).
13#[derive(Debug, Clone)]
14pub struct MemoryConfig {
15    pub regions: Vec<MemoryRegion>,
16}
17
18impl MemoryConfig {
19    /// Parse a `memory.x` file at the given path.
20    ///
21    /// The file format follows GNU LD linker script syntax:
22    /// ```text
23    /// MEMORY
24    /// {
25    ///   FLASH : ORIGIN = 0x08000000, LENGTH = 64K
26    ///   RAM : ORIGIN = 0x20000000, LENGTH = 20K
27    /// }
28    /// ```
29    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, String> {
30        let content = fs::read_to_string(path.as_ref())
31            .map_err(|e| format!("Failed to read {}: {}", path.as_ref().display(), e))?;
32        Self::parse(&content)
33    }
34
35    /// Parse memory configuration from a string.
36    pub fn parse(content: &str) -> Result<Self, String> {
37        let mut regions = Vec::new();
38
39        for line in content.lines() {
40            let line = line.trim();
41
42            // Skip comments and empty lines
43            if line.is_empty()
44                || line.starts_with("/*")
45                || line.starts_with('*')
46                || line.starts_with("//")
47                || line.starts_with('#')
48            {
49                continue;
50            }
51
52            // Skip MEMORY { } wrapper lines
53            if line.starts_with("MEMORY") || line == "{" || line == "}" {
54                continue;
55            }
56
57            // Try to parse a region line:
58            //   FLASH : ORIGIN = 0x08000000, LENGTH = 64K
59            //   FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
60            if let Some(region) = parse_region_line(line) {
61                regions.push(region);
62            }
63        }
64
65        if regions.is_empty() {
66            return Err("No memory regions found in memory.x".to_string());
67        }
68
69        Ok(MemoryConfig { regions })
70    }
71
72    /// Find a region by name (case-insensitive).
73    pub fn find(&self, name: &str) -> Option<&MemoryRegion> {
74        let name_lower = name.to_lowercase();
75        self.regions.iter().find(|r| r.name.to_lowercase() == name_lower)
76    }
77
78    /// Get the FLASH region, if present.
79    pub fn flash(&self) -> Option<&MemoryRegion> {
80        self.find("FLASH")
81    }
82
83    /// Get the RAM region, if present.
84    pub fn ram(&self) -> Option<&MemoryRegion> {
85        self.find("RAM")
86    }
87}
88
89fn parse_region_line(line: &str) -> Option<MemoryRegion> {
90    // Format: NAME : ORIGIN = 0x..., LENGTH = ...
91    // or:     NAME (flags) : ORIGIN = 0x..., LENGTH = ...
92    let colon_pos = line.find(':')?;
93    let name_part = line[..colon_pos].trim();
94
95    // Extract name (strip attribute flags like (rx), (rwx), etc.)
96    let name = if let Some(paren_pos) = name_part.find('(') {
97        name_part[..paren_pos].trim().to_string()
98    } else {
99        name_part.to_string()
100    };
101
102    let rest = &line[colon_pos + 1..];
103
104    // Parse ORIGIN
105    let origin = parse_origin(rest)?;
106
107    // Parse LENGTH
108    let length = parse_length(rest)?;
109
110    Some(MemoryRegion { name, origin, length })
111}
112
113fn parse_origin(s: &str) -> Option<u64> {
114    let idx = s.find("ORIGIN")?;
115    let after = s[idx + "ORIGIN".len()..].trim();
116    let after = after.trim_start_matches('=').trim();
117    parse_number(after.split(|c: char| c == ',').next()?.trim())
118}
119
120fn parse_length(s: &str) -> Option<u64> {
121    let idx = s.find("LENGTH")?;
122    let after = s[idx + "LENGTH".len()..].trim();
123    let after = after.trim_start_matches('=').trim();
124    let val = after.split(|c: char| c == '}').next()?.trim();
125    let val = val.trim_end_matches(',').trim();
126    parse_size(val)
127}
128
129fn parse_number(s: &str) -> Option<u64> {
130    let s = s.trim();
131    if s.starts_with("0x") || s.starts_with("0X") {
132        u64::from_str_radix(&s[2..], 16).ok()
133    } else if s.starts_with("0o") || s.starts_with("0O") {
134        u64::from_str_radix(&s[2..], 8).ok()
135    } else if s.starts_with("0b") || s.starts_with("0B") {
136        u64::from_str_radix(&s[2..], 2).ok()
137    } else {
138        s.parse::<u64>().ok()
139    }
140}
141
142/// Parse a size string like "64K", "20K", "1M", "512", etc.
143pub fn parse_size(s: &str) -> Option<u64> {
144    let s = s.trim().to_uppercase();
145    if s.ends_with('K') {
146        let num: f64 = s[..s.len() - 1].trim().parse().ok()?;
147        Some((num * 1024.0) as u64)
148    } else if s.ends_with('M') {
149        let num: f64 = s[..s.len() - 1].trim().parse().ok()?;
150        Some((num * 1024.0 * 1024.0) as u64)
151    } else {
152        s.parse::<u64>().ok()
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_parse_basic() {
162        let content = r#"
163/* Linker script for the STM32F103C8T6 */
164MEMORY
165{
166  FLASH : ORIGIN = 0x08000000, LENGTH = 64K
167  RAM : ORIGIN = 0x20000000, LENGTH = 20K
168}
169"#;
170        let config = MemoryConfig::parse(content).unwrap();
171        assert_eq!(config.regions.len(), 2);
172
173        let flash = config.flash().unwrap();
174        assert_eq!(flash.origin, 0x08000000);
175        assert_eq!(flash.length, 64 * 1024);
176
177        let ram = config.ram().unwrap();
178        assert_eq!(ram.origin, 0x20000000);
179        assert_eq!(ram.length, 20 * 1024);
180    }
181
182    #[test]
183    fn test_parse_with_flags() {
184        let content = r#"
185MEMORY
186{
187  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
188  RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
189}
190"#;
191        let config = MemoryConfig::parse(content).unwrap();
192        let flash = config.flash().unwrap();
193        assert_eq!(flash.length, 128 * 1024);
194    }
195
196    #[test]
197    fn test_parse_size() {
198        assert_eq!(parse_size("64K"), Some(64 * 1024));
199        assert_eq!(parse_size("20K"), Some(20 * 1024));
200        assert_eq!(parse_size("1M"), Some(1024 * 1024));
201        assert_eq!(parse_size("512"), Some(512));
202        assert_eq!(parse_size("0.5K"), Some(512));
203    }
204}