3.5 Module memory allocation, avoiding recursion, and other details
3.5.3 Platform Independent Types
To simplify networking code, TinyOS has traditionally used structs to define message formats and directly access messages — this avoids the programming complexity and overheads of using marshalling and unmarshalling functions to convert between host and network message representations. For example, the standard header of a packet for the CC2420 802.15.4 wireless radio chip2looks something like this:
typedef struct cc2420_header_t {
uint8_t length; uint16_t fcf; uint8_t dsn; uint16_t destpan; uint16_t dest; uint16_t src; uint8_t type; } cc2420_header_t;
Listing 3.30: CC2420 packet header
That is, it has a one byte length field, a two-byte frame control field, a one byte sequence number, a two byte group, a two byte destination, a two byte source, and one byte type fields. Defining this as a structure allows you to easily access the fields, allocate storage, etc. The problem, though, is that the layout and encoding of this structure depends on the chip you’re compiling for. For example, the CC2420 expects all of these fields to be little-endian. If your microcontroller is big-endian, then you won’t be able to easily access the bits of the frame control field. One commonly used solution to this problem is to explicitly call macros that convert between the microcontroller and the chip’s byte order, e.g. macros like Unix’s htons, ntohl, etc. However, this approach is error-prone, especially when code is initially developed on a processor with the same byte order as the chip.
Another problem with this approach is due to differing alignment rules between processors. On an ATmega128, the structure fields will be aligned on one-byte boundaries, so the layout will work fine. On an MSP430, however, two-byte values have to be aligned on two-byte boundaries: you can’t load an unaligned word. So the MSP430 compiler will introduce a byte of padding after the length field, making the structure incompatible with the CC2420 and other platforms. There are a couple of other issues that arise, but the eventual point is the same: TinyOS programs need to be able to specify platform-independent data formats that can be easily accessed and used.
In TinyOS 1.x, some programs attempted to solve this problem by using gcc’s packed attribute to make data structures platform independent. Packed tells gcc to ignore normal platform struct alignment requirements and instead pack a structure tightly:
typedef struct RPEstEntry {
uint16_t id; uint8_t receiveEst;
} __attribute__ ((packed)) RPEstEntry;
2
Standard in that IEEE 802.15.4 has several options, such as 0-byte, 2-byte, or 8-byte addressing, and so this is just the format TinyOS uses by default.
3.5. Module memory allocation, avoiding recursion, and other details 38
Listing 3.31: The dreaded “packed” attribute in the 1.x MintRoute library
Packed allowed code running on an ATmega128 and on an x86 to agree on data formats. However, packed has several problems. The version of gcc for the MSP430 family (used in Telos motes) doesn’t handle packed structures correctly. Furthermore, packed is a gcc-specific feature, so code that uses it is not very portable. And finally, while packed eliminates alignment differences, it does not change endianness: int16 t maybe be big-endian on one platform and little-endian on another, so you would still have to use conversion macros like htons.
Programming Hint 6: NEVER, EVER USE THE “PACKED” ATTRIBUTE IN PORTABLE CODE.
To keep the convenience of specifying packet layouts using C types while keeping code portable, nesC 1.2 introduced platform independent types. Simple platform independent types (integers) are either big-endian or little-endian, independently of the underlying chip hardware. Generally, an external type is the same as a normal type except that it hasnx ornxle preceding it:
nx_uint16_t val; // A big-endian 16-bit value
nxle_uint32_t otherVal; // A little-endian 32-bit value
In addition to simple types, there are also platform independent structs and unions, declared with nx struct and nx union. Every field of a platform independent struct or union must be a platform independent type. Non-bitfields are aligned on byte boundaries (bitfields are packed together on bit boundaries, as usual). For example, this is how TinyOS 2.0 declares the CC2420 header:
typedef nx_struct cc2420_header_t {
nxle_uint8_t length; nxle_uint16_t fcf; nxle_uint8_t dsn; nxle_uint16_t destpan; nxle_uint16_t dest; nxle_uint16_t src; nxle_uint8_t type; } cc2420_header_t;
Listing 3.32: The CC2420 header
Any hardware architecture that compiles this structure uses the same memory layout and the same endianness for all of the fields. This enables platform code to pack and unpack structures, without resorting to macros or utility functions such as UNIX socket htonl and ntohs.
Programming Hint 7: USE PLATFORM INDEPENDENT TYPES WHEN DEFINING MESSAGE STRUCTURES.
Under the covers, nesC translates network types into byte arrays, which it packs and unpacks on each access. For most nesC codes, this has a negligible runtime cost. For example, this code
nx_uint16_t x = 5; uint16_t y = x;
rearranges the bytes of x into a native chip layout for y, taking a few cycles. This means that if you need to perform significant computation on arrays of multibyte values (e.g., encryption), then you should copy them to a native format before doing so, then move them back to a platform independent format when done. A
39 3.5. Module memory allocation, avoiding recursion, and other details
single access costs a few cycles, but thousands of accesses costs a few thousand cycles.
Programming Hint 8: IF YOU HAVE TO PERFORM SIGNIFICANT COMPUTATION ON A PLATFORM INDEPENDENT TYPE OR ACCESS IT MANY (HUNDREDS OR MORE) TIMES, TEMPORARILY COPY IT TO A NATIVE TYPE.