简体   繁体   中英

How to safely access memory mapped hardware register from C or C++ language level?

In C and C++ I usually access memory mapped hardware registers with the well known pattern:

typedef unsigned int uint32_t;
*((volatile uint32_t*)0xABCDEDCB) = value;

As far as I know, the only thing guaranteed by the C or C++ standard is that accesses to volatile variables are evaluated strictly according to the rules of the abstract machine.

  1. How can I be sure that the compiler will not generate torn stores for the access for a 32-bit processor? For example the compiler is allowed to emit two 16-bit stores instead of a one 32-bit store, isn't it?
  2. Are there any guarantees in this area made by gcc?

When speeking about MCUs, as far as I know there are no such guarantees. Even more, each case of accessing HW registers may be device specific and often may have its own sequence, rules and/or set of assembler instructions. And it depends on compiler implementation, too. The only thing here that works for me is reading datasheets concering concrete devices/compilers and follow the examples.

Microsoft comment about ISO compliant usage of volatile

"The volatile keyword in C++11 ISO Standard code is to be used only for hardware access"

http://msdn.microsoft.com/en-us/library/12a04hfd.aspx

At least in the case of Microsoft C++ (going back to Visual Studio 2005), an example of a pointer to volatile type is shown:

http://msdn.microsoft.com/en-us/library/145yc477.aspx

Another reference, in this case C, which also includes examples of pointers to volatile types.

"static volatile objects model memory-mapped I/O ports, and static const volatile objects model memory-mapped input ports"

http://en.cppreference.com/w/c/language/volatile

Operations on volatile types are not allowed to be reordered by compiler or hardware, a requirement for hardware memory mapped access. However operations to a combination of volatile and non-volatile types may end up with reordered operations on the non-volatile types, making them non thread safe (all inter thread sharing of variables would require all of them to be volatile to be thread safe). Even if two threads only share volatile types, there's still a data race issue (one thread reads just before the other thread writes).

Microsoft compilers have a non-portable (to other compilers) extension to volatile, that makes them thread safe (/volatile:ms - Microsoft specific, used by default except for ARM processors).

Back to the original question, in the case of GCC, you can have the compiler generate assembly code to verify the operation is safe.

If you are really worried use inline assembler. A single assembler instruction will not return until completed.

Also you must ensure that the memory page you are writing to is not cached otherwise the write may not be all the way through. On ARM memory barriers may be necessary as well.

Volatile is just an instruction which tells the compiler to make no assuptions about the content of the memory since the value may be changed outside one's program but has no effect or read write ordering. Use memory barriers or atomics if this is an issue.

How can I be sure that the compiler will not generate torn stores for the access for a 32-bit processor? For example the compiler is allowed to emit two 16-bit stores instead of a one 32-bit store, isn't it?

Normally, the compiler can combine or split memory accesses under the as-if rule, as long as the observable behavior of the program is unchanged, since the observable behavior of access to ordinary objects is the effect on the object's value, and not the memory access itself.

However, accesses to volatile objects are part of the observable behavior of a program. Therefore the compiler can no longer combine or split memory transactions. In the section where the C++ Standard defines "observable behavior" it specifically says that " Access to volatile objects are evaluated strictly according to the rules of the abstract machine. "

Please note that the code shown is still non-portable C++, because the C++ Standard only cares about whether the object accessed is volatile , and not about modifiers on the pointer used to form an lvalue for said access. You'd need to do something crazy like this example of placement-new, to force the existence of a volatile object:

 *(new volatile uint32 ((uint32*)0xABCDEDCB)) = value;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM