In last month’s article, I discussed the dangers of read-modify-write operations when multiple device drivers access the same hardware registers, since basic firmware read-modify-write operations are not atomic. I also discussed how firmware techniques such as shadow copies and mutexes can be used to provide atomic read-modify-write operations, but only if implemented correctly.
Two readers pointed out that some CPUs have atomic read-modify-write commands in their instruction sets. These CPU commands can also be used to assist firmware read-modify-write operations, but such solutions are not without their own dangers.
I had planned to write this month about an atomic register design that provides robust support for concurrent driver access to common registers, but I feel I first need to address solutions that use the CPU’s atomic read-modify-write instructions. I’ve gathered a few examples for discussion.
The PICmicro manual, on page 6, warns that their bit manipulation instructions are implemented as read-modify-write and so the user should bear that in mind on some special function registers, such as ports. The bit manipulation instructions do provide an atomic operation, but it might not be appropriate where reading the register might not reflect what was last written to the register.
For example, an article by a PIC user discusses how, just because an output pin in an I/O port was recently set to 1, the signal line, because of its capacitive load, may not be up to the 1 level yet. So a read from the port register could return a 0—instead of 1—for the corresponding bit. That could cause problems in a read-modify-write from that port register in that a read could incorrectly return 0 for that bit; the subsequent write to that register could cause that incorrect 0 value to overwrite the actual value—1—for that bit. To avoid this situation, the author recommends keeping a shadow copy of the port register and only writing to the port.
The 8051 microcontroller (link) gets around this problem by reading the output latch instead of the input port when read-modify-write instructions are executing.
Another pitfall appears in VAX machines (link) which have atomic read-modify-write operations to memory. VAX system documentation cautions that, due to compiler optimizations, VAX compilers do not guarantee that certain operations, such as x=x+1, will use the read-modify-write operation even though one exists. The documentation then compares VAXen to Alpha systems, which do not have atomic read-modify-write operations and so provide that functionality via special libraries.
While CPUs could provide atomic read-modify-write operations in some situations, they cannot in all situations. Take, for example, a register in an ASIC that communicates with the CPU across a PCI Express bus. Read-modify-write operations on that bus is broken into separate read and write operations. Plus, the bus supports multiple sources and destinations as well as transaction queuing, and could generate exceptions as necessary. The CPU must be able to process other transactions ahead of, and in the middle of, its own separated read and write operations in the queue. This means the read-modify-write operation to a register in an ASIC across the PCI Express bus cannot be atomic.
Read-modify-write operations are necessary. But, as illustrated by these examples, knowledge of the system is required to avoid their potential dangers.
Next month, I will discuss how registers can be designed to eliminate these read-modify-write issues regardless of the CPU used or firmware support.
Until the next atomic newsletter…