@@ -209,9 +209,141 @@ void bad_critical_section(void) {
209209/* CORRECT * /
210210void good_pattern(void) {
211211 mo_sem_wait(semaphore); /* Block outside critical section * /
212-
212+
213213 CRITICAL_ENTER();
214214 /* Quick critical work */
215215 CRITICAL_LEAVE();
216216}
217217```
218+
219+ ## ISR Context Safety Guide
220+
221+ ### Overview
222+ Interrupt Service Routines (ISRs), including timer callbacks, execute in interrupt context with special restrictions.
223+ Violating these restrictions causes heap corruption, deadlocks, or undefined behavior.
224+
225+ ### ISR-Safe vs Task-Only Functions
226+
227+ #### ISR-Safe Functions
228+ These functions can be called from ISR context (timer callbacks, trap handlers):
229+
230+ | Function | Protection | Notes |
231+ |----------|------------|-------|
232+ | `mo_task_id()` | None needed | Read-only |
233+ | `mo_task_count()` | None needed | Read-only |
234+ | `mo_ticks()` | None needed | Volatile read |
235+ | `mo_uptime()` | None needed | Calculated |
236+ | `mo_timer_create/destroy/start/cancel()` | NOSCHED | Full timer API |
237+ | `mo_sem_trywait()` | None | Non-blocking |
238+ | `mo_sem_signal()` | NOSCHED | Wakes waiting tasks |
239+ | `mo_mutex_trylock()` | None | Non-blocking |
240+ | `mo_mutex_unlock()` | NOSCHED | Releases lock |
241+ | `mo_cond_signal/broadcast()` | NOSCHED | Wakes waiters |
242+ | `mo_pipe_nbread/nbwrite()` | None | Non-blocking I/O |
243+ | `mo_logger_enqueue()` | CRITICAL | Deferred logging |
244+ | `isr_puts()` | None | Direct UART output |
245+ | `isr_putx()` | None | Direct UART hex output |
246+
247+ #### Task-Only Functions
248+ These functions must NOT be called from ISR context:
249+
250+ | Function | Reason | Alternative |
251+ |----------|--------|-------------|
252+ | `mo_task_spawn()` | Uses malloc | Pre-create tasks |
253+ | `mo_task_cancel()` | Uses free | Defer to task |
254+ | `mo_task_delay/yield()` | Invokes scheduler | Use timer callback |
255+ | `mo_task_suspend/resume()` | Modifies scheduler | Use semaphore |
256+ | `mo_sem_wait()` | Blocks caller | Use `mo_sem_trywait()` |
257+ | `mo_mutex_lock()` | Blocks caller | Use `mo_mutex_trylock()` |
258+ | `mo_cond_wait()` | Blocks caller | Signal from ISR, wait in task |
259+ | `mo_pipe_read/write()` | Blocks caller | Use `mo_pipe_nbread/nbwrite()` |
260+ | `mo_mq_enqueue/dequeue()` | Uses malloc | Use pipe instead |
261+ | `printf()` | May deadlock | Use `mo_logger_enqueue()` |
262+
263+ ### Timer Callback Patterns
264+
265+ Timer callbacks execute in ISR context. Example safe callback:
266+ ```c
267+ void *my_callback(void *arg) {
268+ /* Safe: ISR-protected logging */
269+ mo_logger_enqueue("Timer fired\n", 12);
270+
271+ /* Safe: Non-blocking semaphore signal to wake task */
272+ mo_sem_signal(signal_sem);
273+
274+ /* Safe: Non-blocking pipe write */
275+ char msg[] = "event";
276+ mo_pipe_nbwrite(event_pipe, msg, sizeof(msg));
277+
278+ /* UNSAFE - do not use in callbacks:
279+ * mo_task_spawn(...); // Uses malloc
280+ * mo_task_delay(10); // Invokes scheduler
281+ * mo_sem_wait(sem); // Blocks caller
282+ * printf(...); // May deadlock
283+ */
284+
285+ return NULL;
286+ }
287+ ```
288+
289+ ### ISR-to-Task Communication Patterns
290+
291+ #### Pattern 1: Semaphore Signaling
292+ ``` c
293+ /* ISR/callback: Signal event */
294+ void *timer_callback (void * arg) {
295+ mo_sem_signal(event_sem);
296+ return NULL;
297+ }
298+
299+ /* Task: Wait for event * /
300+ void event_handler_task(void) {
301+ while (1) {
302+ mo_sem_wait(event_sem); /* Blocks until ISR signals * /
303+ process_event();
304+ }
305+ }
306+ ```
307+
308+ #### Pattern 2: Non-Blocking Pipe
309+ ```c
310+ /* ISR/callback: Send data */
311+ void *sensor_callback(void *arg) {
312+ sensor_data_t data = read_sensor();
313+ mo_pipe_nbwrite(sensor_pipe, &data, sizeof(data));
314+ return NULL;
315+ }
316+
317+ /* Task: Process data */
318+ void sensor_task(void) {
319+ sensor_data_t data;
320+ while (1) {
321+ if (mo_pipe_read(sensor_pipe, &data, sizeof(data)) > 0)
322+ process_sensor_data(&data);
323+ }
324+ }
325+ ```
326+
327+ #### Pattern 3: Deferred Logging
328+ ``` c
329+ /* ISR/callback: Queue log message */
330+ void *debug_callback (void * arg) {
331+ mo_logger_enqueue("Tick!\n", 6);
332+ return NULL;
333+ }
334+ /* Logger task automatically outputs queued messages * /
335+ ```
336+
337+ ### Emergency Debug Output
338+
339+ For debugging trap handlers or when the logger is unavailable, use direct UART:
340+ ```c
341+ void *debug_isr(void *arg) {
342+ isr_puts("[ISR] Value=0x");
343+ isr_putx(some_value);
344+ isr_puts("\r\n");
345+ return NULL;
346+ }
347+ ```
348+
349+ Warning: Direct UART output is blocking and slow. Use only for emergency debugging.
0 commit comments