Search code examples
cinterruptesp32bare-metalsetjmp

using setjmp/longjmp to time-slice tasks using a timer on ESP32S3 bare-metal


I have implemented in C a round-robin scheduler that cycles through 3 tasks. I want to time-slice my tasks, meaning that when a task takes more than a pre-determined timeout value x, it is interrupted and the next task is started instead.

In the scheduler's loop, I save the context using setjmp() before launching a task

while(1) {
    int jump = setjmp(jmpbuf);
    if (jump == 1) {
        printf("TASK TOOK TOO LONG, SCHEDULING NEXT ONE INSTEAD.\n");
    } else {
        taskRun(sched->current);
    }
    sched->current = sched->current->next;
}

Using timer group 0 on the esp32s3, I set up an alarm which triggers an interrupt that has a handler my_isr, which resets the interrupt and alarm registers and performs a longjmp to the previously saved context.

void my_isr() {
    printf("Panic!\n");

    TIMG_INT_CLR_TIMERS_REG |= BIT(0);

    reset(); //this resets the registers in timer group 0 and reenables the alarm.

    longjmp(jmpbuf, 1);
}

The first time a task takes too long, the alarm is triggered and the interrupt is correctly thrown and the next task is scheduled. But further on when the alarm is triggered again (which I can see because the alarm bit does flip), the interrupt is never thrown.

In my case, I highly suspect that the longjmp is somehow responsible since when I modify my_isr to only call reset() and then return (which returns to the task it was executing before the ISR was called), it is indeed able to throw other subsequent interrupts. Though the origin of the problem might completely differ.

I have found no conclusive way to solve this problem, do you have any ideas as to how to resolve it? Please note that I have to use longjmp.


Solution

  • So, after a bit of fiddling, I discovered that the issue was with the interrupt signal number that I was using. After having a look at the esp32s3's technical reference manual, I found out that interrupt signal 19 is level-triggered. By using interrupt signal 10 instead, which is edge-triggered, the program now behaves as expected.

    I believe the problem lied in the difference between the aforementioned trigger modes. The first one will "hold the level till the CPUx handles the interrupts" whilst in the latter "CPUx responds to this kind of interrupts immediately".

    As pointed out in the busybee's answer, the ISR never returns using longjmp, which causes the level to be indefinitely maintained by the interrupt source. By using edge-triggered interrupts, there is no need to hold the level, and therefore multiple subsequent interrupts can be thrown.