Hi there, I included FreeRTOS into a little AVR software project (using ATMega328P and preemptive scheduling), I work on and so far it works pretty well. But while implementing some more features (using binary semaphores to sync with an interrupt) I noticed a strange behaviour, which can cause the whole system to lock.(It might be a general issue with that port.) Imagine the following scenario: Task A waits for an interrupt event using binary semaphores, it blocks when calling "xSemaphoreTake()" – the context is saved (including a set I-flag because we are not in an ISR or deleted the flag manually), then a switchover to task B is performed and the corresponding context restored. Code execution continues… Now an Interrupt occurs (the ISR Task A waits for) – the I-flag is deleted (by the CPU) causing all interrupts to be disabled (including the scheduler) and the ISR code is called. Now the context (of task B) is saved (including the deleted I-flag), some operations are performed and "xSemaphoreGiveFromISR()" is called, waking task A. Because it has a higher priority than B, a context switch (to task A) is performed. The context is restored with the I-Flag set (ok so far, because this flag would have been set by the "reti" instruction anyway). Task A executes and then blocks again waiting for the next Interrupt – the context is saved, the switch to task B is done. Now the context of task B is restored but with the I-flag being DELETED (because it was saved from within an ISR). Since the return is done using "ret", the flag is not set and the interrupts – including the scheduler – remain disabled! Task B executes, but has no idea that interrupts got disabled. Unless interrupts are (re)enabled manually, task B executes forever waiting for a scheduler to interrupt it, but this will NEVER happen. IMHO this could also happen if tasks are switched by the scheduler (in a timer-ISR) AND if one task blocks waiting for an event (maybe occuring in another task or an ISR). Here the contexts – except for the blocked one – will always be saved with the I-flag cleared. If a task is resumed after another task entered the blocked state from outside an ISR, the I-flag will always remain deleted and the scheduler does not execute anymore. Do I miss an important fact, or is this really a problem in the AVR port (and maybe others)? I already implemented a solution that works for me, but I really want to be sure if this might be a general issue (which was kind of hard to track down). kind regards Felix
I’ve been looking at this and from what I can see a yield is only ever done from the function vPortYield(). If a task is running and gets interrupted then its stack will hold the interrupt stack frame. If the interrupt calls vPortYield() then the stack will also hold the function call stack frame when the task stops running. When the task next starts running it will complete executing vPortYield() at the end of which there is a ret instruction that returns it to the interrupt function, at the end of which is a reti instruction that returns it to the task function with interrupts enabled. Looking at your scenario – > Task A waits for an interrupt event using binary semaphores, > it blocks when > calling "xSemaphoreTake()" – the context is saved (including > a set I-flag because > we are not in an ISR or deleted the flag manually), So the saved status register for Task A has interrupts enabled. > then a > switchover to task > B is performed and the corresponding context restored. Code execution > continues… > Now an Interrupt occurs (the ISR Task A waits for) – the > I-flag is deleted (by > the CPU) causing all interrupts to be disabled (including the > scheduler) and > the ISR code is called. Now the context (of task B) is saved > (including the > deleted I-flag), Correct, so when the context of Task B is saved it has the vPortYield() stack frame with interrupts disabled on top of the interrupt stack frame which has interrupts enabled (otherwise the interrupt would not have occurred). > some operations are performed and > "xSemaphoreGiveFromISR()" > is called, waking task A. Because it has a higher priority > than B, a context > switch (to task A) is performed. The context is restored with > the I-Flag set Yes, when task A continues its status register is restored by the ret instruction at the end of the vPortYield() function that caused it to yield in the first place. > (ok so far, because this flag would have been set by the > "reti" instruction > anyway). I don’t think this statement is correct. Task A continues with its original non interrupt context. There is not reti instruction executed because Task A was not in an interrupt when it stopped executing. It continues executing from the point it last executed which was inside a call to vPortYield() made from a task, not an interrupt. > Task A executes and then blocks again waiting for the next > Interrupt – the context > is saved, the switch to task B is done. > Now the context of task B is restored but with the I-flag > being DELETED (because > it was saved from within an ISR). Yes. > Since the return is done > using "ret", the > flag is not set and the interrupts – including the scheduler > – remain disabled! Again, I think that is incorrect. Task B continues from where it was last executing which was in vPortYield(). When it completes vPortYield() the ret instruction takes it back to the interrupt context where it then executes a reti instruction and returns to its task function with interrupts enabled. This is just my interpretation of the code, but if what you were proposing was true I think the demo app would fail immediately after the idle task was interrupted by a tick interrupt.
After reading your comments and going through the code again, I have to agree with you… With the common scheduler ISR the problem does not occur because the return address saved is not the return address within the task, but the return address to the "reti" statement within the scheduler ISR. So the flag is deleted but restored within the next clock cycles in a correct manner. That was the little detail I missed so far. BUT: In my case I did not want to save the stack frame twice (first after entering the ISR and then again after task A has been woken up). So I saved the context, containing the return address to the task (task B in the scenario) at the beginning of the ISR. Then eventually performed a context switch after all things had been done in the ISR. Since the return address does not point to the "reti" instruction at the end of the ISR (as it is the case for the scheduler), the I-flag might not be restored in a correct manner – if task B is resumed from outside an ISR (e.g. after task A blocks again). I think a solution (for me) without the need to change something in the RTOS files would be to include a second return address (to the ‘reti’ command at the end of the ISR) before saving the context. This is ugly, but would only need two more bytes, which is much better than 33 bytes. Thanks for the hint! regards Felix
Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.