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 heap_c: PathBuf,
25 cc: Build,
26 cpu_clock: HertzU32,
27 heap_size: usize,
28 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 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 pub fn new() -> Builder {
119 Self::default()
120 }
121
122 pub fn freertos_kernel<P: AsRef<Path>>(&mut self, path: P) {
124 self.freertos_dir = path.as_ref().to_path_buf();
125 }
126
127 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 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 pub fn shim_file<P: AsRef<Path>>(&mut self, path: P) {
139 self.shim_file = path.as_ref().to_path_buf();
140 }
141
142 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 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 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 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 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 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 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(); let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); 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", ("thumbv7em-none-eabihf", _, _, _) => "GCC/ARM_CM4F",
294 ("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 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 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 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 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()); add_include_with_rerun(&mut cc, self.get_freertos_port_dir()); add_include_with_rerun(&mut cc, &self.config_dir); if let Some(dir) = &self.user_config_dir {
395 set_define!(cc, "__HAS_USER_CONFIG", 1);
396 add_include_with_rerun(&mut cc, dir); println!("cargo:rerun-if-env-changed={}", dir.to_str().unwrap());
398 }
399
400 add_build_files_with_rerun(&mut cc, self.freertos_files()); add_build_files_with_rerun(&mut cc, self.freertos_port_files()); add_build_file_with_rerun(&mut cc, &self.shim_file); add_build_file_with_rerun(&mut cc, self.heap_c_file()); 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 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 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}