El cambio de tareas en Cortex-M3 se bloquea después de IRQ

He usado un modelo exokernel para mi sistema operativo ARM. Cuando una tarea quiere leer desde un UART, llama a una función de biblioteca que, si no hay datos, realiza una llamada SVC para bloquear la tarea (lo que hace que el kernel coloque la tarea en la cola de espera para esa IRQ y habilita la IRQ). ). Cuando ocurre la interrupción, todas las tareas que la esperan se mueven a la cola ejecutable y la interrupción se desactiva nuevamente.

Este modelo funcionaba bien cuando tenía una serie fija de tareas, pero ahora me he movido a listas vinculadas para permitir más tipos de colas de espera (por ejemplo, mensajes IPC). Algo en el cambio está provocando un bloqueo. Aquí está la salida de depuración:

Creating task 0 (idle task)
task0 stack top is 2007cd20
Starting SysTick @ 100Hz
Becoming task 0
Switching to task gsm@2007c008 with SP 2007c3e8
GSM task starting
Switching to task rfid@2007c430 with SP 2007c810
Monitoring RFID reader
Blocking task rfid on IRQ 7
Switching to task gps@2007c858 with SP 2007cc38
Switching to task task0@2007cc80 with SP 2007ccd8
Switching to task gsm@2007c008 with SP 2007c390
Blocking task gsm on IRQ 8
Switching to task gps@2007c858 with SP 2007cc38
Switching to task task0@2007cc80 with SP 2007ccd8
Switching to task gps@2007c858 with SP 2007cc38
Starting GPS tracking
Blocking task gps on IRQ 6
Switching to task task0@2007cc80 with SP 2007ccd8
[... repeats...]
Switching to task task0@2007cc80 with SP 2007ccd8
Unblocking tasks waiting on IRQ 8
Switching to task gsm@2007c008 with SP 2007c3a0
Switching to task task0@2007cc80 with SP 2007ccd8
Switching to task gsm@2007c008 with SP 2007c3a0
Fault: �� �
   r0 = 2007c3a0
   r1 = 10007fb8
   r2 = 2007ccd8
   r3 = 10007fb8
  r12 = 00000008
   lr = fffffffd
   pc = 0070c858
  psr = 00000003
 BFAR = e000ed38
 CFSR = 00040000
 DFSR = 00000000
 AFSR = 00000000
SHCSR = 00070008

Así que todo está bien hasta la interrupción. La salida real varía según qué UART tenga datos primero, pero el patrón es el mismo: cuando ocurre una interrupción, ocurre una falla cuando la tarea desbloqueada se cambia a la segunda vez .

Aquí están los bits de código relevantes. Una cuña de montaje:

zeptos_pendsv_isr:
    push {lr}
    mrs r0, psp
    stmfd r0!, {r4-r11}
    bl zeptos_schedule
    ldmfd r0!, {r4-r11}
    msr psp, r0
    pop {pc}

Y las funciones de C:

static void pendsv(void) {
    SCB->ICSR |= 1 << 28;
}

void *zeptos_schedule(void *sp) {
    if (current_task) {
        current_task->sp = sp;
        DL_APPEND(runnable_tasks, current_task);
    }
    current_task = runnable_tasks;
    DL_DELETE(runnable_tasks, current_task);
    zeptos_printf("Switching to task %s@%p with SP %p\n", current_task->name, current_task, current_task->sp);
    return current_task->sp;
}

static void block(void *sp, uint8_t irq) {
    zeptos_printf("Blocking task %s on IRQ %i\n", current_task->name, irq);
    current_task->sp = sp;
    DL_APPEND(irq_blocked_tasks[irq], current_task);
    current_task = 0;
    NVIC_EnableIRQ(irq);
    pendsv();
}

void __attribute__((interrupt)) zeptos_isr(void) {
    int irq = (SCB->ICSR & 0xff) - 16;
    zeptos_printf("Unblocking tasks waiting on IRQ %i\n", irq);
    NVIC_DisableIRQ(irq);
    // NVIC_ClearPendingIRQ(irq);
    DL_CONCAT(runnable_tasks, irq_blocked_tasks[irq]);
    irq_blocked_tasks[irq] = 0;
    pendsv();
}

void __attribute__((interrupt)) zeptos_svc_isr(void) {
    __disable_irq();
    uint32_t *sp = (uint32_t *) __get_PSP();
    uint32_t pc = sp[6];
    uint8_t svc_type = *((uint8_t *) pc - 2);
    switch (svc_type) {
        case 0:
            sleep(sp[0]);
            break;

        case 1:
            block(sp, sp[0]);
            break;

        default:
            zeptos_puts("Bad SVC type\n");
    }
    __enable_irq();
}

void Zeptos_BlockOnIrq(uint8_t irq) {
    asm("svc 1");
}

SVC, SysTick y PendSV tienen prioridad 29, 30 y 31 respectivamente.

¿Alguna sugerencia? ¿Dónde debería estar buscando?

Esto probablemente sea más adecuado para StackOverflow. ¿Sabes si tus DL_Xfunciones/macros son atómicas? Supongo que la implementación de su lista vinculada no es segura para subprocesos. Editar: Además, ¿cómo se ve tu código de cambio de contexto?
Intentaré una publicación en SO, pero el chat de EE sugerido aquí fue mejor. Las DL_*funciones no son atómicas. Son de uclist. Sin embargo, he intentado deshabilitar/habilitar las interrupciones en torno a los cambios de la lista, lo que no hizo ninguna diferencia. El código de cambio de contexto es zeptos_pendsv_isry zeptos_schedule. Es bastante simple round-robin en este momento.
@tangrs Esta pregunta también está bien aquí

Respuestas (2)

Encontré el problema, finalmente . Cuando mi controlador SVC llama blockpara poner una tarea en la lista bloqueada, la pila de esa tarea solo tiene los registros apilados por el hardware, y no los {r4-r11}que el programador espera que haya cuando la vuelva a ejecutar más tarde.

La solución rápida es tener una corrección de ensamblaje para el SVC ISR que apila y desapila los registros adicionales, y hacer que la zeptos_svc_isrfunción C devuelva un puntero de pila como zeptos_schedulelo hace. Funciona, pero algunas refactorizaciones están en orden ahora.

Hay algunas cosas aquí. Te has perdido guardar r12 en la tienda.

APCS tampoco exige guardar r0-r3, también deberá hacer algo para guardarlos.

IIRC, las excepciones aún tienen r13 y r14 separados, por lo que es posible que también deba almacenar/restaurar el modo normal r13/r14 durante esto.

{r0-r3, r12, lr, pc} y PSR ya están apilados por el hardware. No hay un r14 separado, pero lo conservo.