emcell-macro 0.0.3

A macro crate for emcell
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use sha2::{Digest, Sha256};
use syn::{Data, DataStruct, DeriveInput, ExprMacro, Field, Fields, FieldValue, ItemStruct, LitInt, Member, Meta, parse2, parse_macro_input, parse_quote, Type, Visibility};
use syn::parse::{Parse, Parser, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;

struct EmcellDef {
    strukt: ItemStruct,
    is_primary: bool,

    ram_region: RamRegion,
    flash_region: FlashRegion,
    struct_sha256: [u8; 32],
}

impl ToTokens for EmcellDef {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let name = self.strukt.ident.to_string();
        let ram_region_start = self.ram_region.start;
        let ram_region_end = self.ram_region.end;
        let flash_region_start = self.flash_region.start;
        let flash_region_end = self.flash_region.end;

        let cell_type = if self.is_primary {
            quote! { emcell::CellType::Primary }
        } else {
            quote! { emcell::CellType::NonPrimary }
        };

        let hash = self.struct_sha256;


        tokens.extend(quote! {
            emcell::meta::CellDefMeta {
                name: #name,
                cell_type: #cell_type,
                ram_range_start_offs: #ram_region_start,
                ram_range_end_offs: #ram_region_end,
                flash_range_start_offs: #flash_region_start,
                flash_range_end_offs: #flash_region_end,
                struct_sha256: [#(#hash),*],
            }
        });
    }
}

struct EmcellDeviceConfiguration {
    initial_stack_pointer: usize,
    ram_region: RamRegion,
    flash_region: FlashRegion,
}

impl ToTokens for EmcellDeviceConfiguration {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let initial_stack_pointer = self.initial_stack_pointer;
        let ram_region_start = self.ram_region.start;
        let ram_region_end = self.ram_region.end;
        let flash_region_start = self.flash_region.start;
        let flash_region_end = self.flash_region.end;

        tokens.extend(quote! {
            emcell::meta::DeviceConfigMeta {
                initial_stack_ptr: #initial_stack_pointer,
                ram_range_start: #ram_region_start,
                ram_range_end: #ram_region_end,
                flash_range_start: #flash_region_start,
                flash_range_end: #flash_region_end,
            }
        });
    }

}

struct DeviceMacroParams(Punctuated<FieldValue, Comma>);

impl Parse for DeviceMacroParams {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let device_config = input.parse_terminated(FieldValue::parse, Comma)?;

        Ok(DeviceMacroParams(device_config))
    }
}

impl Parse for EmcellDeviceConfiguration {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let device_config_macro: ExprMacro = input.parse()?;
        if !device_config_macro.mac.path.is_ident("device") {
            return Err(syn::Error::new(device_config_macro.span(), "Expected device! macro"));
        }

        //parse macro content as struct fields list
        let device_config: DeviceMacroParams = parse2(device_config_macro.mac.tokens)?;
        let device_config = device_config.0;

        let mut initial_stack_pointer = None;
        let mut ram_region_start = None;
        let mut ram_region_end = None;
        let mut flash_region_start = None;
        let mut flash_region_end = None;

        for field in device_config.iter() {
            match &field.member {
                Member::Named(ident) => {
                    fn expr_into_lit_int(expr: &syn::Expr) -> syn::Result<usize> {
                        match expr {
                            syn::Expr::Lit(lit) => {
                                match &lit.lit {
                                    syn::Lit::Int(int) => {
                                        parse_integer_lit(int)
                                    }
                                    _ => Err(syn::Error::new(lit.span(), "Expected integer literal"))
                                }
                            }
                            _ => Err(syn::Error::new(expr.span(), "Expected integer literal"))
                        }
                    }

                    match ident.to_string().as_str() {
                        "initial_stack_ptr" => {
                            initial_stack_pointer = Some(expr_into_lit_int(&field.expr)?);
                        }
                        "ram_range_start" => {
                            ram_region_start = Some(expr_into_lit_int(&field.expr)?);
                        }
                        "ram_range_end" => {
                            ram_region_end = Some(expr_into_lit_int(&field.expr)?);
                        }
                        "flash_range_start" => {
                            flash_region_start = Some(expr_into_lit_int(&field.expr)?);
                        }
                        "flash_range_end" => {
                            flash_region_end = Some(expr_into_lit_int(&field.expr)?);
                        }
                        _ => {}
                    }

                }
                _ => {
                    return Err(syn::Error::new(field.span(), "Expected named fields"));
                }
            }
        }
        let Some(initial_stack_pointer) = initial_stack_pointer else {
            return Err(syn::Error::new(device_config.span(), "initial_stack_ptr field required"));
        };
        let Some(ram_region_start) = ram_region_start else {
            return Err(syn::Error::new(device_config.span(), "ram_range_start field required"));
        };
        let Some(ram_region_end) = ram_region_end else {
            return Err(syn::Error::new(device_config.span(), "ram_range_end field required"));
        };
        let Some(flash_region_start) = flash_region_start else {
            return Err(syn::Error::new(device_config.span(), "flash_range_start field required"));
        };
        let Some(flash_region_end) = flash_region_end else {
            return Err(syn::Error::new(device_config.span(), "flash_range_end field required"));
        };

        Ok(EmcellDeviceConfiguration {
            ram_region: RamRegion { start: ram_region_start, end: ram_region_end },
            flash_region: FlashRegion { start: flash_region_start, end: flash_region_end },
            initial_stack_pointer
        })
    }
}

struct EmcellConfiguration {
    device: EmcellDeviceConfiguration,
    cells: Vec<EmcellDef>
}

impl Parse for EmcellConfiguration {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        //parse device! macro
        let device: EmcellDeviceConfiguration = input.parse()?;

        let mut cells = Vec::new();
        let mut primary_count = 0;
        while !input.is_empty() {
            let strukt: ItemStruct = input.parse()?;


            let mut is_primary = None;
            let mut ram_region = None;
            let mut flash_region = None;

            for attr in &strukt.attrs {
                let meta = &attr.meta;
                let name = meta.path();

                match name {
                    _ if name.is_ident("ram_region") => {
                        let meta = meta.require_list()?;
                        ram_region = Some(syn::parse2::<RamRegion>(meta.tokens.clone())?);
                    }
                    _ if name.is_ident("flash_region") => {
                        let meta = meta.require_list()?;
                        flash_region = Some(syn::parse2::<FlashRegion>(meta.tokens.clone())?);
                    }
                    _ if name.is_ident("cell") => {
                        match meta {
                            Meta::Path(_) => {
                                is_primary = Some(false);
                            } // #[cell]
                            Meta::List(inner) => {
                                let attr_params = syn::parse2::<CellAttribParams>(inner.tokens.clone())?;
                                if attr_params.is_primary {
                                    is_primary = Some(true);
                                    primary_count += 1;
                                }
                            }
                            _ => {
                                return Err(syn::Error::new(attr.span(), "Expected either #[cell] or #[cell(primary)]"));
                            }
                        }
                    }
                    _ => {}
                }
            }

            let Some(ram_region) = ram_region else {
                return Err(syn::Error::new(strukt.span(), "Attribute #[ram_region(start, end)] required for each struct definition"));
            };
            let Some(flash_region) = flash_region else {
                return Err(syn::Error::new(strukt.span(), "Attribute #[flash_region(start, end)] required for each struct definition"));
            };
            let Some(is_primary) = is_primary else {
                return Err(syn::Error::new(strukt.span(), "Required attribute #[cell] or #[cell(primary)] missing for struct definition"));
            };


            let mut hasher = Sha256::new();
            let fields = &strukt.fields;
            hasher.update(fields.to_token_stream().to_string().as_bytes());
            let hash = hasher.finalize();
            let struct_sha256 = hash.as_slice().try_into().unwrap();

            cells.push(EmcellDef {
                strukt,
                is_primary,
                ram_region,
                flash_region,
                struct_sha256
            });
        }

        if primary_count > 1 {
            return Err(syn::Error::new(Span::call_site(), "Only one primary cell is allowed"));
        }
        if primary_count == 0 {
            return Err(syn::Error::new(Span::call_site(), "No primary cell found. At least one cell must be marked as #[cell(primary)]"));
        }
        Ok(EmcellConfiguration{
            cells,
            device
        })
    }
}

pub fn emcell_configuration(input: TokenStream) -> TokenStream {
    let output = input.clone();
    let emcell_configuration = parse_macro_input!(input as EmcellConfiguration);

    let mut cell_names = Vec::new();
    let mut cell_idents = Vec::new();
    let mut cell_indices = Vec::new();

    let mut non_primary_cell_idents = Vec::new();
    let mut primary_cell = None;

    for (i, cell) in emcell_configuration.cells.iter().enumerate() {
        let cell_name = cell.strukt.ident.to_string();
        cell_names.push(cell_name.clone());
        cell_idents.push(cell.strukt.ident.clone());
        cell_indices.push(i);

        if cell.is_primary {
            primary_cell = Some(cell);
        }
        else {
            non_primary_cell_idents.push(cell.strukt.ident.clone());
        }
    }
    let primary_cell = primary_cell.unwrap();
    let primary_cell_ident = &primary_cell.strukt.ident;

    let cell_count = cell_names.len();

    let emcell_defs = &emcell_configuration.cells;
    let emcell_device = emcell_configuration.device;
    let output = proc_macro2::TokenStream::from(output);
    let output = quote! {
        #output

        pub type PrimaryCell = #primary_cell_ident;

        #(unsafe impl emcell::Cell for #cell_idents {
            const CUR_META: emcell::meta::CellDefMeta = META.cell_defs[#cell_indices];
            const DEVICE_CONFIG: emcell::meta::DeviceConfigMeta = META.device_configuration;
            const CELLS_META: &'static [emcell::meta::CellDefMeta] = &META.cell_defs;
            fn check_signature(&self, init_memory: bool) -> bool {
                if self.signature != <Self as emcell::WithSignature>::VALID_SIGNATURE { // a little silly :3
                    return false;
                }

                let known_sha256 = Self::CUR_META.struct_sha256;
                let sha_ok = unsafe {(self.init)(known_sha256, init_memory)};
                return sha_ok;
            }
        }

        impl #cell_idents {
            pub const fn get_cell_start_flash_addr() -> usize {
                <Self as emcell::Cell>::CUR_META.absolute_flash_start(&META.device_configuration)
            }
            pub const fn get_cell_end_flash_addr() -> usize {
                <Self as emcell::Cell>::CUR_META.absolute_flash_end(&META.device_configuration)
            }
        })*

        #(unsafe impl emcell::WithSignature for #non_primary_cell_idents {
            const VALID_SIGNATURE: u32 = 0xdeadbeef;
        })*

        unsafe impl emcell::WithSignature for #primary_cell_ident {
            const VALID_SIGNATURE: u32 = 0xbeefdead;
        }

        pub const META: emcell::meta::CellDefsMeta::<#cell_count> = emcell::meta::CellDefsMeta {
            cell_defs: [#(#emcell_defs),*],
            device_configuration: #emcell_device
        };

        pub const CELL_COUNT: usize = #cell_count;
    };

    TokenStream::from(output)
}

// params for #[cell] attribute
struct CellAttribParams {
    is_primary: bool,
}

impl Parse for CellAttribParams {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        // check if input is empty
        if input.is_empty() {
            return Ok(CellAttribParams {
                is_primary: false
            });
        }
        let name: Ident = input.parse()?;

        let is_primary = match name.to_string().as_str() {
            "primary" => true,
            "" => false,
            _ => return Err(syn::Error::new(name.span(), "Invalid attribute! Expected #[cell(primary)] or #[cell]")),
        };

        Ok(CellAttribParams {
            is_primary
        })
    }
}
fn parse_integer_lit(input: &LitInt) -> syn::Result<usize> {
    match input.suffix() {
        "" => input.base10_parse::<usize>(),
        "0x" => usize::from_str_radix(&input.to_string(), 16).map_err(|_| syn::Error::new(input.span(), "Invalid hex literal")),
        _ => Err(syn::Error::new(input.span(), "Invalid integer literal")),
    }
}
// ram_region(start,end) attribute parsing
struct RamRegion {
    start: usize,
    end: usize,
}

impl Parse for RamRegion {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let literals = input.parse_terminated(LitInt::parse, Comma)?;
        let literals = literals.into_iter().collect::<Vec<_>>();

        if literals.len() != 2 {
            return Err(syn::Error::new(input.span(), "Expected two integer literals, separated by comma"));
        }

        let start = &literals[0];
        let end = &literals[1];

        let start = parse_integer_lit(start)?;
        let end = parse_integer_lit(end)?;

        Ok(RamRegion {
            start,
            end
        })
    }
}

// flash_region(start,end) attribute parsing
struct FlashRegion {
    start: usize,
    end: usize,
}

impl Parse for FlashRegion {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let literals = input.parse_terminated(LitInt::parse, Comma)?;
        let literals = literals.into_iter().collect::<Vec<_>>();

        if literals.len() != 2 {
            return Err(syn::Error::new(input.span(), "Expected two integer literals, separated by comma"));
        }

        let start = &literals[0];
        let end = &literals[1];

        let start = parse_integer_lit(start)?;
        let end = parse_integer_lit(end)?;

        Ok(FlashRegion {
            start,
            end,
        })
    }
}

pub fn cell(_cell_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut header_struct = parse_macro_input!(item as DeriveInput);

    // enforce C abi
    header_struct.attrs.push(parse_quote! { #[repr(C)] });

    // Extract the struct fields
    let mut fields = match &mut header_struct.data {
        Data::Struct(DataStruct { fields: Fields::Named(fields), .. }) => fields,
        _ => {
            return TokenStream::from(
                syn::Error::new(Span::call_site(), "Expected a struct with named fields")
                    .to_compile_error(),
            );
        }
    };

    let mut switch_vectors_fn_ident = None;
    for field in fields.named.iter_mut() {
        for (i, attr) in field.attrs.iter().enumerate() {
            if attr.meta.path().is_ident("switch_vectors") {
                field.attrs.remove(i);
                //check signature to be fn() -> !
                let sig = &mut field.ty;
                let Some(ident) = field.ident.as_mut() else {
                    return TokenStream::from(
                        syn::Error::new(field.span(), "Expected named field")
                            .to_compile_error(),
                    );
                };

                let true_sig: Type = parse_quote! { fn() -> ! };
                if sig.to_token_stream().to_string() != true_sig.to_token_stream().to_string() {
                    return TokenStream::from(
                        syn::Error::new(ident.span(), "Expected function signature fn() -> !")
                            .to_compile_error(),
                    );
                }

                switch_vectors_fn_ident = Some(ident.clone());
                break;
            }
        }
    }

    // signature helps us ensure that our abi is indeed located at the correct address
    //
    // Also we assume that if signature is present and valid, we can call init() function safely
    // because it is guaranteed to preserve memory location (with repr C) even if header fields were changed
    let signature_field = Field::parse_named
        .parse2(quote! { pub signature: u32 })
        .unwrap();
    fields.named.insert(0, signature_field);

    let init_field = Field::parse_named
        .parse2(quote! { pub init: unsafe fn([u8; 32], bool) -> bool })
        .unwrap();
    fields.named.insert(1, init_field);


    let header_ident = &header_struct.ident;

    let impl_decl = if let Some(switch_vectors_fn_ident) = switch_vectors_fn_ident {
        let switch_vectors = Field::parse_named
            .parse2(quote! { pub _emcell_internal_switch_vectors: unsafe fn() })
            .unwrap();
        fields.named.insert(2, switch_vectors);

        quote! {

            impl #header_ident {
                pub fn switch_vectors_and_run(&self) -> ! {
                    unsafe {(self._emcell_internal_switch_vectors)()};
                    (self.#switch_vectors_fn_ident)()
                }
            }
        }
    } else {
        quote! {}
    };


    let output = quote! {
        #header_struct

        #impl_decl
    };

    TokenStream::from(output)
}

/// switch_vectors macro attribute is a way of declaring function in a cell header with a signature () -> !
///
/// This function provides additional code generation for interrupt vector switching to ones declared in other cell
///
/// # Warning
/// Do not forget to deinitialize everything you don't need before calling this function!
/// E.g. disabling unused interrupts, resetting peripheral to default state, etc.
///
/// If run is the only function to be called in other cell, it is allowed to overlap ram regions, considering full
/// deinitialization and resetting all the peripherals before calling run().
pub fn switch_vectors(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

//dummy ram_region
pub fn ram_region(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

//dummy flash_region
pub fn flash_region(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

pub fn device(_item: TokenStream) -> TokenStream {
    TokenStream::new()
}