1extern crate proc_macro;
2
3mod prelude;
4
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::{ToTokens, quote};
8use std::collections::HashMap;
9use syn::{
10 ItemFn, Meta, ReturnType, Token, parse::Parser, parse_macro_input, punctuated::Punctuated,
11};
12
13use crate::prelude::generate_prelude_imports;
14
15#[proc_macro_attribute]
20pub fn rust_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
21 let input_fn = parse_macro_input!(item as ItemFn);
22
23 if !input_fn.sig.inputs.is_empty() {
24 panic!("Function marked with #[rust_main] must have no parameters");
25 }
26
27 let docs: Vec<TokenStream2> = input_fn
28 .attrs
29 .iter()
30 .filter(|attr| attr.path().is_ident("doc"))
31 .map(|attr| {
32 let attr_tokens = attr.to_token_stream();
33 quote! { #attr_tokens }
34 })
35 .collect();
36
37 match &input_fn.sig.output {
38 syn::ReturnType::Default => panic!("Function marked with #[rust_main] must return -> !"),
39 syn::ReturnType::Type(_, ty) => {
40 if let syn::Type::Never(_) = &**ty {
41 } else {
43 panic!("Function marked with #[rust_main] must return -> !");
44 }
45 }
46 }
47
48 let fn_block = &input_fn.block;
49
50 let prelude = generate_prelude_imports();
51
52 let dev_debug = if cfg!(feature = "dev") {
53 quote! {
54 loop {
55 if '\n' as u8 == ecos_ssc1::Uart::read_byte_blocking() {
56 break;
57 }
58 }
59 }
60 } else {
61 quote! {}
62 };
63
64 let init_alloc = if cfg!(feature = "alloc") {
65 quote! {
66 unsafe {
67 ::ecos_ssc1::features::alloc::init();
68 }
69 }
70 } else {
71 quote! {}
72 };
73
74 let expanded = quote! {
75 #prelude
76
77 #(#docs)*
78 #[unsafe(no_mangle)]
79 pub extern "C" fn main() -> ! {
80 #dev_debug
81
82 #init_alloc
83 #fn_block
84 }
85 };
86
87 TokenStream::from(expanded)
88}
89
90#[proc_macro_attribute]
108pub fn ecos_main(attr: TokenStream, item: TokenStream) -> TokenStream {
109 let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
110 let attr_args = parser.parse(attr).unwrap_or_default();
111
112 let input_fn = parse_macro_input!(item as ItemFn);
113
114 if !input_fn.sig.inputs.is_empty() {
115 panic!("Function marked with #[ecos_main] must have no parameters");
116 }
117
118 let mut qspi_clkdiv: Option<u32> = None;
119
120 for arg in &attr_args {
121 match arg {
122 Meta::Path(path) => {
123 if let Some(ident) = path.get_ident() {
125 if ident == "qspi" {
126 qspi_clkdiv = Some(0);
127 }
128 }
129 }
130 Meta::List(list) => {
131 if let Some(ident) = list.path.get_ident() {
133 if ident == "qspi" {
134 let args = parse_qspi_args(&list.tokens);
135 qspi_clkdiv = Some(args.clkdiv);
136 }
137 }
138 }
139 Meta::NameValue(_) => {
140 panic!("ecos_main does not support name=value syntax for qspi");
141 }
142 }
143 }
144
145 let docs: Vec<TokenStream2> = input_fn
146 .attrs
147 .iter()
148 .filter(|attr| attr.path().is_ident("doc"))
149 .map(|attr| {
150 let attr_tokens = attr.to_token_stream();
151 quote! { #attr_tokens }
152 })
153 .collect();
154
155 match &input_fn.sig.output {
156 ReturnType::Default => panic!("Function marked with #[ecos_main] must return -> !"),
157 ReturnType::Type(_, ty) => {
158 if let syn::Type::Never(_) = &**ty {
159 } else {
161 panic!("Function marked with #[ecos_main] must return -> !");
162 }
163 }
164 }
165
166 let fn_block = &input_fn.block;
167
168 let mut pm = PeripheralManager::new();
169
170 pm.register("uart", true, || {
172 if !cfg!(feature = "log") {
173 quote! {
174 unsafe {
175 ::ecos_ssc1::bindings::sys_uart_init();
176 }
177 }
178 } else {
179 quote! {}
180 }
181 });
182
183 pm.register("tick", false, || {
185 if !cfg!(feature = "log") {
186 quote! {
187 unsafe {
188 ::ecos_ssc1::bindings::sys_tick_init();
189 }
190 }
191 } else {
192 quote! {}
193 }
194 });
195
196 pm.register("qspi", false, {
197 let clkdiv = qspi_clkdiv;
198 move || {
199 if clkdiv.is_some() {
200 let clkdiv_val = clkdiv.unwrap_or(0);
201 quote! {
202 unsafe {
203 ::ecos_ssc1::bindings::qspi_init(::ecos_ssc1::bindings::qspi_config_t {
204 clkdiv: #clkdiv_val
205 });
206 }
207 }
208 } else {
209 quote! {}
210 }
211 }
212 });
213
214 pm.register("gpio", true, || {
216 quote! { unsafe { ::ecos_ssc1::bindings::gpio_config(
217 &::ecos_ssc1::bindings::gpio_config_t {
219 pin_bit_mask: 0xFFFF,
220 mode: ::ecos_ssc1::bindings::gpio_mode_t_GPIO_MODE_OUTPUT,
221 }
222 ); } }
223 });
224
225 pm.add_preset("on", |pm| {
227 pm.enable("tick");
229 pm.enable("qspi");
230 });
231
232 pm.add_preset("off", |pm| {
234 pm.disable("uart");
236 pm.disable("gpio");
237 });
238
239 for arg in attr_args {
241 match arg {
242 Meta::Path(path) => {
243 if let Some(ident) = path.get_ident() {
244 let ident_str = ident.to_string();
245 if ident_str == "qspi" {
247 continue;
248 }
249
250 pm.process_option(&ident_str);
251 }
252 }
253 Meta::List(list) => {
254 if let Some(ident) = list.path.get_ident() {
256 if ident != "qspi" {
257 panic!(
258 "ecos_main only supports qspi with parameters, other options must be simple identifiers"
259 );
260 }
261 } else {
262 panic!(
263 "ecos_main only supports simple identifiers as options or qspi(...) syntax"
264 );
265 }
266 }
267 Meta::NameValue(_) => {
268 panic!("ecos_main does not support name=value syntax (except qspi(clkdiv=value))");
269 }
270 }
271 }
272
273 let init_pm = pm.generate_init_code();
274
275 let dev_debug = if cfg!(feature = "dev") {
276 quote! {
277 loop {
278 if '\n' as u8 == ecos_ssc1::Uart::read_byte_blocking() {
279 break;
280 }
281 }
282 }
283 } else {
284 quote! {}
285 };
286
287 let init_log = if cfg!(feature = "log") {
288 quote! {
289 unsafe {
290 ::ecos_ssc1::bindings::sys_uart_init();
292 println!("asdsadas");
293 ::ecos_ssc1::bindings::sys_tick_init();
294 ::ecos_ssc1::features::log::init_logger();
295 }
296 }
297 } else {
298 quote! {}
299 };
300
301 let init_alloc = if cfg!(feature = "alloc") {
302 quote! {
303 unsafe {
304 ::ecos_ssc1::features::alloc::init();
305 }
306 }
307 } else {
308 quote! {}
309 };
310
311 let prelude = generate_prelude_imports();
312
313 let expanded = quote! {
314 #prelude
315
316 #(#docs)*
317 #[unsafe(no_mangle)]
318 pub extern "C" fn main() -> ! {
319 #init_pm
320 #init_log
321
322 #dev_debug
323
324 #init_alloc
325 #fn_block
326 }
327 };
328
329 TokenStream::from(expanded)
330}
331
332struct PeripheralManager {
333 peripherals: HashMap<String, PeripheralConfig>,
334 presets: HashMap<String, Box<dyn Fn(&mut PeripheralManager)>>,
335}
336
337impl PeripheralManager {
338 fn new() -> Self {
339 Self {
340 peripherals: HashMap::new(),
341 presets: HashMap::new(),
342 }
343 }
344
345 fn register<F>(&mut self, name: &str, default_enabled: bool, init_fn: F)
351 where
352 F: Fn() -> TokenStream2 + 'static,
353 {
354 self.peripherals.insert(
355 name.to_string(),
356 PeripheralConfig {
357 enabled: default_enabled,
358 init_fn: Box::new(init_fn),
359 },
360 );
361 }
362
363 fn add_preset<F>(&mut self, name: &str, preset_fn: F)
364 where
365 F: Fn(&mut PeripheralManager) + 'static,
366 {
367 self.presets.insert(name.to_string(), Box::new(preset_fn));
368 }
369
370 fn enable(&mut self, name: &str) {
371 if let Some(config) = self.peripherals.get_mut(name) {
372 config.enabled = true;
373 }
374 }
375
376 fn disable(&mut self, name: &str) {
377 if let Some(config) = self.peripherals.get_mut(name) {
378 config.enabled = false;
379 }
380 }
381
382 fn process_option(&mut self, option: &str) {
383 match option {
384 "on" => {
386 let f = |pm: &mut PeripheralManager| {
387 pm.enable("tick");
388 };
389 f(self);
390 }
391 "off" => {
393 let f = |pm: &mut PeripheralManager| {
394 pm.disable("uart");
395 };
396 f(self);
397 }
398 _ => {
399 if let Some(periph_name) = option.strip_prefix("no_") {
401 self.disable(periph_name);
403 } else {
404 self.enable(option);
406 }
407 }
408 }
409 }
410
411 fn generate_init_code(&self) -> TokenStream2 {
412 let mut code = TokenStream2::new();
413
414 for (_, config) in &self.peripherals {
415 if config.enabled {
416 let init_code = (config.init_fn)();
417 code.extend(init_code);
418 }
419 }
420
421 code
422 }
423}
424
425struct PeripheralConfig {
426 enabled: bool,
427 init_fn: Box<dyn Fn() -> TokenStream2>,
428}
429
430fn parse_qspi_args(tokens: &TokenStream2) -> QspiArgs {
431 let mut args = QspiArgs { clkdiv: 0 };
432
433 if let Ok(value) = syn::parse::<syn::LitInt>(tokens.clone().into()) {
434 args.clkdiv = value.base10_parse::<u32>().unwrap_or(0);
435 return args;
436 }
437
438 let parser = Punctuated::<syn::Meta, Token![,]>::parse_terminated;
439 if let Ok(meta_list) = parser.parse(tokens.clone().into()) {
440 for meta in meta_list {
441 match meta {
442 syn::Meta::NameValue(nv) => {
443 if let Some(ident) = nv.path.get_ident() {
444 if ident == "clkdiv" {
445 if let syn::Expr::Lit(expr_lit) = &nv.value {
446 if let syn::Lit::Int(lit_int) = &expr_lit.lit {
447 args.clkdiv = lit_int.base10_parse::<u32>().unwrap_or(0);
448 } else {
449 panic!("clkdiv must be an integer literal");
450 }
451 } else {
452 panic!("clkdiv must be a literal");
453 }
454 } else {
455 panic!("qspi only supports clkdiv parameter");
456 }
457 }
458 }
459 _ => {
460 panic!("qspi only supports clkdiv=value syntax");
461 }
462 }
463 }
464 return args;
465 }
466
467 args
468}
469
470struct QspiArgs {
471 clkdiv: u32,
472}