Reentrant write-only ports
Recently I posted about the challenges of using a write only port. Then I followed that up with another posting that illustrated the use of C++ to encapsulate the necessary coding nastiness. Hopefully, these two postings conveyed the general ideas – defining the problem and outlining a solution.
However, one aspect of the solution was not addressed: the proposed code was not reentrant …
Reentrancy is a potential issue in any multi-threaded system. It needs to be considered if you are using an RTOS or have mainline code and interrupt service routines. In these cases, a sequence of instructions may be interrupted at any time and another thread be given control of the CPU. At some later point the instruction sequence will be resumed. This is fine, unless it is essential that the sequence be completed in one go, without interruption. Such a sequence is often referred to as a “critical section” and measures need to be taken to avoid the interruption.
In the write only port handling code that I have presented so far, the critical section is the two lines of code that update the shadow copy and the writing to the port, for example:
shadow &= val;
*address = shadow;
The problem is that, if an interrupt occurred after the shadow was updated and the new thread utilizes the port, the correct value may never be written. In some cases that might not matter, but, in others, it is essential that every update is written to the port.
The solution is conceptually quite straightforward: the port needs to be “locked” before this pair of statements and “unlocked” afterwards. The code would simply look like this:
lock();
shadow &= val;
*address = shadow;
unlock()
These member functions – lock() and unlock() – could be implemented in a variety of way. The brute force approach would be to simply disable and re-enable interrupts. With an RTOS, using a semaphore or a mutex would be a better approach.
In the spirit of object oriented programming practice, it would be sensible to add these functions to the write_only_port class, but make them virtual. This means that, where reentrancy is not an issue, the class may be used as it stands. If a reentrant version is required, it may be derived from this base class and the lock() and unlock() instantiated appropriately.