Improve behaviour when used with RTSupport threads
Two of the locking data types - mutex_t and spinrw_t - offer a lock-with-sleep function. These can cause the current thread to sleep until another thread releases the lock. Sleeping is achieved using OS_UpCall 6, and so supports thread suspension by either TaskWindow or RTSupport.
This is usually fine, but RTSupport, unlike TaskWindow, permits threads to be assigned differing priority levels. Thus, if a lock is held by a low-priority thread when a high-priority thread attempts to lock it, the low-priority thread continues to run (this is a common feature of priority-based threading systems known as priority inversion). This is unavoidable in some situations, but ideally the high-priority thread should be resumed as soon as the shared resource (in this case the lock) is released by the low-priority thread. Previously, there was no mechanism to achieve this with either of these two lock types.
One challenge is to achieve this without making the unlock functions unnecessarily slow in the common case where there is no higher-priority thread to resume. This is achieved in two ways:
-
Yielding is performed using RT_Yield to explicitly rescan pollwords for RTSupport threads only. Since TaskWindow threads are all implicitly of identical priority, we avoid using an OS_UpCall 6 yield, which would cause all TaskWindow pollwords to be rescanned as well.
-
An internal flag inside the lock struct is used to remember whether any other threads attempted to lock it when it was already locked, and a yield is only performed when unlocking if this indicates there is (or might be) another thread waiting.
The situation is complicated by the fact that (despite what the SWI docs say) RT_Yield can cause IRQs to trigger even if the caller has them disabled. This can however be inferred from the entry conditions of RTSupport callback routines, which usually have IRQs enabled, so if any IRQ-enabled routine is pending when we call RT_Yield, IRQs will become enabled. This is a problem because if the calling thread also holds non-re-entrant locks (e.g. a spinlock) then we defeat those locks by pre-empting the thread in this way. To deal with this, we check that IRQs are enabled prior to calling RT_Yield, even though this will also discard many cases where a yield would have been desirable. A future enhancement would be to maintain a thread-local flag, and trigger RT_Yield when the outermost IRQ-disabling lock is released (even if that lock type wouldn't ordinarily cause other threads to sleep) but this presumes thread-local storage, which is not yet implemented for RTSupport.
More sophisticated schemes are possible, such as by comparing the relative priorities of the lock holder and waiter, but these introduce additional overheads into the lock and unlock functions, so unless and until it becomes clear that they are required, they have not been implemented.
Tested using various SDIODriver test harnesses developed as part of pending IO card support.
Admin: cross-compilation and GitLab CI support also added. This project is expected to pass all static_analysis CI jobs once Support/CI_Source!13 (closed) is merged.