osal_rs_build/lib.rs
1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
18 *
19 ***************************************************************************/
20
21use std::env;
22use std::fs;
23use std::path::PathBuf;
24use std::process::Command;
25
26pub struct FreeRtosTypeGenerator {
27 out_dir: PathBuf,
28 config_path: Option<PathBuf>,
29}
30
31impl FreeRtosTypeGenerator {
32 pub fn new() -> Self {
33 let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
34 Self {
35 out_dir,
36 config_path: None,
37 }
38 }
39
40 /// Create a new generator with a custom FreeRTOSConfig.h path
41 pub fn with_config_path<P: Into<PathBuf>>(config_path: P) -> Self {
42 let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
43 Self {
44 out_dir,
45 config_path: Some(config_path.into()),
46 }
47 }
48
49 /// Set the FreeRTOSConfig.h path
50 pub fn set_config_path<P: Into<PathBuf>>(&mut self, config_path: P) {
51 self.config_path = Some(config_path.into());
52 }
53
54 /// Query FreeRTOS type sizes and generate Rust type mappings
55 pub fn generate_types(&self) {
56 let (tick_size, ubase_size, base_size, base_signed, stack_size) = self.query_type_sizes();
57
58 let tick_type = Self::size_to_type(tick_size, false);
59 let ubase_type = Self::size_to_type(ubase_size, false);
60 let base_type = Self::size_to_type(base_size, base_signed);
61 let stack_type = Self::size_to_type(stack_size, true);
62
63
64 self.write_generated_types(tick_size, tick_type, ubase_size, ubase_type, base_size, base_type, stack_size, stack_type);
65
66 println!("cargo:warning=Generated FreeRTOS types: TickType={}, UBaseType={}, BaseType={} StackType={}",
67 tick_type, ubase_type, base_type, stack_type);
68 }
69
70 /// Query FreeRTOS configuration values and generate Rust constants
71 // pub fn generate_config(&self) {
72 // let (cpu_clock_hz, tick_rate_hz, max_priorities, minimal_stack_size, max_task_name_len) = self.query_config_values();
73
74 // self.write_generated_config(cpu_clock_hz, tick_rate_hz, max_priorities, minimal_stack_size, max_task_name_len);
75
76 // println!("cargo:warning=Generated FreeRTOS config: CPU={}Hz, Tick={}Hz, MaxPrio={}, MinStack={}, MaxTaskNameLen={}",
77 // cpu_clock_hz, tick_rate_hz, max_priorities, minimal_stack_size, max_task_name_len);
78 // }
79
80 /// Generate both types and config
81 pub fn generate_all(&self) {
82 self.generate_types();
83 // self.generate_config();
84 }
85
86 /// Query the sizes of FreeRTOS types
87 fn query_type_sizes(&self) -> (u16, u16, u16, bool, u16) {
88 // Create a small C program to query the type sizes
89 let query_program = r#"
90#include <stdio.h>
91#include <stdint.h>
92
93// We need to include FreeRTOS headers - path will be provided by the main build
94// For now, we'll use the compiled library approach
95// This is a placeholder - we'll use the already compiled C library
96
97int main() {
98 // Since we can't easily compile against FreeRTOS in the build script,
99 // we'll use a different approach: parse the compile_commands.json or
100 // use predefined types based on common configurations
101
102 // Common FreeRTOS configurations:
103 // TickType_t is usually uint32_t (4 bytes) on 32-bit systems
104 // UBaseType_t is usually uint32_t (4 bytes) on 32-bit systems
105 // BaseType_t is usually int32_t (4 bytes) on 32-bit systems
106 // StackType_t is usually long (4 bytes) on 32-bit systems
107
108 printf("TICK_TYPE_SIZE=%d\n", 4);
109 printf("UBASE_TYPE_SIZE=%d\n", 4);
110 printf("BASE_TYPE_SIZE=%d\n", 4);
111 printf("BASE_TYPE_SIGNED=1\n");
112 printf("STACK_TYPE_SIZE=%d\n", 4);
113
114 return 0;
115}
116"#;
117
118 let query_c = self.out_dir.join("query_types.c");
119 fs::write(&query_c, query_program).expect("Failed to write query program");
120
121 // Compile the query program
122 let query_exe = self.out_dir.join("query_types");
123 let compile_status = Command::new("gcc")
124 .arg(&query_c)
125 .arg("-o")
126 .arg(&query_exe)
127 .status();
128
129 if compile_status.is_ok() && compile_status.unwrap().success() {
130 // Run the query program
131 let output = Command::new(&query_exe)
132 .output()
133 .expect("Failed to run query program");
134
135 let stdout = String::from_utf8_lossy(&output.stdout);
136 let mut tick_size = 4u16;
137 let mut ubase_size = 4u16;
138 let mut base_size = 4u16;
139 let mut base_signed = true;
140 let mut stack_type = 4u16;
141
142 for line in stdout.lines() {
143 if let Some(val) = line.strip_prefix("TICK_TYPE_SIZE=") {
144 tick_size = val.parse().unwrap_or(4);
145 } else if let Some(val) = line.strip_prefix("UBASE_TYPE_SIZE=") {
146 ubase_size = val.parse().unwrap_or(4);
147 } else if let Some(val) = line.strip_prefix("BASE_TYPE_SIZE=") {
148 base_size = val.parse().unwrap_or(4);
149 } else if let Some(val) = line.strip_prefix("BASE_TYPE_SIGNED=") {
150 base_signed = val.parse::<u8>().unwrap_or(1) == 1;
151 } else if let Some(val) = line.strip_prefix("STACK_TYPE_SIZE=") {
152 stack_type = val.parse().unwrap_or(4);
153 }
154 }
155
156 (tick_size, ubase_size, base_size, base_signed, stack_type)
157 } else {
158 // Default values for 32-bit ARM Cortex-M (typical for Raspberry Pi Pico)
159 (4, 4, 4, true, 4)
160 }
161 }
162
163 /// Query FreeRTOS configuration values by parsing FreeRTOSConfig.h
164 // fn query_config_values(&self) -> (u64, u64, u64, u64, u64) {
165 // // Use the provided config path or try to auto-detect it
166 // let config_file = if let Some(ref path) = self.config_path {
167 // path.clone()
168 // } else {
169 // // Try to get the workspace root
170 // let workspace_root = env::var("CARGO_MANIFEST_DIR")
171 // .map(|p| PathBuf::from(p).parent().unwrap().parent().unwrap().to_path_buf())
172 // .ok();
173
174 // if let Some(root) = workspace_root {
175 // root.join("inc/FreeRTOSConfig.h")
176 // } else {
177 // // Fallback: try current directory
178 // PathBuf::from("FreeRTOSConfig.h")
179 // }
180 // };
181
182 // // Default values
183 // let mut cpu_clock_hz = 150_000_000u64;
184 // let mut tick_rate_hz = 1000u64;
185 // let mut max_priorities = 32u64;
186 // let mut minimal_stack_size = 512u64;
187 // let mut max_task_name_len = 16u64;
188
189 // // Try to parse the config file
190 // if config_file.exists() {
191 // if let Ok(contents) = fs::read_to_string(&config_file) {
192 // for line in contents.lines() {
193 // let line = line.trim();
194
195 // // Parse #define configCPU_CLOCK_HZ value
196 // if line.starts_with("#define") && line.contains("configCPU_CLOCK_HZ") {
197 // if let Some(value) = Self::extract_define_value(line) {
198 // cpu_clock_hz = value;
199 // }
200 // }
201 // // Parse #define configTICK_RATE_HZ value
202 // else if line.starts_with("#define") && line.contains("configTICK_RATE_HZ") {
203 // if let Some(value) = Self::extract_define_value(line) {
204 // tick_rate_hz = value;
205 // }
206 // }
207 // // Parse #define configMAX_PRIORITIES value
208 // else if line.starts_with("#define") && line.contains("configMAX_PRIORITIES") {
209 // if let Some(value) = Self::extract_define_value(line) {
210 // max_priorities = value;
211 // }
212 // }
213 // // Parse #define configMINIMAL_STACK_SIZE value
214 // else if line.starts_with("#define") && line.contains("configMINIMAL_STACK_SIZE") {
215 // if let Some(value) = Self::extract_define_value(line) {
216 // minimal_stack_size = value;
217 // }
218 // }
219 // // Parse #define configMAX_TASK_NAME_LEN value
220 // else if line.starts_with("#define") && line.contains("configMAX_TASK_NAME_LEN") {
221 // if let Some(value) = Self::extract_define_value(line) {
222 // max_task_name_len = value;
223 // }
224 // }
225 // }
226 // println!("cargo:warning=Successfully parsed FreeRTOS config from {}", config_file.display());
227 // } else {
228 // println!("cargo:warning=Failed to read FreeRTOS config file, using defaults");
229 // }
230 // } else {
231 // println!("cargo:warning=FreeRTOS config file not found at {}, using defaults", config_file.display());
232 // }
233
234 // (cpu_clock_hz, tick_rate_hz, max_priorities, minimal_stack_size, max_task_name_len)
235 // }
236
237 /// Extract numeric value from a #define line
238 // fn extract_define_value(line: &str) -> Option<u64> {
239 // // Split by whitespace and get the value part (after the macro name)
240 // let parts: Vec<&str> = line.split_whitespace().collect();
241 // if parts.len() >= 3 {
242 // let value_str = parts[2];
243 // // Remove common suffixes and parentheses
244 // let cleaned = value_str
245 // .trim_end_matches('U')
246 // .trim_end_matches('L')
247 // .trim_matches('(')
248 // .trim_matches(')');
249
250 // // Try to parse as decimal or hex
251 // if let Ok(val) = cleaned.parse::<u64>() {
252 // return Some(val);
253 // }
254 // // Try hex format (0x...)
255 // if cleaned.starts_with("0x") || cleaned.starts_with("0X") {
256 // if let Ok(val) = u64::from_str_radix(&cleaned[2..], 16) {
257 // return Some(val);
258 // }
259 // }
260 // }
261 // None
262 // }
263
264 /// Convert a size to the corresponding Rust type
265 fn size_to_type(size: u16, signed: bool) -> &'static str {
266 match (size, signed) {
267 (1, false) => "u8",
268 (1, true) => "i8",
269 (2, false) => "u16",
270 (2, true) => "i16",
271 (4, false) => "u32",
272 (4, true) => "i32",
273 (8, false) => "u64",
274 (8, true) => "i64",
275 // Default to u32 for unknown sizes
276 _ => if signed { "i32" } else { "u32" },
277 }
278 }
279
280 /// Write the generated types to a file
281 fn write_generated_types(
282 &self,
283 tick_size: u16,
284 tick_type: &str,
285 ubase_size: u16,
286 ubase_type: &str,
287 base_size: u16,
288 base_type: &str,
289 stack_size: u16,
290 stack_type: &str,
291 ) {
292 let generated_code = format!(r#"
293// Auto-generated by build.rs - DO NOT EDIT MANUALLY
294// This file contains FreeRTOS type mappings based on the actual type sizes
295
296// FreeRTOS type mappings (auto-detected)
297// TickType_t: {} bytes -> {}
298// UBaseType_t: {} bytes -> {}
299// BaseType_t: {} bytes -> {}
300// StackType_t: {} bytes -> {}
301
302pub type TickType = {};
303pub type UBaseType = {};
304pub type BaseType = {};
305pub type StackType = {};
306
307"#,
308 tick_size, tick_type,
309 ubase_size, ubase_type,
310 base_size, base_type,
311 stack_size, stack_type,
312 tick_type,
313 ubase_type,
314 base_type,
315 stack_type
316 );
317
318 let types_rs = self.out_dir.join("types_generated.rs");
319 fs::write(&types_rs, generated_code).expect("Failed to write generated types");
320 }
321
322// /// Write the generated config constants to a file
323// fn write_generated_config(
324// &self,
325// cpu_clock_hz: u64,
326// tick_rate_hz: u64,
327// max_priorities: u64,
328// minimal_stack_size: u64,
329// max_task_name_len: u64,
330// ) {
331// let generated_code = format!(r#"
332// // Auto-generated by build.rs - DO NOT EDIT MANUALLY
333// // This file contains FreeRTOS configuration constants
334
335// /// FreeRTOS CPU clock frequency in Hz
336// pub const CPU_CLOCK_HZ: u64 = {};
337
338// /// FreeRTOS tick rate in Hz
339// pub const TICK_RATE_HZ: u64 = {};
340
341// /// Maximum number of FreeRTOS priorities
342// pub const MAX_PRIORITIES: u64 = {};
343
344// /// Minimal stack size for FreeRTOS tasks
345// pub const MINIMAL_STACK_SIZE: u64 = {};
346
347// /// Maximum task name length
348// pub const MAX_TASK_NAME_LEN: u64 = {};
349// "#,
350// cpu_clock_hz,
351// tick_rate_hz,
352// max_priorities,
353// minimal_stack_size,
354// max_task_name_len
355// );
356
357// let config_rs = self.out_dir.join("config_generated.rs");
358// fs::write(&config_rs, generated_code).expect("Failed to write generated config");
359// }
360}
361
362impl Default for FreeRtosTypeGenerator {
363 fn default() -> Self {
364 Self::new()
365 }
366}