freertos_build/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub mod prelude;
4
5use cc::Build;
6use fugit::HertzU32;
7use prelude::*;
8use std::env;
9use std::ffi::OsStr;
10use std::path::{Path, PathBuf};
11use walkdir::WalkDir;
12
13const ENV_KEY_CRATE_DIR: &str = "DEP_FREERTOS_CRATE_DIR";
14
15#[derive(Debug, Clone)]
16pub struct Builder {
17    freertos_dir: PathBuf,
18    config_dir: PathBuf,
19    user_config_dir: Option<PathBuf>,
20    shim_file: PathBuf,
21    freertos_port: Option<PathBuf>,
22    freertos_port_base: Option<PathBuf>,
23    /// name of the heap_?.c file
24    heap_c: PathBuf,
25    cc: Build,
26    cpu_clock: HertzU32,
27    heap_size: usize,
28    /// in words
29    minimal_stack_size: usize,
30    max_priorities: u8,
31    timer_task_config: Option<TimerTaskConfig>,
32    use_preemption: bool,
33    idle_should_yield: Option<bool>,
34    interrupt_priority_bits: Option<InterruptPriorityBits>,
35    interrupt_priority: Option<InterruptPriority>,
36    max_task_name_len: Option<usize>,
37    queue_registry_size: Option<usize>,
38    check_for_stack_overflow: Option<u8>,
39}
40
41#[derive(Debug, Clone, Copy)]
42struct TimerTaskConfig {
43    priority: u8,
44    queue_length: usize,
45    stack_depth: usize,
46}
47
48#[derive(Debug, Clone, Copy)]
49struct InterruptPriorityBits {
50    bits: u8,
51    lowest_priority: u32,
52    max_syscall_priority: u32,
53}
54
55#[derive(Debug, Clone, Copy)]
56struct InterruptPriority {
57    lowest_priority: u32,
58    max_syscall_priority: u32,
59}
60
61#[derive(Debug)]
62pub enum Error {
63    /// More explanation of error that occurred.
64    Message(String),
65}
66
67impl Error {
68    fn new(message: &str) -> Self {
69        Self::Message(message.to_owned())
70    }
71}
72
73impl Default for Builder {
74    fn default() -> Self {
75        let crate_dir = PathBuf::from(env::var(ENV_KEY_CRATE_DIR).unwrap());
76
77        Self {
78            freertos_dir: crate_dir.join("FreeRTOS-Kernel"),
79            shim_file: crate_dir.join("src/freertos/shim.c"),
80            config_dir: crate_dir.join("src/config"),
81            user_config_dir: None,
82            freertos_port: None,
83            freertos_port_base: None,
84            cc: cc::Build::new(),
85            heap_c: PathBuf::from("heap_4.c"),
86            cpu_clock: 0.Hz(),
87            heap_size: 16 * 1024,
88            minimal_stack_size: 80,
89            max_priorities: 5,
90            timer_task_config: None,
91            use_preemption: true,
92            idle_should_yield: None,
93            interrupt_priority_bits: None,
94            interrupt_priority: None,
95            max_task_name_len: None,
96            queue_registry_size: None,
97            check_for_stack_overflow: None,
98        }
99    }
100}
101
102macro_rules! set_define {
103    ($cc:ident, $def:expr, $v:expr) => {
104        $cc.define($def, $v.to_string().as_str());
105    };
106    (bool, $cc:ident, $def:expr, $v:expr) => {
107        let v = if $v { 1 } else { 0 };
108        $cc.define($def, v.to_string().as_str());
109    };
110}
111
112impl Builder {
113    /// Construct a new instance of a blank set of configuration.
114    ///
115    /// This builder is finished with the [`compile`] function.
116    ///
117    /// [`compile`]: struct.Build.html#method.compile
118    pub fn new() -> Builder {
119        Self::default()
120    }
121
122    /// Set the path to FreeRTOS-Kernel source files
123    pub fn freertos_kernel<P: AsRef<Path>>(&mut self, path: P) {
124        self.freertos_dir = path.as_ref().to_path_buf();
125    }
126
127    /// Set the path to the directory of `FreeRTOSConfig.h`
128    pub fn freertos_config_dir<P: AsRef<Path>>(&mut self, path: P) {
129        self.config_dir = path.as_ref().to_path_buf();
130    }
131
132    /// Set the path to the directory of `UserConfig.h`
133    pub fn user_config_dir<P: AsRef<Path>>(&mut self, path: P) {
134        self.user_config_dir = Some(path.as_ref().to_path_buf());
135    }
136
137    /// Set the path to shim.c
138    pub fn shim_file<P: AsRef<Path>>(&mut self, path: P) {
139        self.shim_file = path.as_ref().to_path_buf();
140    }
141
142    /// Returns a list of all FreeRTOS source files
143    fn freertos_files(&self) -> Vec<PathBuf> {
144        let files: Vec<_> = WalkDir::new(self.freertos_dir.as_path())
145            .follow_links(false)
146            .max_depth(1)
147            .into_iter()
148            .filter_map(|e| e.ok())
149            .filter_map(|entry| {
150                let f_name = entry.path().to_str().unwrap();
151
152                if f_name.ends_with(".c") {
153                    return Some(entry.path().to_owned());
154                }
155                None
156            })
157            .collect();
158        files
159    }
160    fn freertos_port_files(&self) -> Vec<PathBuf> {
161        let files: Vec<_> = WalkDir::new(self.get_freertos_port_dir())
162            .follow_links(false)
163            .into_iter()
164            .filter_map(|e| e.ok())
165            .filter_map(|entry| {
166                match entry
167                    .path()
168                    .extension()
169                    .map(|s| s.to_string_lossy())
170                    .as_ref()
171                    .map(|s| s.as_ref())
172                {
173                    Some("c" | "s" | "S") => Some(entry.path().to_owned()),
174                    _ => None,
175                }
176            })
177            .collect();
178        files
179    }
180
181    /// Set the heap_?.c file to use from the "/portable/MemMang/" folder.
182    /// heap_1.c ... heap_5.c (Default: heap_4.c)
183    /// see also: https://www.freertos.org/a00111.html
184    pub fn heap<P: AsRef<Path>>(&mut self, file_name: P) {
185        self.heap_c = file_name.as_ref().to_path_buf();
186    }
187
188    /// Access to the underlining cc::Build instance to further customize the build.
189    pub fn get_cc(&mut self) -> &mut Build {
190        &mut self.cc
191    }
192
193    pub fn cpu_clock(&mut self, clock: HertzU32) {
194        self.cpu_clock = clock;
195    }
196
197    pub fn heap_size(&mut self, size: usize) {
198        self.heap_size = size;
199    }
200
201    /// in words
202    pub fn minimal_stack_size(&mut self, size: usize) {
203        self.minimal_stack_size = size;
204    }
205
206    pub fn max_task_priorities(&mut self, val: u8) {
207        self.max_priorities = val;
208    }
209
210    /// http://www.freertos.org/Configuring-a-real-time-RTOS-application-to-use-software-timers.html
211    pub fn use_timer_task(&mut self, priority: u8, queue_length: usize, stack_depth: usize) {
212        self.timer_task_config = Some(TimerTaskConfig {
213            priority,
214            queue_length,
215            stack_depth,
216        });
217    }
218
219    pub fn use_preemption(&mut self, v: bool) {
220        self.use_preemption = v;
221    }
222
223    pub fn idle_should_yield(&mut self, v: bool) {
224        self.idle_should_yield = Some(v);
225    }
226
227    /// http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html
228    pub fn interrupt_priority_bits(
229        &mut self,
230        bits: u8,
231        max_syscall_priority: u32,
232        lowest_priority: u32,
233    ) {
234        self.interrupt_priority_bits = Some(InterruptPriorityBits {
235            bits,
236            lowest_priority,
237            max_syscall_priority,
238        });
239    }
240
241    pub fn interrupt_priority(&mut self, max_syscall_priority: u32, lowest_priority: u32) {
242        self.interrupt_priority = Some(InterruptPriority {
243            lowest_priority,
244            max_syscall_priority,
245        });
246    }
247
248    pub fn max_task_name_len(&mut self, v: usize) {
249        self.max_task_name_len = Some(v);
250    }
251
252    pub fn queue_registry_size(&mut self, v: usize) {
253        self.queue_registry_size = Some(v);
254    }
255
256    pub fn check_for_stack_overflow(&mut self, v: u8) {
257        self.check_for_stack_overflow = Some(v)
258    }
259
260    fn freertos_include_dir(&self) -> PathBuf {
261        self.freertos_dir.join("include")
262    }
263
264    /// set the freertos port dir relativ to the FreeRTOS/Source/portable directory
265    /// e.g. "GCC/ARM_CM33_NTZ/non_secure"
266    ///
267    /// If not set it will be detected based on the current build target (not many targets supported yet).
268    pub fn freertos_port<P: AsRef<Path>>(&mut self, port_dir: P) {
269        self.freertos_port = Some(port_dir.as_ref().to_path_buf());
270    }
271
272    fn get_freertos_port_dir(&self) -> PathBuf {
273        let base = self.get_freertos_port_base();
274        if self.freertos_port.is_some() {
275            return base.join(self.freertos_port.as_ref().unwrap());
276        }
277
278        let target = env::var("TARGET").unwrap_or_default();
279        let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default(); // msvc, gnu, ...
280        //let target_family = env::var("CARGO_CFG_TARGET_FAMILY").unwrap_or_default(); // unix, windows
281        let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); // x86_64
282        let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); // none, windows, linux, macos
283        let port = match (
284            target.as_str(),
285            target_arch.as_str(),
286            target_os.as_str(),
287            target_env.as_str(),
288        ) {
289            (_, "x86_64", "windows", _) => "MSVC-MingW",
290            (_, "x86_64", "linux", "gnu") => "ThirdParty/GCC/Posix",
291            ("thumbv7m-none-eabi", _, _, _) => "GCC/ARM_CM3",
292            ("thumbv7em-none-eabi", _, _, _) => "GCC/ARM_CM3", // M4 cores without FPU use M3
293            ("thumbv7em-none-eabihf", _, _, _) => "GCC/ARM_CM4F",
294            // TODO We should support feature "trustzone"
295            ("thumbv8m.main-none-eabi", _, _, _) => "GCC/ARM_CM33_NTZ/non_secure",
296            ("thumbv8m.main-none-eabihf", _, _, _) => "GCC/ARM_CM33_NTZ/non_secure",
297            _ => {
298                panic!(
299                    "Unknown target: '{}', from TARGET environment variable.",
300                    target
301                );
302            }
303        };
304        base.join(port)
305    }
306
307    pub fn freertos_port_base<P: AsRef<Path>>(&mut self, base_dir: P) {
308        self.freertos_port_base = Some(base_dir.as_ref().to_path_buf());
309    }
310
311    fn get_freertos_port_base(&self) -> PathBuf {
312        if let Some(base) = &self.freertos_port_base {
313            base.clone()
314        } else {
315            PathBuf::from(&self.freertos_dir).join("portable")
316        }
317    }
318
319    fn heap_c_file(&self) -> PathBuf {
320        self.freertos_dir
321            .join("portable/MemMang")
322            .join(&self.heap_c)
323    }
324
325    /// Check that all required files and paths exist
326    fn verify_paths(&self) -> Result<(), Error> {
327        if !self.freertos_dir.is_dir() {
328            return Err(Error::new(&format!(
329                "Directory freertos_dir does not exist: {}",
330                self.freertos_dir.to_str().unwrap()
331            )));
332        }
333        let port_dir = self.get_freertos_port_dir();
334        if !port_dir.is_dir() {
335            return Err(Error::new(&format!(
336                "Directory freertos_port_dir does not exist: {}",
337                port_dir.to_str().unwrap()
338            )));
339        }
340
341        let include_dir = self.freertos_include_dir();
342        if !include_dir.is_dir() {
343            return Err(Error::new(&format!(
344                "Directory freertos_include_dir does not exist: {}",
345                include_dir.to_str().unwrap()
346            )));
347        }
348
349        // The heap implementation
350        let heap_c = self.heap_c_file();
351        if !heap_c.is_file() {
352            return Err(Error::new(&format!(
353                "File heap_?.c does not exist: {}",
354                heap_c.to_str().unwrap()
355            )));
356        }
357
358        // Make sure FreeRTOSConfig.h exists in freertos_config_dir
359        if !self.config_dir.join("FreeRTOSConfig.h").is_file() {
360            return Err(Error::new(&format!(
361                "File FreeRTOSConfig.h does not exist in the directory: {}",
362                self.config_dir.to_str().unwrap()
363            )));
364        }
365
366        if let Some(dir) = &self.user_config_dir {
367            if !dir.join("UserConfig.h").is_file() {
368                return Err(Error::new(&format!(
369                    "File UserConfig.h does not exist in the directory: {}",
370                    dir.to_str().unwrap()
371                )));
372            }
373        }
374
375        // Add the freertos shim.c
376        if !self.shim_file.is_file() {
377            return Err(Error::new(&format!(
378                "File freertos_shim '{}' does not exist, missing freertos dependency?",
379                self.shim_file.to_str().unwrap()
380            )));
381        }
382
383        Ok(())
384    }
385
386    pub fn compile(&self) -> Result<(), Error> {
387        let mut cc = self.cc.clone();
388
389        self.verify_paths()?;
390
391        add_include_with_rerun(&mut cc, self.freertos_include_dir()); // FreeRTOS header files
392        add_include_with_rerun(&mut cc, self.get_freertos_port_dir()); // FreeRTOS port header files (e.g. portmacro.h)
393        add_include_with_rerun(&mut cc, &self.config_dir); // FreeRTOSConfig.h
394        if let Some(dir) = &self.user_config_dir {
395            set_define!(cc, "__HAS_USER_CONFIG", 1);
396            add_include_with_rerun(&mut cc, dir); // User's UserConfig.h
397            println!("cargo:rerun-if-env-changed={}", dir.to_str().unwrap());
398        }
399
400        add_build_files_with_rerun(&mut cc, self.freertos_files()); // Non-port C files
401        add_build_files_with_rerun(&mut cc, self.freertos_port_files()); // Port C files
402        add_build_file_with_rerun(&mut cc, &self.shim_file); // Shim C file
403        add_build_file_with_rerun(&mut cc, self.heap_c_file()); // Heap C file
404
405        if self.cpu_clock.raw() > 0 {
406            set_define!(cc, "configCPU_CLOCK_HZ", self.cpu_clock.raw());
407        }
408        set_define!(cc, "configMINIMAL_STACK_SIZE", self.minimal_stack_size);
409        set_define!(cc, "configTOTAL_HEAP_SIZE", self.heap_size);
410        set_define!(cc, "configMAX_PRIORITIES", self.max_priorities);
411        if let Some(config) = &self.timer_task_config {
412            set_define!(cc, "configUSE_TIMERS", 1);
413            set_define!(cc, "configTIMER_TASK_PRIORITY", config.priority);
414            set_define!(cc, "configTIMER_QUEUE_LENGTH", config.queue_length);
415            set_define!(cc, "configTIMER_TASK_STACK_DEPTH", config.stack_depth);
416        }
417        set_define!(bool, cc, "configUSE_PREEMPTION", self.use_preemption);
418        if let Some(v) = self.idle_should_yield {
419            set_define!(bool, cc, "configIDLE_SHOULD_YIELD", v);
420        }
421        if let Some(config) = self.interrupt_priority_bits {
422            set_define!(
423                cc,
424                "configKERNEL_INTERRUPT_PRIORITY",
425                config.lowest_priority << (8 - config.bits)
426            );
427            set_define!(
428                cc,
429                "configMAX_SYSCALL_INTERRUPT_PRIORITY",
430                config.max_syscall_priority << (8 - config.bits)
431            );
432        }
433        if let Some(config) = self.interrupt_priority {
434            set_define!(
435                cc,
436                "configKERNEL_INTERRUPT_PRIORITY",
437                config.lowest_priority
438            );
439            set_define!(
440                cc,
441                "configMAX_SYSCALL_INTERRUPT_PRIORITY",
442                config.max_syscall_priority
443            );
444        }
445        if let Some(v) = self.max_task_name_len {
446            set_define!(cc, "configMAX_TASK_NAME_LEN", v);
447        }
448        if let Some(v) = self.queue_registry_size {
449            set_define!(cc, "configQUEUE_REGISTRY_SIZE", v);
450        }
451        if let Some(v) = self.check_for_stack_overflow {
452            set_define!(cc, "configCHECK_FOR_STACK_OVERFLOW", v);
453        }
454        setup_all_define(&mut cc);
455
456        println!(
457            "cargo:rerun-if-env-changed={}",
458            self.freertos_dir.to_str().unwrap()
459        );
460        println!(
461            "cargo:rerun-if-env-changed={}",
462            self.config_dir.to_str().unwrap()
463        );
464        println!(
465            "cargo:rerun-if-env-changed={}",
466            self.shim_file.to_str().unwrap()
467        );
468
469        cc.try_compile("freertos")
470            .map_err(|e| Error::new(&format!("{}", e)))?;
471
472        Ok(())
473    }
474
475    /// Add a single file to the build. This also tags the file with cargo:rerun-if-changed so that cargo will re-run
476    /// the build script if the file changes. If you don't want this additional behavior, use get_cc().file() to
477    /// directly add a file to the build instead.
478    pub fn add_build_file<P: AsRef<Path>>(&mut self, file: P) {
479        add_build_file_with_rerun(self.get_cc(), file);
480    }
481
482    /// Add multiple files to the build. This also tags the files with cargo:rerun-if-changed so that cargo will re-run
483    /// the build script if the files change. If you don't want this additional behavior, use get_cc().files() to
484    /// directly add files to the build instead.
485    pub fn add_build_files<P>(&mut self, files: P)
486    where
487        P: IntoIterator,
488        P::Item: AsRef<Path>,
489    {
490        add_build_files_with_rerun(self.get_cc(), files);
491    }
492}
493
494fn add_build_file_with_rerun<P: AsRef<Path>>(build: &mut Build, file: P) {
495    build.file(&file);
496    println!("cargo:rerun-if-changed={}", file.as_ref().display());
497}
498
499fn add_build_files_with_rerun<P>(build: &mut Build, files: P)
500where
501    P: IntoIterator,
502    P::Item: AsRef<Path>,
503{
504    for file in files.into_iter() {
505        add_build_file_with_rerun(build, file);
506    }
507}
508
509fn add_include_with_rerun<P: AsRef<Path>>(build: &mut Build, dir: P) {
510    build.include(&dir);
511
512    WalkDir::new(&dir)
513        .follow_links(false)
514        .max_depth(1)
515        .into_iter()
516        .filter_map(|e| e.ok())
517        .for_each(|entry| {
518            let f_name = entry.path();
519            if f_name.extension() == Some(OsStr::new("h")) {
520                println!("cargo:rerun-if-changed={}", f_name.display());
521            }
522        });
523}
524
525fn setup_all_define(cc: &mut cc::Build) {
526    sync_define(cc, "__IS_CORTEX_M");
527    sync_define(cc, "INCLUDE_vTaskDelete");
528    sync_define(cc, "INCLUDE_vTaskDelayUntil");
529    sync_define(cc, "INCLUDE_uxTaskGetStackHighWaterMark");
530    sync_define(cc, "INCLUDE_HeapFreeSize");
531    sync_define(cc, "INCLUDE_vTaskSuspend");
532    sync_define(cc, "configUSE_RECURSIVE_MUTEXES");
533    sync_define(cc, "configUSE_COUNTING_SEMAPHORES");
534    sync_define(cc, "configUSE_TRACE_FACILITY");
535}
536
537fn sync_define(cc: &mut cc::Build, def: &str) {
538    let v = "DEP_FREERTOS_DEF_".to_string() + &def.to_uppercase();
539    let v_string = env::var(v).unwrap_or("0".to_string());
540    set_define!(cc, def, v_string);
541}
542
543#[test]
544fn test_paths() {
545    unsafe { env::set_var("FREERTOS_SRC", "some/path") };
546    unsafe { env::set_var("TARGET", "thumbv8m.main-none-eabihf") };
547    let b = Builder::new();
548    assert_eq!(b.freertos_dir.to_str().unwrap(), "some/path");
549}