Quality RTOS & Embedded Software

 Real time embedded FreeRTOS RSS feed 
Quick Start Supported MCUs PDF Books Trace Tools Ecosystem


Loading

GCC-ARM Legacy Code?

Posted by dave m on February 16, 2008
I'm playing with FreeRTOS on a GameBoy Advance (ARM7TDMI processor), and I started my port with the GCC version for LPC2000. There's several weird things in there, and I wonder whether they're workarounds for (possibly old) GCC bugs, or whether I just don't understand ARM Deep Magic well enough.

For example, SAVE_CONTEXT does:

STMDBLR,{R0-LR}^
NOP
SUBLR, LR, #60

instead of just:

STMDBLR!,{R0-LR}^

Maybe there's an issue with write-back when you're using the IRQ LR to save USR/SYS regs, including USR/SYS-LR? The same thing shows up in RESTORE_CONTEXT, too.

Also, the stack frame prepared by pxPortInitaliseStack has this comment:

/* Some optimisation levels use the stack differently to others. This
means the interrupt flags cannot always be stored on the stack and will
instead be stored in a variable, which is then saved as part of the
tasks context. */

and it stores a task-specific value for ulCriticalNesting at the bottom of the stack frame. However, despite the comment, it appears that ulCriticalNesting isn't used to set the processor interrupt flags when a task is swapped in. In fact, I think the CriticalNesting counter may introduce a problem: normally, when you go from CriticalNesting == 0 to CriticalNesting > 0, the processor interrupt flags are cleared (IRQ disabled). Therefore, if CriticalNesting > 0, the timer interrupt won't happen, and the task won't be preempted. But if the task YIELDs within a critical section, don't Bad Things happen? When the next task runs, interrupts will be re-enabled. Then, when the first task resumes, interrupts will still be on, even though CriticalNesting > 0. Maybe YIELDing while interrupts are disabled should cause a panic?

RE: GCC-ARM Legacy Code?

Posted by Richard on February 16, 2008
>STMDB LR,{R0-LR}^
>NOP
>SUB LR, LR, #60

This dates back to a discussion regarding a comment in the ARM ARM that was not clear. I just had a brief scan to try and locate it again but couldn't. I will check again later as its an interesting point. I think even if you use the '!' method the NOP would still be required, but this is just from memory I need to find the text.


With reference to the critical section handling: The ulCriticalSection value is not used to set the interrupt flags when a task is swapped in and out - this happens automatically by the saving and restoring of the status registers on the task stack as the task enters/exits exceptions. Instead the critical nesting value is used solely to keep a count of the number of nested calls to taskENTER_CRITICAL(). Take the case where a task does something like this (which would be an odd thing to do):

taskENTER_CRITICAL(); // Nesting count = 1;
taskENTER_CRITICAL(); // Nesting count = 2;
taskYIELD(); // Yield with interrupts disabled. When the task starts again interrupts will remain disabled.
taskEXIT_CRITICAL(); // Interrupts should remain disabled here as the nesting count has been restored.
taskEXIT_CRITICAL(); // Interrupts should be enabled here as the nesting count reaches 0 again.


The code comment you refer to is talking about a time when the nesting count was not used. This was the first ARM port and is years ago now. The original critical section entry method pushed the status register on the stack then disabled interrupts, with the exit method simply popping the status registers from the stack. Using the stack gave you a natural nesting ability but only worked when a frame pointer was used. At high optimisation there is no frame pointer so modifying the stack in this way could not be done. The code comments are just an explanation of why the nesting count is required as it is not used in all ports.

Regards.


RE: GCC-ARM Legacy Code?

Posted by dave m on February 16, 2008
Hi Richard.

Thanks for the reply!

> taskENTER_CRITICAL(); // Nesting count = 1;
> taskENTER_CRITICAL(); // Nesting count = 2;
> taskYIELD(); // Yield with interrupts disabled. When the task starts again interrupts will remain disabled.
> taskEXIT_CRITICAL(); // Interrupts should remain disabled here as the nesting count has been restored.
> taskEXIT_CRITICAL(); // Interrupts should be enabled here as the nesting count reaches 0 again.

I can't think of a situation offhand where you'd do that, though. AFAIK, you typically disable interrupts because you're going to do a non-atomic operation to shared state, and you can't risk some other thread looking at the state (or worse, modifying it) before you're finished. Yet, in the middle, you decide to let any other task run for a while? I guess it could happen if you're calling subs & libraries that aren't really careful about critical sections, but not being careful is a good way to end up debugging.

Anyway, as far as I can tell, the IRQ & FIQ state of a thread when it gets switched to depends on the SPSR that's saved on the thread's own stack when it yielded or was preempted. No muss, no fuss, but you _do_ need a counter or something if you're going to keep track of nested disable/enable interrupts.

> The original critical section entry method pushed the status
> register on the stack then disabled interrupts, with the exit
> method simply popping the status registers from the stack.
> Using the stack gave you a natural nesting ability but only
> worked when a frame pointer was used. At high optimisation
> there is no frame pointer so modifying the stack in this way
> could not be done.

Ah, I see. I'm pretty cautious of using the stack like that in compiled code, so I wouldn't have thought of doing it that way. (It makes plenty of sense, logically, but you're changing the stack in a way that can't be seen from the code, so you never know what the compiler is going to do. There's a reason they put a dragon on the Dragon Book!)

RE: GCC-ARM Legacy Code?

Posted by Richard on February 16, 2008
> can't think of a situation offhand where you'd do that,

The kernel itself relies on this mechanism to implement the scheduler locking. Scheduler locking is done in an attempt minmise the time the kernel keeps interrupts disabled while accessing kernel data structures. Say for example the scheduler is accessing a queue - rather than disable interrupts for the entire operation it locks the scheduler and enables interrupts. Now if the queue operation causes the task to block (maybe the queue is empty and a blocking read is being performed) before the task can yield it must unlock the scheduler - otherwise it would deadlock. However, when the task next runs it needs to have the scheduler locked immediately so it can continue its operation. It does this by yielding from within a critical section. You then get a sequence as follows:

+ lock scheduler
+ perform operation
+ need to yield..but can't with scheduler locked.
+ Enter critical section
+ Unlock scheduler
+ Yield (this and the two steps above are effectively atomic as a critical section is used)
+ Lock scheduler - we start running again from within the critical section.
+ Exit critical section - exit the critical section that was entered prior to yielding
+ Continue with operation.

This is incidentally the most complex part of the code. The critical section is used so when the task starts running again it can immediately relock the scheduler without being preempted. This works because each task maintains its own critical section nesting count - and a task that was in a critical section can yield to a task that is not in a critical section - but when it starts running again it starts from within its own critical section.

Confused?

RE: GCC-ARM Legacy Code?

Posted by dave m on February 16, 2008
> This is incidentally the most complex part of the code. [...] Confused?

Typical...whenever I look at somebody else's code, I always end up wondering about the hairiest part. Just _once_ I'd like to go in with the goal of changing a misspelling in an error message or something :-)

Well, I'll try to clean up the portxxx stuff under portable/GCC/GBA and submit patches.

Y'know what would have been handy for this project, and probably for future porters: a "stub" port with blank functions for just the minimum required support, and comments explaining what each function must do. I've started collecting this info, but I don't know whether I'll get it all. Up to now, it looks like a port only needs to provide some data type #defines (why not typedefs?) and DISABLE_INTERRUPTS, YIELD, and ENTER/EXIT_CRITICAL. YIELD is the only tricky one, since it requires being able to save one context and restore a (possibly) different one.

RE: GCC-ARM Legacy Code?

Posted by Richard on February 16, 2008
Did you see the following page? http://www.freertos.org/FreeRTOS-porting-guide.html - not much but a leg up.

Regards.

RE: GCC-ARM Legacy Code?

Posted by dave m on February 16, 2008
Yep, that's the reference I started with. But the "copy an existing port and delete function bodies" leaves you with a bigger empty framework than FreeRTOS actually needs. One of the hardest things for me was figuring out which bits of asm() are necessary and which can be dropped. The GBA has some quirks that you have to work around (e.g. the ISR entry point is in ROM, and it stacks some regs before you can get control. Plus, you can't use SWI or FIQ because the vectors are in ROM.) But I'm getting there.

Ciao!


[ Back to the top ]    [ About FreeRTOS ]    [ Privacy ]    [ Sitemap ]    [ ]


Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.

Latest News

NXP tweet showing LPC5500 (ARMv8-M Cortex-M33) running FreeRTOS.

Meet Richard Barry and learn about running FreeRTOS on RISC-V at FOSDEM 2019

Version 10.1.1 of the FreeRTOS kernel is available for immediate download. MIT licensed.

View a recording of the "OTA Update Security and Reliability" webinar, presented by TI and AWS.


Careers

FreeRTOS and other embedded software careers at AWS.



FreeRTOS Partners

ARM Connected RTOS partner for all ARM microcontroller cores

Espressif ESP32

IAR Partner

Microchip Premier RTOS Partner

RTOS partner of NXP for all NXP ARM microcontrollers

Renesas

STMicro RTOS partner supporting ARM7, ARM Cortex-M3, ARM Cortex-M4 and ARM Cortex-M0

Texas Instruments MCU Developer Network RTOS partner for ARM and MSP430 microcontrollers

OpenRTOS and SafeRTOS

Xilinx Microblaze and Zynq partner