Skip to main content

endless_rs/
macros.rs

1//! The `endless_patch!` macro — generates ABI glue and the patch header.
2
3/// Register a [`Patch`](crate::Patch) implementation as the active patch.
4///
5/// Generates all `extern "C"` ABI functions and emits the `PatchHeader`
6/// in the `.patch_header` linker section via `global_asm!`.
7///
8/// # Usage
9///
10/// ```ignore
11/// endless_patch!(MyPatch, MyPatch { field: value });
12/// ```
13///
14/// The first argument is the concrete type implementing `Patch`.
15/// The second argument is the expression used to construct the initial
16/// instance. It is evaluated once, on the first call from the firmware,
17/// whichever entry point that turns out to be.
18#[macro_export]
19macro_rules! endless_patch {
20    ($PatchType:ty, $init_expr:expr) => {
21        // ── singleton storage ────────────────────────────────────────
22        //
23        // Uses `UnsafeCell` with a manual `Sync` impl instead of
24        // `static mut` (which is deprecated in edition 2024).
25
26        struct PatchState {
27            instance: core::mem::MaybeUninit<$PatchType>,
28            initialized: bool,
29            error: bool,
30        }
31
32        /// Wrapper to make `PatchState` usable in a `static`.
33        #[repr(transparent)]
34        struct SyncPatchState(core::cell::UnsafeCell<PatchState>);
35
36        // SAFETY: bare-metal single-core. The firmware never calls two
37        // entry points concurrently, so no two references to the state
38        // are ever live at once.
39        unsafe impl Sync for SyncPatchState {}
40
41        static PATCH_STATE: SyncPatchState = SyncPatchState(core::cell::UnsafeCell::new(PatchState {
42            instance: core::mem::MaybeUninit::uninit(),
43            initialized: false,
44            error: false,
45        }));
46
47        /// Get a mutable reference to the patch state.
48        ///
49        /// # Safety
50        /// Caller must ensure no concurrent access (guaranteed on bare-metal).
51        #[inline(always)]
52        unsafe fn state() -> &'static mut PatchState {
53            unsafe { &mut *PATCH_STATE.0.get() }
54        }
55
56        /// Get the patch instance, constructing it on first use.
57        ///
58        /// Every entry point goes through here, so the instance is valid no
59        /// matter which function the firmware calls first and whether or not
60        /// `patch_init` accepted the ABI version.
61        ///
62        /// # Safety
63        /// Caller must ensure no concurrent access (guaranteed on bare-metal).
64        #[inline(always)]
65        unsafe fn patch() -> &'static mut $PatchType {
66            let s = unsafe { state() };
67            if !s.initialized {
68                s.instance.write($init_expr);
69                s.initialized = true;
70            }
71            unsafe { s.instance.assume_init_mut() }
72        }
73
74        /// Metadata for a raw parameter index, or the defaults when the index
75        /// is out of range.
76        ///
77        /// # Safety
78        /// Caller must ensure no concurrent access (guaranteed on bare-metal).
79        #[inline(always)]
80        unsafe fn param_metadata(idx: i32) -> $crate::ParameterMetadata {
81            match $crate::ParamId::from_raw(idx) {
82                Some(param) => $crate::Patch::param_metadata(unsafe { patch() }, param),
83                None => $crate::ParameterMetadata::default(),
84            }
85        }
86
87        // ── ABI functions (extern "C") ───────────────────────────────
88        //
89        // These are referenced by the header below through `sym` operands,
90        // so they need no `no_mangle` and cannot collide with symbols in
91        // the patch crate.
92
93        unsafe extern "C" fn patch_init(env: *const $crate::abi::PatchEnv) {
94            let supported =
95                !env.is_null() && unsafe { (*env).abi_version } == u32::from($crate::abi::PATCH_ABI_VERSION);
96            if !supported {
97                unsafe { state().error = true };
98                return;
99            }
100            $crate::Patch::init(unsafe { patch() });
101        }
102
103        unsafe extern "C" fn patch_agent_update_buffers(
104            _env: *const $crate::abi::PatchEnv,
105            left: *mut f32,
106            right: *mut f32,
107            samples: i32,
108        ) -> f32 {
109            if left.is_null() || right.is_null() {
110                return 0.0;
111            }
112            // A negative sample count is a firmware bug; treat it as empty
113            // rather than building a slice of absurd length.
114            let n = usize::try_from(samples).unwrap_or(0);
115            let left = unsafe { core::slice::from_raw_parts_mut(left, n) };
116            let right = unsafe { core::slice::from_raw_parts_mut(right, n) };
117            $crate::Patch::process_audio(unsafe { patch() }, left, right)
118        }
119
120        unsafe extern "C" fn patch_agent_set_buffer(_env: *const $crate::abi::PatchEnv, buffer: *mut f32) {
121            if buffer.is_null() {
122                unsafe { state().error = true };
123                return;
124            }
125            let buf = unsafe { core::slice::from_raw_parts_mut(buffer, $crate::WORKING_BUFFER_SIZE) };
126            $crate::Patch::set_working_buffer(unsafe { patch() }, buf);
127        }
128
129        unsafe extern "C" fn patch_agent_get_buffer_size(_env: *const $crate::abi::PatchEnv) -> i32 {
130            $crate::WORKING_BUFFER_SIZE as i32
131        }
132
133        unsafe extern "C" fn patch_agent_get_param_min(_env: *const $crate::abi::PatchEnv, idx: i32) -> f32 {
134            unsafe { param_metadata(idx) }.min
135        }
136
137        unsafe extern "C" fn patch_agent_get_param_max(_env: *const $crate::abi::PatchEnv, idx: i32) -> f32 {
138            unsafe { param_metadata(idx) }.max
139        }
140
141        unsafe extern "C" fn patch_agent_get_param_default(_env: *const $crate::abi::PatchEnv, idx: i32) -> f32 {
142            unsafe { param_metadata(idx) }.default
143        }
144
145        unsafe extern "C" fn patch_agent_is_param_enabled(
146            _env: *const $crate::abi::PatchEnv,
147            idx: i32,
148            source_id: i32,
149        ) -> u8 {
150            let is_knob = source_id == 0;
151            u8::from(is_knob && $crate::ParamId::from_raw(idx).is_some())
152        }
153
154        unsafe extern "C" fn patch_agent_get_param_name(
155            _env: *const $crate::abi::PatchEnv,
156            _idx: i32,
157            out: *mut u8,
158            buf_size: usize,
159        ) {
160            if !out.is_null() && buf_size > 0 {
161                unsafe { *out = 0 };
162            }
163        }
164
165        unsafe extern "C" fn patch_agent_get_param_unit(
166            _env: *const $crate::abi::PatchEnv,
167            _idx: i32,
168            out: *mut u8,
169            buf_size: usize,
170        ) {
171            if !out.is_null() && buf_size > 0 {
172                unsafe { *out = 0 };
173            }
174        }
175
176        unsafe extern "C" fn patch_agent_set_param(_env: *const $crate::abi::PatchEnv, idx: i32, value: f32) {
177            if let Some(param) = $crate::ParamId::from_raw(idx) {
178                $crate::Patch::set_param(unsafe { patch() }, param, value);
179            }
180        }
181
182        unsafe extern "C" fn patch_agent_special_action(_env: *const $crate::abi::PatchEnv, idx: i32) {
183            if let Some(action) = $crate::ActionId::from_raw(idx) {
184                $crate::Patch::handle_action(unsafe { patch() }, action);
185            }
186        }
187
188        unsafe extern "C" fn patch_agent_get_state_idx(_env: *const $crate::abi::PatchEnv) -> i32 {
189            if unsafe { state().error } {
190                return $crate::Color::Red as i32;
191            }
192            $crate::Patch::state_led_color(unsafe { patch() }) as i32
193        }
194
195        // ── PatchHeader via global_asm! ──────────────────────────────
196        //
197        // Emitted in assembly so linker-computed values (image_size,
198        // bss_begin, bss_size) can be filled in at link time. The magic
199        // and ABI version come from `crate::abi` through `const` operands,
200        // and the function addresses from `sym` operands, so the header
201        // cannot drift from the constants `patch_init` checks against.
202        // The linker sets the Thumb bit on function addresses for Thumb
203        // targets; `just check-example` verifies the emitted bytes.
204
205        core::arch::global_asm!(
206            ".section .patch_header, \"a\"",
207            ".balign 4",
208            ".global patch_header",
209            "patch_header:",
210            // magic
211            ".word {magic}",
212            // abi_version (u16) + flags (u16)
213            ".short {abi_version}",
214            ".short 0",
215            // init
216            ".word {init}",
217            // agent function pointers
218            ".word {update_buffers}",
219            ".word {set_buffer}",
220            ".word {get_buffer_size}",
221            ".word {get_param_min}",
222            ".word {get_param_max}",
223            ".word {get_param_default}",
224            ".word {is_param_enabled}",
225            ".word {get_param_name}",
226            ".word {get_param_unit}",
227            ".word {set_param}",
228            ".word {special_action}",
229            ".word {get_state_idx}",
230            // image_size (linker-computed absolute symbol)
231            ".word patch_image_size",
232            // bss_begin
233            ".word __patch_bss_start",
234            // bss_size (linker-computed absolute symbol)
235            ".word patch_bss_size",
236            // reserved[16]
237            ".fill 16, 4, 0",
238            magic = const $crate::abi::PATCH_MAGIC,
239            abi_version = const $crate::abi::PATCH_ABI_VERSION,
240            init = sym patch_init,
241            update_buffers = sym patch_agent_update_buffers,
242            set_buffer = sym patch_agent_set_buffer,
243            get_buffer_size = sym patch_agent_get_buffer_size,
244            get_param_min = sym patch_agent_get_param_min,
245            get_param_max = sym patch_agent_get_param_max,
246            get_param_default = sym patch_agent_get_param_default,
247            is_param_enabled = sym patch_agent_is_param_enabled,
248            get_param_name = sym patch_agent_get_param_name,
249            get_param_unit = sym patch_agent_get_param_unit,
250            set_param = sym patch_agent_set_param,
251            special_action = sym patch_agent_special_action,
252            get_state_idx = sym patch_agent_get_state_idx,
253        );
254    };
255}