@@ -209,9 +209,144 @@ 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_start/cancel()` | NOSCHED | Timer control only |
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_timer_create()` | Uses malloc | Pre-create timers |
255+ | `mo_timer_destroy()` | Uses free | Defer to task |
256+ | `mo_task_delay/yield()` | Invokes scheduler | Use timer callback |
257+ | `mo_task_suspend/resume()` | Modifies scheduler | Use semaphore |
258+ | `mo_sem_wait()` | Blocks caller | Use `mo_sem_trywait()` |
259+ | `mo_mutex_lock()` | Blocks caller | Use `mo_mutex_trylock()` |
260+ | `mo_cond_wait()` | Blocks caller | Signal from ISR, wait in task |
261+ | `mo_pipe_read/write()` | Blocks caller | Use `mo_pipe_nbread/nbwrite()` |
262+ | `mo_mq_enqueue/dequeue()` | Uses malloc | Use pipe instead |
263+ | `printf()` | May deadlock | Use `mo_logger_enqueue()` |
264+
265+ ### Timer Callback Patterns
266+
267+ Timer callbacks execute in ISR context. Example safe callback:
268+ ```c
269+ void *my_callback(void *arg) {
270+ /* Safe: ISR-protected logging */
271+ mo_logger_enqueue("Timer fired\n", 12);
272+
273+ /* Safe: Non-blocking semaphore signal to wake task */
274+ mo_sem_signal(signal_sem);
275+
276+ /* Safe: Non-blocking pipe write */
277+ char msg[] = "event";
278+ mo_pipe_nbwrite(event_pipe, msg, sizeof(msg));
279+
280+ /* UNSAFE - do not use in callbacks:
281+ * mo_task_spawn(...); // Uses malloc
282+ * mo_task_delay(10); // Invokes scheduler
283+ * mo_sem_wait(sem); // Blocks caller
284+ * printf(...); // May deadlock
285+ */
286+
287+ return NULL;
288+ }
289+ ```
290+
291+ ### ISR-to-Task Communication Patterns
292+
293+ #### Pattern 1: Semaphore Signaling
294+ ``` c
295+ /* ISR/callback: Signal event */
296+ void *timer_callback (void * arg) {
297+ mo_sem_signal(event_sem);
298+ return NULL;
299+ }
300+
301+ /* Task: Wait for event * /
302+ void event_handler_task(void) {
303+ while (1) {
304+ mo_sem_wait(event_sem); /* Blocks until ISR signals * /
305+ process_event();
306+ }
307+ }
308+ ```
309+
310+ #### Pattern 2: Non-Blocking Pipe
311+ ```c
312+ /* ISR/callback: Send data */
313+ void *sensor_callback(void *arg) {
314+ sensor_data_t data = read_sensor();
315+ mo_pipe_nbwrite(sensor_pipe, &data, sizeof(data));
316+ return NULL;
317+ }
318+
319+ /* Task: Process data (non-blocking read, polls or uses semaphore wakeup) */
320+ void sensor_task(void) {
321+ sensor_data_t data;
322+ while (1) {
323+ if (mo_pipe_nbread(sensor_pipe, &data, sizeof(data)) > 0)
324+ process_sensor_data(&data);
325+ mo_task_delay(1); /* Yield or use semaphore for event-driven */
326+ }
327+ }
328+ ```
329+
330+ #### Pattern 3: Deferred Logging
331+ ``` c
332+ /* ISR/callback: Queue log message */
333+ void *debug_callback (void * arg) {
334+ mo_logger_enqueue("Tick!\n", 6);
335+ return NULL;
336+ }
337+ /* Logger task automatically outputs queued messages * /
338+ ```
339+
340+ ### Emergency Debug Output
341+
342+ For debugging trap handlers or when the logger is unavailable, use direct UART:
343+ ```c
344+ void *debug_isr(void *arg) {
345+ isr_puts("[ISR] Value=0x");
346+ isr_putx(some_value);
347+ isr_puts("\r\n");
348+ return NULL;
349+ }
350+ ```
351+
352+ Warning: Direct UART output is blocking and slow. Use only for emergency debugging.
0 commit comments