- Wire Protocol Message
- Data channels
- Receiving and transmitting data
- Receiver workflow
- Wire Protocol Commands
- How to add support for a new command
- How to add support for new channels
- HAL interface
- Application interface
- Debugging Wire Protocol communications
- CRC32 validations
About this document
This document describes the Wire Protocol used by nanoFramework for debug and the booter stage. The protocol follows the implementation of the .NET Micro Framework Wire Protocol. The intention is to review it later in order to improve and simplify it.
The message basic structure is comprised by:
- Signature which is basically a marker to detect the start of a new message packet. Has a fixed length.
- Header with several fields to cary packet sequence, flags, commands, CRC, etc. Has a fixed length.
- CRC32 of header (for verification calculation this CRC32 field has to be zeroed).
- CRC32 of payload, when it exists (for verification calculation this CRC32 field has to be zeroed).
- Command code.
- Sequence number of the message.
- Sequence reply. Carries the sequence number of the message that the message is a reply to.
- Flags.
- Size of the payload.
- Payload for carrying data. Optional and its size is variable.
You can check the details on WireProtocol.h
Currently nanoFramework Wire Protocol supports only serial channels. The plan is to add support for USB (using CDC class device) and TCP. To ease the port to new HAL/platforms the code is architecture so that only minimal changes are required to add support for new implementations.
The code is architecture to receive and transmit data over a serial stream. Preferably (and to use the reference implementation provided without much changes) the interface/API of the serial stream should:
- Allow checking if there is data available for reading.
- Allow reading sequentially (FIFO fashion) the input stream for a definite number of bytes. Having a timeout for the read operation is ideal to prevent bad/incomplete read operations.
- Allow writing to the transmit stream a definite number of bytes. Ideally in a non-blocking fashion to prevent bad/incomplete write operations.
Follows a high-level description on how the Wire Protocol component works.
- RTOS thread -
ReceiverThread(...)
in WireProtocol_ReceiverThread.c - that loops continuously checking for available data in the receiving channel. - On available data the reception of the message is initialized (WP_Message_Initialize) and prepared (WP_Message_PrepareReception) so the reception can actually occur and be processed by calling WP_Message_Process.
- During the reception states the input stream is read (
WP_ReceiveBytes(...)
in WireProtocol_HAL_Interface.c) so the message header is received and it's integrity checked. Follows the reception and the integrity check of the payload, if there is any. - After a successful reception of the header (and payload, if any) the Process state machine in WireProtocol_Message.c) reaches the
ReceiveState_CompletePayload
state and calls theProcessPayload(...)
function. - Inside
ProcessPayload(...)
the lookup table for the commands that are implemented is searched and, if the command is found, the respective handler is called. According to the command its processing can require extra processing or gathering data. Invariably the handler execution end with a call toReplyToCommand(...)
where the reply is sent back to the host device. - When executing
ReplyToCommand(...)
the output stream is written (WP_TransmitMessage(...)
in WireProtocol_HAL_Interface.c) with the reply message.
Processing a command is carried in a handler function.
The collection of the commands that are implemented is listed in c_Lookup_Request
. This lookup structure is basically an array with the command code along with a pointer to the respective handler. It resides in WireProtocol_App_Interface.c.
The actual command implementation resides in WireProtocol_Commands.c.
There are two groups of commands: monitor commands and debug commands.
In order to add a new monitor command you have to:
- Add the function declaration and any required structure and/or type definition in WireProtocol_MonitorCommands.h
- Add a weak prototype in WireProtocol_MonitorCommands.c
- The actual code for the command handler function (and any required helper functions or extra processing) is added at target level. For the reference implementation for nanoBooter in ChibiOS check WireProtocol_MonitorCommands.c
To add the command to the collection of the supported monitor commands un-comment or add the respective line in the c_Lookup_Request
variable in WireProtocol_App_Interface.c for both nanoBooter and/or nanoCLR.
Because this declaration uses a macro to add the declaration of a command, make sure the existing naming pattern is strictly followed.
This architecture tries to bring flexibility by making it easy to have different monitor commands for nanoBooter and nanoCLR and also having them implemented in different ways, if necessary.
To ease code portability from .NET Micro Framework code base and maintain an understandable implementation the naming has been maintained or minimally adapted from the original C++ code. Try to follow this as much as possible when implementing new commands or porting the original C++ code to C.
Current Wire Protocol implementation has support for transmission over serial port (UART/USART) and serial over USB (USB CDC device class). Support for TCP channel is planned at a later stage.
When adding support for new channels the functions WP_ReceiveBytes(...)
and WP_TransmitMessage(...)
in WireProtocol_HAL_Interface.c are the ones that need to be reworked. This implementation is target and board specific so it resides in the board folder. Check the reference implementation for the ST_STM32F4_DISCOVERY board here.
On both, the relevant part is that they read/write to a serial stream a specified number of bytes. Preferably non blocking calls with a timeout. Please read the comments inside of each of those functions for the details.
The last piece that needs to be adjusted is the code inside the ReceiverThread(...)
which is the RTOS thread that is running the Wire Protocol component. That thread is basically a loop with a wait state were the checks for existing data to be read on the input stream. On data available the WP_Message_Process(...)
function is called.
The Wire Protocol requires the following functions in order to interface with the HAL. Weak implementations of each function are part of the core code.
WP_TransmitMessage(...)
in WireProtocol_HAL_Interface.cWP_ReceiveBytes(...)
in WireProtocol_HAL_Interface.cWP_CheckAvailableIncomingData(...)
in WireProtocol_HAL_Interface.c
An implementation for an STM32F4_DISCOVERY board with ChibiOS (including its HAL) is provided as a reference. Please check it at WireProtocol_HAL_Interface.c.
When porting nanoFramework to another RTOS or HAL follow the reference implementation to ease the port work.
The Wire Protocol requires the following functions in order to interface with it's client app. Weak implementations of each function are part of the core code.
WP_App_ProcessHeader(...)
in WireProtocol_App_Interface.cWP_App_ProcessPayload(...)
in WireProtocol_App_Interface.c
Actual implementations of these are to be provided by nanoBooter and nanoCLR. Please check the reference implementation for ChibiOS at WireProtocol_App_Interface.c.
To ease debugging of Wire Protocol sessions there are available a set of CMake options to adjust the output of the Wire Protocol state machine and TX/Rx operations. The available options are:
- NF_WP_TRACE_ERRORS: Enable error tracing.
- NF_WP_TRACE_HEADERS: Enable packet headers tracing.
- NF_WP_TRACE_STATE: Enable tracing of the current state of the Wire Protocol sate machine.
- NF_WP_TRACE_NODATA: Enable tracing of empty or incomplete packets.
- NF_WP_TRACE_ALL: Enable all the options above. In case this setting is chosen it takes precedence over all the other and replaces when on.
In order to ensure Wire Protocol communications integrity the message header and payload have each a CRC32 field that can be filled in with the CRC32 hash of the respective section. This allows the receiver to validate the integrity of both the header and the payload.
Considering that the most frequent transport layers are USB and Ethernet (under development) which already have their own validation and integrity mechanisms, it might seems a bit paranoid to have this extra validation on top of those. Moreover, in the unlikely event of a garbled packet reaching the end of the parser engine, the wrong data would (most likely) prevent the correct execution of such command.
In face of the above the CRC32 calculation and validation of the Wire Protocol header and payload has been made optional, meaning that a target can choose not to implement that. The Wire Protocol layer in the debugger is able to automatically handle both situations.
To have a target image built without implementing CRC32 validation the option NF_WP_IMPLEMENTS_CRC32=OFF
has to be passed to CMake.
In case a less reliable transport layer is used (COM port for example) or if the developer deems this necessary it can be enabled with the option above set to ON
.