My first post here!
I'm enjoying getting to know FreeRTOS and how we can use it for our purpose, plus all the other neat things that go with it.
Anyway, I'm trying to understand the idea of context switching in an ISR on a Pic32MX processor running with FreeRTOS on top.
My colleges are under the impression that when a interrupt occurs, it ALWAYS finishes. And, that's sort of how I think about it too: when the interrupt starts and code is being executed from within an ISR, all code completes without being interrupted by another ISR or even by any other function or anything else.
However, I've come to discover that this isn't necessarily true in FreeRTOS. Or at least from what I have read.
For example, let's assume this is my ISR:
char externalBuffer; //some external buffer that is somewhere else
for(int i=0; i < 10; i++)
tempBuffer[i] = externalBuffer[i];
//do something with tempBuffer here to make some
//difference in the data.
//update the external buffer with changes
for(int i=0; i < 10; i++)
externalBuffer[i] = tempBuffer[i];
Now, I agree this is a silly ISR example that doesn't do much of anything. So please consider it more like pseudo-code for discussion purpose only.
What we had occurring, was that from within an ISR, a deeply nested function was eventually calling taskENTERCRITICAL(). However in the psedo-code above, I've removed the function nesting and am just calling taskENTERCRITICAL() directly from an ISR to make things simpler.
Thefore, here's what we were seeing:
At "Marker #2": As soon as taskENTERCRITICAL() is called, it would succeed. Looking at what taskENTERCRITICAL() does on the Pic32MX is that it disables interrupts. Ok, so interrupts are now disabled. Seems like no big deal.
At "Marker #3": We could then say, copy from externalBuffer to tempBuffer.
At "Marger #4": As soon as taskEXIT_CRITICAL() is called, BAM somehow magically MyISRHandler() starts at "Marker #1". The tempBuffer then get's pushed on the stack on re-entry to MyISRHandler(). The cycle starts all over again.
Eventually, we blow the stack from within the ISR. And, we NEVER get to the point where we reach Marker #5 from within the ISR. The code after Marker #5 is never called.
So the question is? How is that possible? What is going on?
It would seem that what taskEXIT_CRITICAL() does is re-enable interrupts. And soon as interrupts are re-enabled, it would seem the interrupt MyISRHandler() get's fired off again and NEVER reaches "Marker #5".
In looking into this a bit more, I found the following from Richard himself:
When are you using taskENTERCRITICAL() and taskEXITCRITICAL()? It is not clear from your post if this is in the interrupt that is sending the data to the queue, or in the task that is receiving the data.
If its in the interrupt, then basically DON'T. First it would make no sense to do that because the interrupt priorities should be used to ensure things get processed in the right order. If you really must create a critical section then there is an interrupt safe way of doing it, and I can point you in the right direction for that.
If its in the task, then, presumably its after the call to xQueueReceive() has returned? API functions such as xQueueReceive() must not be called in a critical section (I think the above linked page will say that somewhere too.
Especially the bit about not calling API functions that don't end in FromISR from an ISR. This is essential particularly for taskENTERCRITICAL() and taskEXITCRITICAL() which should never be called from an ISR.
I wish I knew what Richard means by "If you really must create a critical section then there is an interrupt safe way of doing it, and I can point you in the right direction for that."? It goes to the question, why would you need a critical section in an interrupt handler in the first place?
Ok, so Richard has made it clear: DO NOT use taskENTERCRITICAL() or taskEXITCRITICAL() from an ISR.
However, it's still not clear to me HOW an interrupt can be essentially pre-empted or how a context switch can occur from within an ISR. Where is the magic that is pre-empting the interrupt? I'm guessing it has something to do with the assembly in Port.c? But then I read this:
Can a context switch occur within an ISR?
Yes. Each RTOS port provides a macro to request a context switch from within an ISR. The name of the macro is dependent on the port (for historic reasons). It will be either portYIELDFROMISR() or portENDSWITCHINGISR. Refer to the documentation page for the port being used.
Every official port comes with a demo application that demonstrates context switching from an ISR.
That means context switching CAN occur within the ISR!
So, it leaves me sort of confused. For one, is this true? Is it possible that a ISR may not always execute ALL of it's code? I couldn't convince myself that it was possible and I was not able to explain it to someone else either. So I must ask!
Traditionally, ISR's are to be short, but they usually always fully execute all their code too. When an ISR is executing, nothing else is executing? Right?
Here's another link that touches on this topic:
“When a RTOS is running and an interrupt arrives, which subsequently invokes a yeidlFromISR() function, does that constitute a "context switch" as per in the normal Time sharing desktop systems ?”
Calling portYIELDFROMISR() from an interrupt context switches the context of the task that was interrupted, to the task that should run when the interrupt completes. That means, the interrupt interrupts one task, but potentially returns to another. The task that was interrupted will run again, but only when it is the highest priority ready state task. So yes, that is a context switch, but the context switch does not switch the interrupt context but the task context. I don't know how that relates to your question about time sharing desktop systems though.
Yes, but here we are essentially saying that this ISR handler is somehow associated or assigned to a task, which isn't necessarily true. Like in the case when taskEXIT_CRITICAL() is called. Interrupts are re-enabled, and suddenly you are the top of the same ISR before the ISR has even completed? It's like the interrupt itself was interrupted somehow.
But is that even possible? Or, how is that even possible? I'm having a hard time believing that somehow an interrupt was interrupted.
If someone can help explain what is going on that would be great or at least help to confirm what I see is truly what is occurring with taskEXIT_CRITICAL()?
What is going on? I would truly appreciate the feedback or input from others!
My colleges are under the impression that when a interrupt occurs, it ALWAYS finishes.
And, that's sort of how I think about it too: when the interrupt starts and code is being
executed from within an ISR, all code completes without being interrupted by another ISR
or even by any other function or anything else.
Then your colleges are wrong :o)
However, I've come to discover that this isn't necessarily true in FreeRTOS. Or at least
from what I have read.
Yes. Interrupts can nest. So one interrupt can itself be interrupted by higher priority interrupts. Although eventually all interrupts will complete. Read the configuration and usage section on http://www.freertos.org/portPIC32MIPS_MK4.html
You must not call taskENTERCRITICAL() from an interrupt. You can call the interrupt safe version, which is called port portSETINTERRUPTMASKFROMISR() and portCLEARINTERRUPT_MASK(). Look at the implementation of xQueueGenericSendFromISR() in FreeRTOSSourcequeue.c for an example of how to use it.
Eventually, we blow the stack from within the ISR
On the PIC32 the size of the interrupt stack is set in FreeRTOSConfig.h.
What is going on?
Have you followed the instructions on writing ISRs that are on the same page link as above? You need an asm wrapper.
That means context switching CAN occur within the ISR!
Different ports handle this in different ways, but in most ports the context switch happens at the end of the ISR. I think on the PIC32 the task context is saved on the way in. You can then nest as many interrupts as you like, and any nested interrupt can request a context switch, but the context switch does not happen until the nesting of interrupts has unwound. When FreeRTOS sees that interrupts are no longer nested, and that a context switch has been requested, it restored the context of a different task to that running when the first interrupt was taken.
Don't get confused by reading posts that relate to other FreeRTOS ports as how this is done is very much dependent on the features available on the chip (PIC32 in your case).