Wi-SUN OAD

The application layer is responsible for plugging the OAD service. This section documents WiSUN stack specific implementation of OAD. This covers the high level protocol, the message structures, OAD image distributor and the target side software needed to handle OAD.

OAD Protocol

In the context of TI Wi-SUN stack, the distributor of OAD images is the Border Router in combination with the host. The OAD target is node flashed with the CoAP OAD application. The OAD module of this application handles the communication with the OAD image distributor, manages and stores the downloaded firmware images. The distributor initiates the OAD process. Then the target is responsible for requesting OAD image blocks, re-requesting when required and handle error conditions. Once OAD is complete, the OAD client will reset and start running with the new image.

../_images/oad-architecture.png

Figure 60. OAD distributor and target

OAD State Machines

../_images/ServerStateMachine.PNG

Figure 61. OAD Distributor State Machine

../_images/TargetStateMachine.PNG

Figure 62. OAD Target State Machine

OAD Messages

These messages are exchanged between the distributor and target over the air with CoAP protocol.

OAD Firmware Request

The distributor sends a firmware request message to the target’s unicast address with the oad/fwv URI. The response from the target is the current software version number. This message is independent of the OAD protocol and is used independently by the distributor. The content of the response is as follows.:

/* MCU Boot image version Structure */
typedef struct __attribute__((__packed__)) {
    uint8_t iv_major;
    uint8_t iv_minor;
    uint16_t iv_revision;
    uint32_t iv_build_num;
} mcuboot_image_version_t;

/* OAD Firmware request response content */
typedef struct __attribute__((__packed__)) {
    uint8_t img_id;
    uint8_t platform_type;
    mcuboot_image_version_t image_version;
} oad_fw_version_rsp_msg_t;
../_images/fw_request.png

Figure 63. OAD Firmware Request(Distributor) <-> Response(Target)

OAD Notification Request

To initiate the OAD process, the distributor sends the OAD notification request to the target. The request contains information about the new candidate image, such as firmware image type and size, and block size to be used. The target device responds with OAD notification response.

/* OAD Notification Request */
typedef struct  __attribute__((packed)){
uint8_t        img_id;
uint8_t        platform_type;
uint16_t       block_len;
uint32_t       image_len;
mcuboot_image_version_t image_version;
} oad_notif_req_msg_t;

OAD Notification Response

When the OAD target receives OAD notification request, it checks the content of the request and validates the image. It makes a decision whether to accept or reject the image and sends the response back to the distributor.

/* OAD Notification Response */
typedef struct __attribute__((__packed__)) {
uint8_t      img_id;
uint8_t      status;
} oad_notif_rsp_msg_t;
../_images/oad_notif_request.png

Figure 64. OAD Notification Request(Distributor) <-> Response(Target)

OAD Image Block Request

Once the target accepts the image, it will initiate the download process. The target sends image block requests to the distributor’s unicast address with the oad/img URI. The request sepcifies the block number of the image needed. These requests are done in a sequential order. Thus the target keeps track of the block number and requestest the next block, only when the current block is successfully downloaded. The target immediately requests the next block once the current block is downloaded. The OAD server does not keep track of the block number. If there is corrupted block or lost block, the target requests for the block again automatically.

/* OAD Block Request */
typedef struct  __attribute__((packed)){
    uint8_t  imgId;
    uint16_t blkNum;
    uint16_t totalBlks;
} oad_imgBlockReq_t;

OAD Image Block Response

The distributor responds to valid block requests with a block of the OAD image with the block number specified by the request.

/* OAD Block Response */
typedef struct  __attribute__((packed)){
uint8_t  imgId;
uint16_t blkNum;
uint8_t  data[OAD_BLOCK_SIZE];
} oad_imgBlockRsp_t;
../_images/oad_image_download.png

Figure 65. OAD Block Request(Target) <-> OAD Block Repsonse (Distributor)

OAD Complete Request

When the target determines that it has received the last OAD image block, it sends an OAD complete request to the distributor. This is a block request as defined above with 0xFFFF(OAD_COMPLETE Flag) as the block id number. This tells the distributor to mark the OAD session as complete. The total blocks field in the block request structure is ignored for the OAD complete message.

OAD Complete Response

The distributor responds with an Ack called OAD complete response to the OAD complete request. This triggers a reset on the target and allows the target to boot to the newly downloaded image. Thereby, completing the OAD process.

../_images/oad_complete.png

Figure 66. OAD Completion Interaction

OAD Abort

The target can intiate an abort of the OAD process due to timing out of requests. The is done by sending a request to the unicast address of the distributor with the oad/abort URI.

OAD Target Module

The software on the target that enables OAD functionality is called the OAD module. This module implements the behavior described by the OAD Target State Machine.

The OAD module on that target has an event driven tasklet and callbacks that handle the requests to the OAD URIs.

OAD Tasklet

The OAD tasklet handles block transfer and OAD complete messages. It constructs and sends to OAD block request (OAD_BLOCK_REQ_EVT) and handles the OAD block response (OAD_BLOCK_RECV_EVT), issuing another OAD block request afterwards if needed. On last block received, it sends and handles the response for the OAD complete request (OAD_COMPLETE_EVT). It also handles the abort request from the target(OAD_ABORT_EVT) and retries during block transfers due to timeout(OAD_BLOCK_REQ_TIMEOUT_EVT).

/**
* @brief OAD tasklet event enums
*/
typedef enum oad_evt {
    OAD_INIT_EVT              = 0,
    OAD_BLOCK_REQ_EVT         = 1,
    OAD_BLOCK_RECV_EVT        = 2,
    OAD_COMPLETE_EVT          = 3,
    OAD_ABORT_EVT             = 4,
    OAD_BLOCK_REQ_TIMEOUT_EVT = 5,
    OAD_COMPLETE_TIMEOUT_EVT  = 6,
} oad_evt_t;

/**
* @brief   Initializes the CoAP OAD tasket which handles CoAP block transfer
*
* @param   None.
*
* @return  None.
*/
void oad_tasklet_start(void);

/**
* @brief   Tasklet for handling OAD block request/response and OAD complete request/response.
*          Handles OAD_BLOCK_REQ_EVT, OAD_BLOCK_RECV_EVT, OAD_COMPLETE_EVT, OAD_ABORT_EVT,
*          OAD_BLOCK_REQ_TIMEOUT_EVT, and OAD_COMPLETE_TIMEOUT_EVT
*
* @param   event - Event object containing event type to trigger
*
* @return  None
*/
static void oad_tasklet(arm_event_s *event)

OAD CoAP Callbacks

There are multiple callbacks executing on the target reacting to requests and responses from the distributor. These process the incoming data and trigger events so that the tasklet can get unblocked.

/**
* @brief   CoAP callback for the CoAP OAD service. Handles CoAP requests to the
*          oad/fvw and oad/ntf URIs.
*
* @param   service_id     - Service ID for the CoAP server
* @param   source_address - Source address of the CoAP request message
* @param   source_port    - Source port of the CoAP request message
* @param   request_ptr    - Pointer to the contents of the CoAP request message
*
* @return  0 on successfully processing CoAP request. -1 otherwise.
*/
int coap_oad_cb(int8_t service_id, uint8_t source_address[static 16],
                    uint16_t source_port, sn_coap_hdr_s *request_ptr);

/**
* @brief   Callback for OAD image block response. Process the payload of the block
*          response and trigger the OAD_BLOCK_RECV_EVT callback.
*
* @param   service_id     - Service ID for the CoAP block response
* @param   source_address - Source address of the CoAP block response
* @param   source_port    - Source port of the CoAP block response
* @param   response_ptr    - Pointer to the contents of the CoAP block response
*
* @return  0 on successfully processing CoAP response. -1 otherwise.
*/
static int oad_img_rsp_cb(int8_t service_id, uint8_t source_address[static 16],
                            uint16_t source_port, sn_coap_hdr_s *response_ptr)

/**
* @brief   Callback for OAD complete response. This triggers boot into the newly downloaded image.
*
* @param   service_id     - Service ID for the CoAP complete response
* @param   source_address - Source address of the CoAP complete response
* @param   source_port    - Source port of the CoAP complete response
* @param   request_ptr    - Pointer to the contents of the CoAP complete response
*
* @return  0 on successfully processing CoAP response. -1 otherwise. Should reset before ever returning.
*/

static int oad_complete_rsp_cb(int8_t service_id, uint8_t source_address[static 16],
                            uint16_t source_port, sn_coap_hdr_s *response_ptr)

OAD Storage

The OAD module also handles the storage and provides device independent APIs to store the downloaded firmware image to flash. These APIs are the same for both on-chip and off-chip solutions.

/*********************************************************************
* @fn      OADStorage_init
*
* @brief   Initialize OAD flash storage interface.
*
* @param   None.
*
* @return  None.
*/
extern void OADStorage_init(void);

/*********************************************************************
* @fn      OADStorage_imgBlockWrite
*
* @brief   Write Image Block.
*
* @param   blockNum   - block number to be written
* @param   blockLen   - length of block (full block size)
* @param   pBlockData - pointer to data to be written
* @param   dataLen    - length of data to be written (may be <= blockLen)
*
*
* @return  status
*/
extern OADStorage_Status_t OADStorage_imgBlockWrite(uint32_t blockNum, uint16_t blockLen, uint8_t *pBlockData, uint8_t dataLen);

/*********************************************************************
* @fn      OADStorage_eraseImg
*
* @brief   Erases the required number of flash pages in preparation
*          for a new image.
*
* @param   image_len Length of flash to be erased to prepare for the
*                    new image.
*
* @return  OADStorage_Status_t
*/
extern OADStorage_Status_t OADStorage_eraseImg(uint32_t imageLen);

/*********************************************************************
* @fn      OADStorage_close
*
* @brief   Releases the OAD flash storage interface.
*
* @param  none
*
* @return none
*/
extern void OADStorage_close(void);

OAD Distributor

The OAD distributor is a system with an application processor(PC/AM64x) connected to a Wi-SUN Border Router as a Network Co-Processor.

TI Wi-SUN FAN pySpinel GitHub repo contains a python based CLI which now has the additional APIs that enable you to get firmware version from the target and initiate the OAD process. It also has additional logic to allow the host behave as a OAD Server that is described in OAD Distributor State Machine. This pyspinel application runs on both a windows or a linux host.

The APIs are mentioned here, but the usage examples can be accessed with the help command in the spinel-cli.

Function

PySpinel API

OAD Firmware Request

getoadfwver

OAD Notification Request

startoad

In addition to the APIs, the Spinel CLI also has the following functionalities implemented.

Token based CoAP Response Tracking

CoAP protocol provides the option of including an up to 8 byte token in each CoAP message. This token is echoed back in the response to the CoAP message to properly track which request maps to which response. See the CoAP RFC for details.

PySpinel CoAP implementation includes token support. This support will be utilized in firmware, notification, and block request/response messages to distinguish between responses.

In the case of received CoAP responses (triggered by CoAP requests sent by PySpinel):

  • Saved tokens are compared to the response token. In PySpinel, 3 tokens are saved: firmware version request, notification request, LED request (updated from regular coap_node implementation).

  • If there is a match, the response has been identified as a response to a specific request sent by PySpinel. Perform the appropriate action (usually different payload parsing and log output).

In the case of received CoAP requests (currently only OAD block requests):

  • Check the URI of the request and compare to supported URIs (oad/img for OAD block requests)

  • If there is a match, take the appropriate action. Send the appropriate CoAP response, echoing the token in the CoAP request.

Block Request handling

Handle the block request as follows:

  • Navigate to the appropriate byte of the OAD upgrade image as indicated by the requested block number and block size.

  • Assemble the OAD response payload by combining OAD image ID, number of bytes in block, and block contents.

  • Send the CoAP block response, echoing the token of the request as described above.

Response handling

Notification and Firmware Responses are processed by printing out the data of interest on the command line Notification response would have the decision of the target whether the distributor’s OAD request was accepted or rejected. Firmware Response would have the image ID, platform, OAD firmaware version number.