I am porting some legacy code from an ARM926 core to CortexA9. This code is baremetal and does not include an OS or standard libraries, all custom. I am having a failure that appears to be related to a race condition that should be prevented by critical sectioning of the code.
I want some feedback on my approach to see if my critical sections may not be correctly implemented for this CPU. I am using GCC. I suspect there is some subtle error.
Also, is there an opensource library that has these types of primitives for ARM (or even a good lightweight spinlock/semephore library)?
#define ARM_INT_KEY_TYPE unsigned int
#define ARM_INT_LOCK(key_) \
asm volatile(\
"mrs %[key], cpsr\n\t"\
"orr r1, %[key], #0xC0\n\t"\
"msr cpsr_c, r1\n\t" : [key]"=r"(key_) :: "r1", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile ("MSR cpsr_c,%0" : : "r" (key_))
The code is used as follows:
/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);
ARM_INT_UNLOCK(key);
The idea of the "key" is to allow nested critical sections, and these are used at the beginning and end of functions to create reentrant functions.
Thanks!
Answer
The most difficult part of handling a critical section without an OS is not actually creating the mutex, but rather figuring out what should happen if code wants to use a resource which is not presently available. The load-exclusive and conditional-store-exclusive instructions make it fairly easy to create an "swap" function which, given a pointer to an integer, will atomically store a new value but return what the pointed-to integer had contained:
int32_t atomic_swap(int32_t *dest, int32_t new_value)
{
int32_t old_value;
do
{
old_value = __LDREXW(&dest);
} while(__STREXW(new_value,&dest);
return old_value;
}
Given a function like the above, one can easily enter a mutex via something like
if (atomic_swap(&mutex, 1)==0)
{
... do stuff in mutex ... ;
mutex = 0; // Leave mutex
}
else
{
... couldn't get mutex...
}
In the absence of an OS, the main difficulty often lies with the "couldn't get mutex" code. If an interrupt occurs when a mutex-guarded resource is busy, it may be necessary to have the interrupt-handling code set a flag and save some information to indicate what it wanted to do, and then have any main-like code which acquires the mutex check whenever it's going to release the mutex to see whether an interrupt wanted to do something while the mutex was held and, if so, perform the action on behalf of the interrupt.
Although it's possible to avoid problems with interrupts wanting to use mutex-guarded resources by simply disabling interrupts (and indeed, disabling interrupts can eliminate the need for any other kind of mutex), in general it's desirable to avoid disabling interrupts any longer than necessary.
A useful compromise can be to use a flag as described above, but have the main-line code which is going to release the mutex disable interrupts and check the aforementioned flag just before doing so (re-enable interrupts after releasing the mutex). Such an approach doesn't require leaving interrupts disabled very long, but will guard against the possibility that if the main-line code tests the interrupt's flag after releasing the mutex, there's a danger that between the time it sees the flag and the time it acts upon it, it might get preempted by other code which acquires and releases the mutex and and acts upon the interrupt flag; if the main-line code doesn't test the interrupt's flag after releasing the mutex, an interrupt which occurs just before the main-line code releases the mutex might get blocked by the mutex but not noticed by the main-line.
In any case, what's most important will be to have a means by which code that tries to use a mutex-guarded resource when it's unavailable will have a means of repeating its attempt once the resource is released.
No comments:
Post a Comment