Given that, on the ARM Cortex M3, I can:
- atomically read a single bit
- atomically set a single bit
- atomically clear a single bit
How can I combine these for a mutex style set of operations:
try lock
take lock
release lock
It seems that try_lock
or take_lock
would require two operations that would not be atomic.
Do I need more control to accomplish this? Disable global interrupts would do it but it seems there should be a more surgical approach.
Your rwl_TryLock()
doesn't necessarily return a failure if the lock is already held when it's called (your compiler should be giving at least a warning about a code path that has no return value). Try the following:
int rwl_TryLock(volatile uint32_t *lock, int who){
Var_SetBit_BB((uint32_t)lock, who);
if(*lock == (1<<who)){ // check that we have exclusive access
// got the lock!
return 1;
}
// do not have the lock
Var_ResetBit_BB((uint32_t)lock, who); // clear the lock flag
return 0;
}
Note that the above will not work for recursively claiming the same lock (ie., if the task specified by who == 1
already has the lock and tries to claim it again, the code above will not work correctly), but that was true of your original as well.
Also, interrupts can be disabled/enabled on the Cortex M3 pretty quickly (it's a simple update to an NVIC register). Are you sure your system can't live with an additional few cycles of interrupt latency to keep the code for handling the lock data structures simple (which generally means easier to make correct)?
Bit banding won't work for this situation. It is just a really neat way of setting bits in device register files and your memory. Use the Load Exclusive and Store Exclusive instructions to implement your semaphore/mutex. Here's an example document you can use which implements a semaphore using these instructions and it details how this works.
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0439b/CHDDIGAC.html
That being said, you can reduce the memory footprint of your mutexes by using bit banding...
Some for info after some searching:
"
ARM Cortex-M3 bit-banding
ARM's microcontroller core offers yet another way to implement semaphores. Write access to variables in the bit-band alias region causes an atomic read–modify–write access to a memory location in the bit-band region at system bus level.
How does that translate into semaphores? A variable in the bit-band region could serve as container for semaphores. Every client "owns" a bit in that container. Whenever a client needs to claim the semaphore, it sets its own bit by writing a 1 to the corresponding location in the bit-band alias region. It would then read the container (bit-band region) and check that no other bits are set, meaning the client has sucessfully claimed the semaphore. In case that other bits are set, the client would have to clear its own bit again, and retry (perhaps after waiting).
"
(source)
Here is my crude (untested) interpretation:
/*
* Frees a lock.
*
* @note lock must point to a fully aligned 32 bit integer.
* (atomically set to 0)
*
* @returns 1 if successfull
*/
int rwl_FreeLock(volatile uint32_t *lock){
*lock = 0;
return 1; // always successful
}
/*
* Attempts to acquire a lock
* @param who is the client taking the lock
* @lock pointer to the mutex (uint32_t value in memory)
* @note lock must point to a fully aligned 32 bit integer.
* (atomically set to 1 only if set to 0)
*/
int rwl_TryLock(volatile uint32_t *lock, int who){
// initial check of lock
if(*lock == 0){
Var_SetBit_BB((uint32_t)lock, who);
if(*lock == (1<<who)){ // check that we still have exclusive access
// got the lock!
return 1;
} else {
// do not have the lock
Var_ResetBit_BB((uint32_t)lock, who); // clear the lock flag
return 0;
}
}
}
Var_Set_BB / Var_Reset_BB: set / clear a bit using bit banding. (atomic)
However, it does not work!!!
I've never used bit-banding on the ARM; my inclination instead would be to use load-exclusive/store-conditional for all such operations. Use a loop to load-exclusive the old value, compute the new value, and use a conditional store to write it back. Loop until the conditional store succeeds (which it probably will the second time, if it doesn't the first).