[Tinyos-beta-commits]
CVS: tinyos-1.x/beta/teps/txt tep110.txt, NONE,
1.1 tep111.txt, NONE, 1.1
Phil Levis
scipio at users.sourceforge.net
Sat Aug 13 15:12:08 PDT 2005
Update of /cvsroot/tinyos/tinyos-1.x/beta/teps/txt
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv6031
Added Files:
tep110.txt tep111.txt
Log Message:
TEPs 110 (service distributions) and 111 (message_t).
--- NEW FILE: tep110.txt ---
============================
Service Distributions
============================
:TEP: 110
:Group: Core Working Group
:Type: Documentary
:Status: Draft
:TinyOS-Version: 2.x
:Author: Philip Levis
:Draft-Created: 20-Jun-2005
:Draft-Version: $Revision: 1.1 $
:Draft-Modified: $Date: 2005/08/13 22:12:05 $
:Draft-Discuss: TinyOS Developer List <tinyos-devel at mail.millennium.berkeley.edu>
.. Note::
This memo documents a part of TinyOS for the TinyOS Community, and
requests discussion and suggestions for improvements. Distribution
of this memo is unlimited. This memo is in full compliance with
TEP 1.
Abstract
====================================================================
This memo describes the structure and implementation of TinyOS 2.x
service distributions, collections of components designed to work
as a cohesive whole for application builders. It documents one
example distribution, OSKI.
1. Introduction
====================================================================
The TinyOS component model allows flexible composition, but that
flexibility is often limited by reasons which are not explicitly
stated in components. These implicit assumptions can manifest as buggy
behavior. In TinyOS 1.x, on the Telos platform, if a program
simultaneously initializes non-volatile storage and the radio, one of
them will fail: a program has to initialize them serially. They can
also manifest as compile-time errors: if two separate communication
services happen to receive packets of the same AM type, the nesC
compiler will issue a warning due to the buffer swap semantics.
On one hand, the flexbility components provide allows expert users to
build complex and intricate applications. On the other, it can make
managing complexity and intricacy very difficult when building very
simple and basic applications. To promote this latter class of
development, TinyOS 2.x has the notion of "distributions," which are
collections of system abstractions that are designed to be used
together. As long as a user implements an application in terms of the
distribution, the underlying components will operate correctly and
there will be no unforseen failures.
This memo documents an example distribution, named OSKI (Operating
System Key Interfaces). It describes the services OSKI provides and
how their implementations are structured to simplify application
writing.
2. Distributions
====================================================================
A distribution presents *services* to the programmer. A service is a
set of generic (instantiable) components that represent API
abstractions. To use an abstraction, a programmer instantiates the
generic. For example, OSKI has the ``AMSenderC`` abstraction for
sending active messages. The AMSender generic component takes a single
parameter, the AM type. For example, if a programmer wants a component
named AppM to send active messages of type 32, the configuration would
look something like this:
| configuration AppC {}
| implementation {
| components AppM, new AMSenderC(32);
| AppM.AMSend -> AMSenderC;
| }
Services often present abstractions at a fine grain. For example, the
active message service has AMSender, AMReceiver, and AMSnooper, each
of which is a separate abstraction.
2.1 Controlling a Service
--------------------------------------------------------------------
Every service has two abstractions: ``ServiceC``, for powering it on
and off, and ``ServiceNotifierC``, for learning when the service's
power state has changed. For example, active messages have the
``AMServiceC`` and ``AMServiceNotifierC`` abstractions. A service
abstraction provides the ``Service`` interface
| interface Service {
| command void start();
| command void stop();
| command bool isRunning();
| }
while a notifier abstraction provides the ``ServiceNotify`` interface
| interface ServiceNotify {
| event void started();
| event void stopped();
| }
For example, if a routing layer wants to be able to turn the active
message layer on and off, then it needs to instantiate an AMServiceC.
However, many components may be using active messages and have their
own instances of AMServiceC; while routing might consider it
acceptable to turn off active messages, other components might
not. Therefore, a service abstraction does not necessarily represent
explicit control; instead, each service has a *policy* for how it
deals with control requests. When a service changes its activity
state, it MUST signal all instances of its ServiceNotifierC.
For example, the active messages service has an "OR" policy; the
service remains active if *any* of its ServiceC instances are in the
on state, and goes inactive if and only if *all* of its ServiceC
instances are in the off state. This is an example timeline for
active messages being used by two components, RouterA and RouterB:
1. System boots: active messages are off.
2. RouterA calls ``Service.start()``. The AM layer is turned on.
3. All AMServiceNotifierC abstractions signal ``ServiceNotify.started().``
4. RouterB calls ``Service.start()``.
5. RouterA calls ``Service.stop()``. RouterB is still using active messages, so the layer stays on.
6. RouterB calls ``Service.stop()``. The AM layer is turned off.
7. All AMServiceNotifierC abstractions signal ``ServiceNotify.stopped().``
By default, a service that has a control interface MUST be off. For an
application to use the service, at least one component has to call
``Service.start()``.
2.2 Service Initialization
--------------------------------------------------------------------
Because distributions are collections of services that are designed to
work together, they can avoid many of the common issues that arise
when composing TinyOS programs. For example, user code does not have
to initialize a service; this is done automatically by the
distribution. If a user component instantiates a service abstraction,
the distribution MUST make sure that the service is properly
initialized. Section 4 goes into an example implementation of how a
distribution can achieve this.
3. OSKI Services
====================================================================
This section briefly describes the services that OSKI, an example
distribution provides. It is intended to give a feel for how a
distribution presents its abstractions.
3.1 Timers
--------------------------------------------------------------------
OSKI provides timers at one fidelity: milliseconds. Timers do not have
a Service abstraction, as their use implicitly defines whether the
service is active or not (the timer service is off if there are no
pending timers). The ``OSKITimerMsC`` component provides the
abstraction: it provides a single interface, ``Timer<TMilli>``.
This is an example code snippet for instantiating a timer in a
configuration:
| configuration ExampleC {
| uses interface Timer<TMilli>;
| }
|
| configuration TimerExample {}
| implementation {
| components ExampleC, new OSKITimerMsC() as T;
| ExampleC.Timer -> T;
| }
3.2 Active Messages
--------------------------------------------------------------------
OSKI provides four functional active messaging abstractions:
``AMSender``, ``AMReceiver``, ``AMSnooper``, and
``AMSnoopingReceiver``. Each one takes an ``am_id_t`` as a parameter,
indicating the AM type. Following the general TinyOS 2.x approach to
networking, all active message abstractions provide the ``Packet`` and
``AMPacket`` interfaces.
AMSender
This abstraction is for sending active messages. In addition to Packet
and AMPacket, it provides the AMSend interface.
AMReceiver
This abstraction is for receiving active messages addressed to the
node or to the broadcast address. In addition to Packet and AMPacket,
it provides the Receive interface.
AMSnooper
This abstraction is for receiving active messages addressed to other
nodes ("snooping" on traffic). In addition to Packet and AMPacket,
it provides the Receive interface.
AMSnoopingReceiver
A union of the functionality of AMReceiver and AMSnooper, this
abstraction allows a component to receive *all* active messages
that it hears. The AMPacket interface allows a component to determine
whether such a message is destined for it. In addition to
Packet and AMPacket, this component provides the Receive interface.
This snippet of code is an example of a configuration that composes a
routing layer with needed active message abstractions. This
implementation snoops on data packets sent by other nodes to improve
its topology formation:
| configuration RouterC {
| uses interface AMSend as DataSend;
| uses interface AMSend as ControlSend;
| uses interface Receive as DataReceive;
| uses interface Receive as BeaconReceive;
| }
|
| configuration RoutingExample {
| components RouterC;
| components new AMSender(5) as CSender;
| components new AMSender(6) as DSender;
| components new AMReceiver(5) as CReceiver;
| components new AMSnoopingReceiver(6) as DReceiver;
|
| RouterC.DataSend -> DSender;
| RouterC.ControlSend -> CSender;
| RouterC.DataReceive -> DReceiver;
| RouterC.ControlReceive -> CReceiver;
| }
The active messages layer has control abstractions, named ``AMServiceC``
and ``AMServiceNotifierC``. Active messages follow an OR policy.
3.3 Broadcasts
--------------------------------------------------------------------
In addition to active messages, OSKI provides a broadcasting service.
Unlike active messages, which are addressed in terms of AM addresses,
broadcasts are address-free. Broadcast communication has two
abstractions: ``BroadcastSenderC`` and ``BroadcastReceiverC``, both of
which take a parameter, a broadcast message type. This parameter is
similar to the AM type in active messages. Both abstractions provide
the Packet interface. The broadcast service has control abstractions,
named ``BroadcastServiceC`` and ``BroadcastServiceNotifierC``, which
follow an OR policy.
3.4 Tree Collection/Convergecast
--------------------------------------------------------------------
**NOTE: These services are not supported as of the 2.x prerelease.
They will be supported by the first full release.*
OSKI's third communication service is tree-based collection routing.
This service has four abstractions:
CollectionSenderC
This abstraction is for sending packets up the collection tree,
to the collection root. It provides the Send and Packet interfaces.
CollectionReceiverC
This abstraction is for a collection end-point (a tree root). It
provides the Receive, Packet, and CollectionPacket interfaces.
CollectionInterceptorC
This abstraction represents a node's ability to view and possibly
suppress packets it has received for forwarding. It provides
the Intercept, CollectionPacket, and Packet interfaces.
CollectionSnooperC
This abstraction allows a node to overhear routing packets
sent to other nodes to forward. It provides the Receive,
CollectionPacket, and Packet interfaces.
All of the collection routing communication abstractions take a
parameter, similar to active messages and broadcasts, so multiple
components can independelty use collection routing. In addition to
communication, collection routing has an additional abstraction:
CollectionControlC
This abstraction controls whether a node is a collection root
or not.
Finally, collection routing has ``CollectionServiceC`` and
``CollectionServiceNotifierC`` abstractions, which follow an OR
policy.
3.5 UART
--------------------------------------------------------------------
**NOTE: These services are not supported as of the 2.x prerelease.
They will be supported by the first full release.
They will be fully defined pending discussion/codification of
UART interfaces.**
4. OSKI Service Structure and Design
====================================================================
Presenting services through abstractions hides the underlying wiring
details and gives a distribution a great deal of implementation
freedom. One issue that arises, however, is initialization. If a user
component instantiates a service, then a distribution MUST make sure
the service is initialized properly. OSKI achieves this by
encapsulating a complete service as a working component; abstractions
export the service's interfaces.
4.1 Example: Timers
--------------------------------------------------------------------
For example, the timer service provides a single abstraction,
OskiTimerMilliC, which is a generic component. OskiTimerMilliC provides a
single instance of the Timer<TMilli> interface. It is implemented as a
wrapper around the underlying timer service, a component named
``TimerMilliImplP``, which provides a parameterized interface and
follows the Service Instance design pattern[sipattern]_:
| generic configuration OskiTimerMilliC() {
| provides interface Timer<TMilli>;
| }
| implementation {
| components TimerMilliImplP;
| Timer = TimerMilliImplP.TimerMilli[unique("TimerMilliC.TimerMilli")];
| }
TimerMilliImplP is a fully composed and working service. It takes
a platform's timer implementation and makes sure it is initialized through
the TinyOS boot sequence[boot]_:
| configuration TimerMilliImplP {
| provides interface Timer<TMilli> as TimerMilli[uint8_t id];
| }
| implementation {
| components TimerMilliC, Main;
| Main.SoftwareInit -> TimerMilliC;
| TimerMilli = TimerMilliC;
| }
This composition means that if any component instantiates a timer,
then TimerMilliImplP will be included in the component graph. If
TimerMilliImplP is included, the TimerMilliP (the actual platform HIL
implementation) will be properly initialized at system boot time. In
this case, the order of initialization isn't important; in cases where
there are services that have to be initialized in a particular
sequence to ensure proper ordering, the Impl components can
orchestrate that order. For example, a distribution can wire
Main.SoftwareInit to a DistributionInit component, which calls
sub-Inits in a certain order; when a service is included, it wires
itself to one of the sub-Inits.
The user does not have to worry about unique strings to manage the
underlying Service Instance pattern: the abstractions take care of
that.
4.2 Example: Active Messages
--------------------------------------------------------------------
Active messaging reprsent a slightly more complex service, as it has
several abstractions and a control interface. However, it follows the
same basic pattern: abstractions are generics that export wirings to
the underlying service, named ``ActiveMessageImplP``:
| configuration ActiveMessageImplP {
| provides {
| interface SplitControl;
| interface AMSend[am_id_t id];
| interface Receive[am_id_t id];
| interface Receive as Snoop[am_id_t id];
| interface Packet;
| interface AMPacket;
| }
| }
|
| implementation {
| components ActiveMessageC, Main;
|
| Main.SoftwareInit -> ActiveMessageC;
|
| SplitControl = ActiveMessageC;
| AMSend = ActiveMessageC;
| Receive = ActiveMessageC.Receive;
| Snoop = ActiveMessageC.Snoop;
| Packet = ActiveMessageC;
| AMPacket = ActiveMessageC;
| }
For example, this is the AMSender abstraction:
| generic configuration AMSenderC(am_id_t AMId) {
| provides {
| interface AMSend;
| interface Packet;
| interface AMPacket;
| }
| }
|
| implementation {
| components ActiveMessageImplP as Impl;
|
| AMSend = Impl.AMSend[AMId];
| Packet = Impl;
| AMPacket = Impl;
| }
AMReceiver is similar, except that it wires to the Receive interface,
while AMSnooper wires to the Snoop interface, and AMSnoopingReceiver
provides a single Receive that exports both Snoop and Receive (the
unidirectional nature of the Receive interface makes this simple to
achieve, as it represents only fan-in and no fan-out).
ActiveMessageImplP does not provide a Service interface; it provides
the SplitControl interface of the underlying active message
layer. OSKI layers a *ServiceController* on top of SplitControl. As
the active message service follows an OR policy, OSKI uses a
``ServiceOrControllerM``, which is a generic component with the
following signature:
| generic module ServiceOrControllerM(char strID[]) {
| provides {
| interface Service[uint8_t id];
| interface ServiceNotify;
| }
| uses {
| interface SplitControl;
| }
| }
ServiceOrControllerM follows the Service Instance pattern[sipattern];
it calls its underlying SplitControl based on the state of each of its
instances of the Service interface. The parameter denotes the string
used to generate the unique service IDs. The active messages service
controller implementation, AMServiceImplP, instantiates a
ServiceOrControllerM, wires it to ActiveMessageImplP:
| configuration AMServiceImplP {
| provides interface Service[uint8_t id];
| provides interface ServiceNotify;
| }
| implementation {
| components ActiveMessageImplP;
| components new ServiceOrControllerM("AMServiceImplP.Service");
|
| Service = ServiceOrControllerM;
| ServiceOrControllerM.SplitControl -> ActiveMessageImplP;
| ServiceNotify = ServiceOrControllerM;
| }
AMServiceC then provides an instance of AMServiceImplP.Service:
| generic configuration AMServiceC() {
| provides interface Service;
| }
|
| implementation {
| components AMServiceImplP;
|
| Service = AMServiceImplP.Service[unique("AMServiceImplP.Service")];
|}
Note that the two strings are the same, so that the uniqueCount() in
the ServiceOrControllerM is correct based on the number of instances
of AMServiceC. As with timers, encapsulating the service instance
pattern in generic components relieves the programmer of having to
deal with unique strings, a common source of bugs in TinyOS 1.x code.
4.3 OSKI Requirements
--------------------------------------------------------------------
OSKI is a layer on top of system components: it presents a more
usable, less error-prone, and simpler interface to common TinyOS
functionality. For OSKI to work properly, a platform MUST be compliant
with the following TEPs:
o TEP 102: Timers
o TEP 106: Schedulers and Tasks
o TEP 107: TinyOS 2.x Boot Sequence
o TEP 1XX: Active Messages
o TEP 1XX: Collection Routing
Not following some of these TEPS MAY lead to OSKI services being
inoperable, exhibit strange behavior, or being uncompilable.
5. Distribution Interfaces
====================================================================
The basic notion of a distribution is that it provides a self-contained,
tested, and complete (for an application domain) programming interface
to TinyOS. Layers can be added on top of a distribution, but as a
distribution is a self-contained set of abstractions, adding new
services can lead to failures. A distribution represents a hard line
above which all other code operates. One SHOULD NOT add new services,
as they can disrupt the underlying organization. Of course, one MAY
create a new distribution that extends an existing one, but this is
in and of itself a new distribution.
Generally, as distributions are intended to be higher-level abstractions,
they SHOULD NOT provide any asynchronous (async) events. They can,
of course, provide async commands. The idea is that no code written on
top of a distribution should be asynchronous, due to the complexity
introduced by having to manage concurrency. Distributions are usually
platform independent; if an application needs async events, then
chances are it is operating close to the hardware, and so is not
platform independent.
6. Author's Address
====================================================================
| Philip Levis
| 467 Soda Hall
| UC Berkeley
| Berkeley, CA 94720
|
| phone - +1 510 290 5283
|
| email - pal at cs.berkeley.edu
7. Citations
====================================================================
.. [rst] reStructuredText Markup Specification. <http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html>
.. [sipattern] The Service Instance Pattern. In *Software Design Patterns for TinyOS.* David Gay, Philip Levis, and David Culler. Published in Proceedings of the ACM SIGPLAN/SIGBED 2005 Conference on Languages, Compilers, and Tools for Embedded Systems (LCTES'05).
.. [boot] TEP 107: TinyOS 2.x Boot Sequence.
--- NEW FILE: tep111.txt ---
============================
message_t
============================
:TEP: 111
:Group: Core Working Group
:Type: Documentary
:Status: Draft
:TinyOS-Version: 2.x
:Author: Philip Levis
:Draft-Created: 11-Jul-2005
:Draft-Version: $Revision: 1.1 $
:Draft-Modified: $Date: 2005/08/13 22:12:05 $
:Draft-Discuss: TinyOS Developer List <tinyos-devel at mail.millennium.berkeley.edu>
.. Note::
This memo documents a part of TinyOS for the TinyOS Community, and
requests discussion and suggestions for improvements. Distribution
of this memo is unlimited. This memo is in full compliance with
TEP 1.
Abstract
====================================================================
This memo covers the TinyOS 2.x message buffer abstraction, ``message_t``.
It describes the message buffer design considerations, how and where
``message_t`` is specified, and how data link layers should access it.
1. Introduction
====================================================================
In TinyOS 1.x, a message buffer is a TOS_Msg. A buffer contains an
active message (AM) packet as well as packet metadata, such as timestamps,
acknowledgement bits, and signal strength if the packet was received.
TOS_Msg is a fixed size structure whose size is defined by the maximum
AM payload length (the default is 29 bytes). Fixed sized buffers allows
TinyOS 1.x to have zero-copy semantics: when a component receives a
buffer, rather than copy out the contents it can return a pointer
to a new buffer for the underlying layer to use for the next received
packet.
One issue that arises is what defines the TOS_Msg structure, as different
link layers may require different layouts. For example, 802.15.4 radio
hardware (such as the CC2420, used in the Telos and micaZ platforms)
may require 802.15.4 headers, while a software stack built on top of
byte radios (such as the CC1000, used in the mica2 platform) can specify
its own packet format. This means that TOS_Msg may be different on
different platforms.
The solution to this problem in TinyOS 1.x is for there to be a standard
definition of TOS_Msg, which a platform (e.g., the micaZ) can
redefine to match its radio. For example, a mica2 mote uses the standard
definition, which is
| ``typedef struct TOS_Msg {``
| `` /* The following fields are transmitted/received on the radio. */``
| `` uint16_t addr;``
| `` uint8_t type;``
| `` uint8_t group;``
| `` uint8_t length;``
| `` int8_t data[TOSH_DATA_LENGTH];``
| `` uint16_t crc;``
|
| `` /* The following fields are not actually transmitted or received ``
| `` * on the radio! They are used for internal accounting only.``
| `` * The reason they are in this structure is that the AM interface``
| `` * requires them to be part of the TOS_Msg that is passed to``
| `` * send/receive operations.``
| `` */``
| `` uint16_t strength;``
| `` uint8_t ack;``
| `` uint16_t time;``
| `` uint8_t sendSecurityMode;``
| `` uint8_t receiveSecurityMode;``
| ``} TOS_Msg;``
while on a mote with a CC420 radio (e.g., micaZ), TOS_Msg is defined as:
| ``typedef struct TOS_Msg {``
| `` /* The following fields are transmitted/received on the radio. */``
| `` uint8_t length;``
| `` uint8_t fcfhi;``
| `` uint8_t fcflo;``
| `` uint8_t dsn;``
| `` uint16_t destpan;``
| `` uint16_t addr;``
| `` uint8_t type;``
| `` uint8_t group;``
| `` int8_t data[TOSH_DATA_LENGTH];``
| ````
| `` /* The following fields are not actually transmitted or received ``
| `` * on the radio! They are used for internal accounting only.``
| `` * The reason they are in this structure is that the AM interface``
| `` * requires them to be part of the TOS_Msg that is passed to``
| `` * send/receive operations.``
| `` */``
| `` uint8_t strength;``
| `` uint8_t lqi;``
| `` bool crc;``
| `` uint8_t ack;``
| `` uint16_t time;``
| ``} TOS_Msg;``
There are two basic problems with this approach. First, exposing all of
the link layer fields leads components to directly access the packet
structure. This introduces dependencies between higher level components
and the structure layout. For example, many network services built on
top of data link layers care whether sent packets are acknowledged. They
therefore check the ``ack`` field of TOS_Msg. If a link layer does not
provide acknowledgements, it must still include the ``ack`` field
and always set it to 0, wasting a byte of RAM per buffer.
Second, this model does not easily support multiple data link layers.
Radio chip implementations assume that the fields they require are
defined in the structure and directly access them. If a platform
has two different link layers (e.g., a CC1000 *and* a CC2420 radio),
then a TOS_Msg needs to allocate the right amount of space for both
of their headers while allowing implementations to directly access
header fields. This is very difficult to do in C.
The ``data`` payload is especially problematic. Many
components refer to this field, so it must be at a fixed offset.
Depending on the underlying link layer, the header fields
preceding it might have different lengths, and packet-level radios
often require packets to be contiguous memory regions. Overall, these
complexities make specifying the format of TOS_Msg very difficult.
2. message_t
====================================================================
In TinyOS 2.x, the standard message buffer is ``message_t``. The
message_t structure is defined in ``tos/types/TOSMsg.h``:
| ``typedef nx_struct message_t {``
| `` TOSRadioHeader header;``
| `` nx_uint8_t data[TOSH_DATA_LENGTH];``
| `` TOSRadioFooter footer;``
| `` TOSRadioMetadata metadata;``
| ``} message_t;``
This format keeps data at a fixed offset, which is important when
passing a message buffer between two different link layers. If
the data payload is at different offsets for different link layers,
then passing a packet between two link layers requires a ``memmove(3)``
operation (essentially, a copy).
The header, footer, and metadata fields are all opaque. Higher
level components access their fields through interfaces. Section
3 discusses this in greater depth.
Every link layer defines its header, footer, and metadata
structures. For example, the CC1000 radio implementation defines its
structures in ``CC1000Msg.h``:
| ``typedef nx_struct CC1KHeader {``
| `` nx_am_addr_t addr;``
| `` nx_uint8_t length;``
| `` nx_am_group_t group;``
| `` nx_am_id_t type;``
| ``} CC1KHeader;``
| ````
| ``typedef nx_struct CC1KFooter {``
| `` nxle_uint16_t crc; ``
| ``} CC1KFooter;``
| ````
| ``typedef nx_struct CC1KMetadata {``
| `` nx_uint16_t strength;``
| `` nx_uint8_t ack;``
| `` nx_uint16_t time;``
| `` nx_uint8_t sendSecurityMode;``
| `` nx_uint8_t receiveSecurityMode; ``
| ``} CC1KMetadata;``
Each link layer defines its structres, but a **platform** is responsible
for defining ``TOSRadioHeader``, ``TOSRadioFooter``, and ``TOSRadioMetadata``.
This is because a platform may have multiple link layers, and so only
it can resolve which structures are needed. These definitions MUST be
in a file in a platform directory named ``RadioTOSMsg.h``.
The mica2 platform is a simple example, as it has only a CC1000 radio.
Its RadioTOSMsg.h looks like this:
| ``typedef CC1KHeader TOSRadioHeader;``
| ``typedef CC1KFooter TOSRadioFooter;``
| ``typedef CC1KMetadata TOSRadioMetadata;``
For a more complex example, consider a fictional platform named 'megamica'
that has both a CC1000 and a CC2420 radio. Its RadioTOSMsg.h looks like this:
| ``typedef union MegaMicaHeader {``
| `` CC1KHeader cc1k;``
| `` CC2420Header cc2420;``
| ``} MegaMicaHeader;``
|
| ``typedef union MegaMicaFooter {``
| `` CC1KFooter cc1k;``
| `` CC2420Footer cc2420;``
| ``} MegaMicaFooter;``
|
| ``typedef union MegaMicaMetadata {``
| `` CC1KMetadata cc1k;``
| `` CC2420Metadata cc2420;``
| ``} MegaMicaMetadata;``
|
| ``typedef MegaMicaHeader TOSRadioHeader;``
| ``typedef MegaMicaFooter TOSRadioFooter;``
| ``typedef MegaMicaMetadata TOSRadioMetadata;``
If a platform has more than one link layer, it SHOULD define each of the
message_t fields to be a union of the underlying link layer structures.
This ensures that enough space is allocated for all underlying link layers.
3. Accessing message_t fields
====================================================================
A TinyOS component MUST NOT access any message_t fields directly besides
its top-level structures:
o TOSRadioHeader
o data
o TOSRadioFooter
o TOSRadioMetadata
That is, a TinyOS component MUST NOT access any sub-fields of these
structures. Components above the link layer MUST access packet fields
through a nesC interface. For example, active messages have an interface
named ``AMPacket`` which provides access commands to AM fields. In
TinyOS 1.x, a component would directly access ``TOS_Msg.addr``;
in TinyOS 2.x, a component calls ``AMPacket.getAddress(msg)``.
3.1 TOSRadioHeader
----------------------------------------------------------------
Link layer components MAY handle packet fields differently than other
components, as they are aware of the actual packet format. They can
therefore implement the interfaces that provide access to the fields
for other components.
Link layer components MUST NOT directly access sub-fields
of message_t. There are two reasons for this. First, whether
each type is their link type or a union of link types is unknown,
as it can change depending on the platform.
Each of these cases has different C syntax, and in the union case the
name of the field is unknown.
Second, although the structures ensure that enough space is allocated,
C's placement of the structures is not necessarily correct.
Defining a message_t header as a union of the underlying link layer headers
means that, in terms of C structures, a packet may not be contiguous. For
example, consider this case:
| ``typedef struct HeaderA {``
| `` uint8_t a;``
| ``}``
| ``typedef struct HeaderB {``
| `` uint16_t b;``
| ``}``
| ``typedef union MyHeader {``
| `` HeaderA A;``
| `` HeaderB B;``
| ``}``
| ``typedef MyHeader TOSRadioHeader;``
Given nesC nx_struct layout, when TOSRadioHeader is specified as a union
of HeaderA and HeaderB, there will be a padding byte after ``HeaderA.a``.
However, some radios require that packets passed to them are contiguous.
The packet for a link layer does not necessarily start at the beginning
of the message_t. Instead, is starts at a negative offset from the
data field. When a link layer component needs to read or write protocol
header fields, it MUST compute the location of the header as a negative
offset from the data field. The padding bytes that C introduces for
the different sized headers are at the beginning of message_t, not
between the header and payload. For example, let us suppose that HeaderA
has an interface ``APacket``, which provides a command ``a`` that
returns the field ``a`` of HeaderA. Its code looks like this:
| ``command uint8_t APacket.a(message_t* msg) { ``
| `` HeaderA* hdr = (HeaderA*)(msg->data - sizeof(HeaderA));``
| `` return hdr->a;``
| ``}``
We can trust the C compiler to optimize the call into a static offset
memory load.
The following code is incorrect, as it directly casts the header field.
It is an example of what components MUST NOT do:
| ``command uint8_t APacket.a(message_t* msg) { ``
| `` HeaderA* hdr = (HeaderA*)(msg->header);``
| `` return hdr->a;``
| ``}``
The following example is also incorrect, as it directly accesses the
header structure. It is an example of what components MUST NOT do.
| ``command uint8_t APacket.a(message_t* msg) { ``
| `` return msg->header.A.a;``
| ``}``
3.2 TOSRadioFooter
----------------------------------------------------------------
The TOSRadioFooter field ensures that message_t has enough space
to store the footers for all underlying link layers when there
are MTU-sized packets. Like headers, footers are not necessarily
stored where the C structs indicate they are: instead, their
placement is implementation dependent. For example, a link
layer can store a full packet as a contiguous area of memory. In
this case, a short packet will store the footer within the ``data``
field of the structure.
The placement of the packet footer is implementation dependent.
The footer starts at the start of the footer field, and metadata begins
at the start of the metadata field. However, data link components MUST
NOT directly access these fields, as their structure is platform
dependent. A component MUST cast these fields to link specific structures.
Following these rules means that a packet is
The basic issue driving these design considerations is that
the C naming layout of message_t fields is platform specific. As a
data link implementation may be used on many platforms, an implementation
cannot depend on a particular C naming. An implementation can depend on
there being enough space for its header and footer in message_t.
Following this approach means that the packet header and data payload
are contiguous in memory, which allows other communication abstractions
(such as a UART) to easily encapsulate a packet.
4. Author's Address
====================================================================
| Philip Levis
| 467 Soda Hall
| UC Berkeley
| Berkeley, CA 94720
|
| phone - +1 510 290 5283
|
| email - pal at cs.berkeley.edu
7. Citations
====================================================================
.. [rst] reStructuredText Markup Specification. <http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html>
.. [sipattern] The Service Instance Pattern. In *Software Design Patterns for TinyOS.* David Gay, Philip Levis, and David Culler. Published in Proceedings of the ACM SIGPLAN/SIGBED 2005 Conference on Languages, Compilers, and Tools for Embedded Systems (LCTES'05).
.. [boot] TEP 107: TinyOS 2.x Boot Sequence.
More information about the Tinyos-beta-commits
mailing list