You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
665 lines
23 KiB
665 lines
23 KiB
//
|
|
// fwu.c
|
|
// nrf52-dfu
|
|
//
|
|
// C library for the Nordic firmware update protocol.
|
|
//
|
|
// Created by Andreas Schweizer on 30.11.2018.
|
|
// Copyright © 2018-2019 Classy Code GmbH
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
//
|
|
|
|
#include "fwu.h"
|
|
#include <stdbool.h>
|
|
|
|
// TODO too big, split in separate files!
|
|
|
|
typedef enum {
|
|
FWU_PS_IDLE = 0,
|
|
FWU_PS_PING = 10,
|
|
FWU_PS_RCPT_NOTIF = 20,
|
|
FWU_PS_MTU = 30,
|
|
FWU_PS_OBJ1_SELECT = 40,
|
|
FWU_PS_OBJ1_CREATE = 50,
|
|
FWU_PS_OBJ1_WRITE = 60,
|
|
FWU_PS_OBJ1_CRC_GET = 70,
|
|
FWU_PS_OBJ1_EXECUTE = 80,
|
|
FWU_PS_OBJ2_SELECT = 90,
|
|
FWU_PS_OBJ2_WAIT_FOR_CHUNK = 91,
|
|
FWU_PS_OBJ2_CREATE = 100,
|
|
FWU_PS_OBJ2_WRITE = 110,
|
|
FWU_PS_OBJ2_CRC_GET = 120,
|
|
FWU_PS_OBJ2_EXECUTE = 130,
|
|
FWU_PS_FAIL = 254,
|
|
FWU_PS_DONE = 255,
|
|
} EFwuProcessState;
|
|
|
|
// Process requests, triggering process state transitions.
|
|
typedef enum {
|
|
FWU_PR_NONE = 0,
|
|
FWU_PR_START = 1,
|
|
FWU_PR_RECEIVED_RESPONSE,
|
|
FWU_PR_REQUEST_FAILED,
|
|
FWU_PR_REQUEST_SENT,
|
|
} EFwuProcessRequest;
|
|
|
|
typedef enum {
|
|
FWU_CS_IDLE = 0,
|
|
FWU_CS_SEND = 1, // sending data from the private request buffer
|
|
FWU_CS_RECEIVE = 2, // receiving data into the private response buffer
|
|
FWU_CS_FAIL = 3,
|
|
FWU_CS_DONE = 4,
|
|
} EFwuCommandState;
|
|
|
|
// Command requests, triggering command state transitions.
|
|
typedef enum {
|
|
FWU_CR_NONE = 0,
|
|
FWU_CR_SEND = 1,
|
|
FWU_CR_SENDONLY = 2,
|
|
FWU_CR_EOM_RECEIVED = 3,
|
|
FWU_CR_RX_OVERFLOW = 4,
|
|
FWU_CR_INVALID_ESCAPE_SEQ,
|
|
} EFwuCommandRequest;
|
|
|
|
#define FWU_EOM 0xC0
|
|
#define FWU_RESPONSE_START 0x60
|
|
#define FWU_RESPONSE_SUCCESS 0x01
|
|
|
|
// PING 09 01 C0 -> 60 09 01 01 C0
|
|
static uint8_t sPingRequest[] = {0x09, 0x01};
|
|
static uint8_t sPingRequestLen = 2;
|
|
|
|
// SET RECEIPT 02 00 00 C0 -> 60 02 01 C0
|
|
static uint8_t sSetReceiptRequest[] = {0x02, 0x00, 0x00};
|
|
static uint8_t sSetReceiptRequestLen = 3;
|
|
|
|
// Get the preferred MTU size on the request.
|
|
// GET MTU 07 -> 60 07 01 83 00 C0
|
|
static uint8_t sGetMtuRequest[] = {0x07};
|
|
static uint8_t sGetMtuRequestLen = 1;
|
|
|
|
// Triggers the last transferred object of the specified type to be selected
|
|
// and queries information (max size, cur offset, cur CRC) about the object.
|
|
// If there's no object of the specified type, the object type is still
|
|
// selected, CRC and offset are 0 in this case.
|
|
// SELECT OBJECT 06 01 C0 -> 60 06 01 00 01 00 00 00 00 00 00 00 00 00 00 C0
|
|
static uint8_t sSelectObjectRequest[] = {0x06, 0x01};
|
|
static uint8_t sSelectObjectRequestLen = 2;
|
|
|
|
// Creating a command or data object; the target reserves the space, resets the
|
|
// progress since the last Execute command and selects the new object.)
|
|
// CREATE OBJECT 01 01 87 00 00 00 C0 -> 60 01 01 C0
|
|
static uint8_t sCreateObjectRequest[] = {0x01, 0x01, 0x87, 0x00, 0x00, 0x00};
|
|
static uint8_t sCreateObjectRequestLen = 6;
|
|
|
|
// CRC GET 03 C0 -> 60 03 01 87 00 00 00 38 f4 97 72 C0
|
|
static uint8_t sGetCrcRequest[] = {0x03};
|
|
static uint8_t sGetCrcRequestLen = 1;
|
|
|
|
// Execute an object after it has been fully transmitted.
|
|
// EXECUTE OBJECT 04 C0 -> 60 04 01 C0
|
|
static uint8_t sExecuteObjectRequest[] = {0x04};
|
|
static uint8_t sExecuteObjectRequestLen = 1;
|
|
|
|
static void fwuYieldProcessFsm(TFwu *fwu, uint32_t elapsedMillisec);
|
|
static void fwuYieldCommandFsm(TFwu *fwu, uint32_t elapsedMillisec);
|
|
|
|
static EFwuResponseStatus fwuTestReceivedPacketValid(TFwu *fwu);
|
|
|
|
// Don't send more than FWU_REQUEST_BUF_SIZE bytes.
|
|
// Don't include the EOM.
|
|
static void fwuPrepareSendBuffer(TFwu *fwu, uint8_t *data, uint8_t len);
|
|
|
|
static void fwuPrepareLargeObjectSendBuffer(TFwu *fwu, uint8_t requestCode);
|
|
|
|
// static void fwuDebugPrintStatus(TFwu *fwu, char *msg);
|
|
|
|
static void updateCrc(TFwu *fwu, uint8_t b);
|
|
static void fwuSignalFailure(TFwu *fwu, EFwuResponseStatus reason);
|
|
static inline uint16_t fwuLittleEndianToHost16(uint8_t *bytes);
|
|
static inline uint32_t fwuLittleEndianToHost32(uint8_t *bytes);
|
|
static inline void fwuHostToLittleEndian32(uint32_t v, uint8_t *bytes);
|
|
|
|
// First function to call to set up the internal state in the FWU structure.
|
|
void fwuInit(TFwu *fwu) {
|
|
fwu->privateProcessState = FWU_PS_IDLE;
|
|
fwu->privateProcessRequest = FWU_PR_NONE;
|
|
fwu->privateCommandState = FWU_CS_IDLE;
|
|
|
|
fwu->processStatus = FWU_STATUS_UNDEFINED;
|
|
fwu->responseStatus = FWU_RSP_OK;
|
|
}
|
|
|
|
// Execute the firmware update.
|
|
void fwuExec(TFwu *fwu) {
|
|
// Start with sending a PING command to the target to see if it's there...
|
|
fwu->privateProcessRequest = FWU_PR_START;
|
|
}
|
|
|
|
// Call regularly to allow asynchronous processing to continue.
|
|
EFwuProcessStatus fwuYield(TFwu *fwu, uint32_t elapsedMillisec) {
|
|
// Nothing to do if processing has failed or successfully completed...
|
|
if (fwu->processStatus == FWU_STATUS_FAILURE ||
|
|
fwu->privateProcessState == FWU_PS_FAIL) {
|
|
return FWU_STATUS_FAILURE;
|
|
} else if (fwu->processStatus == FWU_STATUS_COMPLETION ||
|
|
fwu->privateProcessState == FWU_PS_DONE) {
|
|
return FWU_STATUS_COMPLETION;
|
|
}
|
|
|
|
// Processing is ongoing, yield to FSMs.
|
|
fwuYieldCommandFsm(fwu, elapsedMillisec);
|
|
fwuYieldProcessFsm(fwu, elapsedMillisec);
|
|
|
|
return fwu->processStatus;
|
|
}
|
|
|
|
// Call after data from the target has been received.
|
|
void fwuDidReceiveData(TFwu *fwu, uint8_t *bytes, uint8_t len) {
|
|
while (len > 0) {
|
|
if (fwu->privateResponseLen == FWU_RESPONSE_BUF_SIZE) {
|
|
fwu->privateCommandRequest = FWU_CR_RX_OVERFLOW;
|
|
return;
|
|
}
|
|
uint8_t c = *bytes++;
|
|
if (c == FWU_EOM) {
|
|
fwu->privateCommandRequest = FWU_CR_EOM_RECEIVED;
|
|
}
|
|
|
|
if (c == 0xDB) {
|
|
fwu->privateResponseEscapeCharacter = 1;
|
|
} else {
|
|
if (fwu->privateResponseEscapeCharacter) {
|
|
fwu->privateResponseEscapeCharacter = 0;
|
|
if (c == 0xDC) {
|
|
c = 0xC0;
|
|
} else if (c == 0xDD) {
|
|
c = 0xDB;
|
|
} else {
|
|
fwu->privateCommandRequest = FWU_CR_INVALID_ESCAPE_SEQ;
|
|
return;
|
|
}
|
|
}
|
|
fwu->privateResponseBuf[fwu->privateResponseLen++] = c;
|
|
}
|
|
len--;
|
|
}
|
|
}
|
|
|
|
// Inform the FWU module that it may send maxLen bytes of data to the target.
|
|
void fwuCanSendData(TFwu *fwu, uint8_t maxLen) {
|
|
fwu->privateSendBufSpace = maxLen;
|
|
}
|
|
|
|
void fwuSendChunk(TFwu *fwu, uint8_t *buf, uint32_t len) {
|
|
if (fwu->privateProcessState == FWU_PS_OBJ2_WAIT_FOR_CHUNK &&
|
|
fwu->privateDataObjectSize == len) {
|
|
fwu->dataObject = buf;
|
|
fwu->privateProcessState = FWU_PS_OBJ2_CREATE;
|
|
}
|
|
}
|
|
|
|
bool fwuIsReadyForChunk(TFwu *fwu) {
|
|
return fwu->privateProcessState == FWU_PS_OBJ2_WAIT_FOR_CHUNK;
|
|
}
|
|
|
|
static void fwuYieldProcessFsm(TFwu *fwu, uint32_t elapsedMillisec) {
|
|
uint8_t tmpPrivateProcessRequest = fwu->privateProcessRequest;
|
|
fwu->privateProcessRequest = FWU_PR_NONE;
|
|
|
|
// No processing in final states
|
|
if (fwu->privateProcessState == FWU_PS_DONE ||
|
|
fwu->privateProcessState == FWU_PS_FAIL) {
|
|
return;
|
|
}
|
|
|
|
// Failure handling
|
|
if (tmpPrivateProcessRequest == FWU_PR_REQUEST_FAILED) {
|
|
fwu->privateProcessState = FWU_PS_FAIL;
|
|
fwu->processStatus = FWU_STATUS_FAILURE;
|
|
return;
|
|
}
|
|
|
|
// Executing the firmware update process.
|
|
switch (fwu->privateProcessState) {
|
|
case FWU_PS_IDLE:
|
|
if (tmpPrivateProcessRequest == FWU_PR_START) {
|
|
// Send a PING and switch to the PING state to wait for the response.
|
|
fwuPrepareSendBuffer(fwu, sPingRequest, sPingRequestLen);
|
|
fwu->privateProcessState = FWU_PS_PING;
|
|
}
|
|
break;
|
|
|
|
// PING: Check if the nRF52 DFU code is listening
|
|
case FWU_PS_PING:
|
|
// Wait for the PING response, then verify it.
|
|
if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
|
|
// ID match?
|
|
if (fwu->privateRequestBuf[1] == fwu->privateResponseBuf[3]) {
|
|
// Send a SET_RECEIPT and switch to the corresponding state to wait
|
|
// for the response.
|
|
fwuPrepareSendBuffer(fwu, sSetReceiptRequest, sSetReceiptRequestLen);
|
|
fwu->privateProcessState = FWU_PS_RCPT_NOTIF;
|
|
} else {
|
|
fwuSignalFailure(fwu, FWU_RSP_PING_ID_MISMATCH);
|
|
}
|
|
}
|
|
break;
|
|
|
|
// RCPT_NOTIF: Define Receipt settings
|
|
case FWU_PS_RCPT_NOTIF:
|
|
// Wait for the SET_RECEIPT response.
|
|
if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
|
|
// Send a SET_RECEIPT and switch to the corresponding state to wait for
|
|
// the response.
|
|
fwuPrepareSendBuffer(fwu, sGetMtuRequest, sGetMtuRequestLen);
|
|
fwu->privateProcessState = FWU_PS_MTU;
|
|
}
|
|
break;
|
|
|
|
// FWU_PS_MTU: Get maximum transmission unit size
|
|
case FWU_PS_MTU:
|
|
if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
|
|
fwu->privateMtuSize =
|
|
fwuLittleEndianToHost16(&fwu->privateResponseBuf[3]);
|
|
// Send a SET_RECEIPT and switch to the corresponding state to wait for
|
|
// the response.
|
|
sSelectObjectRequest[1] = 0x01; // select object 1 (command object)
|
|
fwuPrepareSendBuffer(fwu, sSelectObjectRequest,
|
|
sSelectObjectRequestLen);
|
|
fwu->privateProcessState = FWU_PS_OBJ1_SELECT;
|
|
}
|
|
break;
|
|
|
|
// FWU_PS_OBJ1_SELECT: Select the INIT command object
|
|
case FWU_PS_OBJ1_SELECT:
|
|
if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
|
|
uint32_t maxSize = fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
|
|
if (maxSize < fwu->commandObjectLen) {
|
|
fwuSignalFailure(fwu, FWU_RSP_INIT_COMMAND_TOO_LARGE);
|
|
} else {
|
|
sCreateObjectRequest[1] = 0x01; // create type 1 object (COMMAND)
|
|
fwuHostToLittleEndian32(fwu->commandObjectLen,
|
|
&sCreateObjectRequest[2]);
|
|
fwuPrepareSendBuffer(fwu, sCreateObjectRequest,
|
|
sCreateObjectRequestLen);
|
|
fwu->privateProcessState = FWU_PS_OBJ1_CREATE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
// FWU_PS_OBJ1_CREATE: Create the INIT command object
|
|
case FWU_PS_OBJ1_CREATE:
|
|
if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
|
|
fwu->privateProcessState = FWU_PS_OBJ1_WRITE;
|
|
fwu->privateObjectBuf = fwu->commandObject;
|
|
fwu->privateObjectLen = fwu->commandObjectLen;
|
|
fwu->privateObjectIx = 0;
|
|
fwu->privateObjectCrc = 0xffffffff;
|
|
fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
|
|
}
|
|
break;
|
|
|
|
// FWU_PS_OBJ1_WRITE: Write the INIT command object
|
|
case FWU_PS_OBJ1_WRITE:
|
|
if (tmpPrivateProcessRequest == FWU_PR_REQUEST_SENT) {
|
|
// more to send?
|
|
if (fwu->privateObjectIx == fwu->privateObjectLen) {
|
|
// no - request the CRC of the written data...
|
|
fwuPrepareSendBuffer(fwu, sGetCrcRequest, sGetCrcRequestLen);
|
|
fwu->privateProcessState = FWU_PS_OBJ1_CRC_GET;
|
|
} else {
|
|
fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
|
|
}
|
|
}
|
|
break;
|
|
|
|
// FWU_PS_OBJ1_CRC_GET: Checksum verification
|
|
case FWU_PS_OBJ1_CRC_GET:
|
|
if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
|
|
// uint32_t actualLen =
|
|
// fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
|
|
uint32_t actualCks =
|
|
fwuLittleEndianToHost32(&fwu->privateResponseBuf[7]);
|
|
if (actualCks == ~fwu->privateObjectCrc) {
|
|
// Checksum is OK; execute the command!
|
|
fwuPrepareSendBuffer(fwu, sExecuteObjectRequest,
|
|
sExecuteObjectRequestLen);
|
|
fwu->privateProcessState = FWU_PS_OBJ1_EXECUTE;
|
|
} else {
|
|
fwuSignalFailure(fwu, FWU_RSP_CHECKSUM_ERROR);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case FWU_PS_OBJ1_EXECUTE:
|
|
if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
|
|
sSelectObjectRequest[1] = 0x02; // select object 2 (DATA object)
|
|
fwu->privateDataObjectOffset = 0; // from the beginning
|
|
fwuPrepareSendBuffer(fwu, sSelectObjectRequest,
|
|
sSelectObjectRequestLen);
|
|
fwu->privateProcessState = FWU_PS_OBJ2_SELECT;
|
|
}
|
|
break;
|
|
|
|
// FWU_PS_OBJ2_SELECT: Select the DATA object
|
|
case FWU_PS_OBJ2_SELECT:
|
|
if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
|
|
fwu->privateDataObjectMaxSize =
|
|
fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
|
|
fwu->privateObjectCrc =
|
|
0xffffffff; // do it here because it's global for the entire blob
|
|
// We'll create and execute multiple data objects, so it's ok if the
|
|
// actual size is greater than max size.
|
|
fwu->privateDataObjectSize =
|
|
(fwu->dataObjectLen -
|
|
fwu->privateDataObjectOffset); // nof bytes remaining
|
|
if (fwu->privateDataObjectSize > fwu->privateDataObjectMaxSize) {
|
|
fwu->privateDataObjectSize = fwu->privateDataObjectMaxSize;
|
|
}
|
|
sCreateObjectRequest[1] = 0x02; // create type 2 object (COMMAND)
|
|
fwuHostToLittleEndian32(fwu->privateDataObjectSize,
|
|
&sCreateObjectRequest[2]);
|
|
fwuPrepareSendBuffer(fwu, sCreateObjectRequest,
|
|
sCreateObjectRequestLen);
|
|
fwu->privateProcessState = FWU_PS_OBJ2_WAIT_FOR_CHUNK;
|
|
}
|
|
break;
|
|
case FWU_PS_OBJ2_WAIT_FOR_CHUNK:
|
|
break;
|
|
// FWU_PS_OBJ2_CREATE: Create the DATA object
|
|
case FWU_PS_OBJ2_CREATE:
|
|
if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
|
|
fwu->privateProcessState = FWU_PS_OBJ2_WRITE;
|
|
fwu->privateObjectBuf = fwu->dataObject;
|
|
fwu->privateObjectLen = fwu->privateDataObjectSize;
|
|
fwu->privateObjectIx = 0;
|
|
fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
|
|
}
|
|
break;
|
|
|
|
// FWU_PS_OBJ2_WRITE: Write the DATA object
|
|
case FWU_PS_OBJ2_WRITE:
|
|
if (tmpPrivateProcessRequest == FWU_PR_REQUEST_SENT) {
|
|
// more to send?
|
|
if (fwu->privateObjectIx == fwu->privateObjectLen) {
|
|
// no - request the CRC of the written data...
|
|
fwuPrepareSendBuffer(fwu, sGetCrcRequest, sGetCrcRequestLen);
|
|
fwu->privateProcessState = FWU_PS_OBJ2_CRC_GET;
|
|
} else {
|
|
fwuPrepareLargeObjectSendBuffer(fwu, 0x08);
|
|
}
|
|
}
|
|
break;
|
|
|
|
// FWU_PS_OBJ2_CRC_GET: Checksum verification
|
|
case FWU_PS_OBJ2_CRC_GET:
|
|
if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
|
|
// uint32_t actualLen =
|
|
// fwuLittleEndianToHost32(&fwu->privateResponseBuf[3]);
|
|
uint32_t actualCks =
|
|
fwuLittleEndianToHost32(&fwu->privateResponseBuf[7]);
|
|
if (actualCks == ~fwu->privateObjectCrc) {
|
|
// Checksum is OK; execute the command!
|
|
fwuPrepareSendBuffer(fwu, sExecuteObjectRequest,
|
|
sExecuteObjectRequestLen);
|
|
fwu->privateProcessState = FWU_PS_OBJ2_EXECUTE;
|
|
|
|
} else {
|
|
fwuSignalFailure(fwu, FWU_RSP_CHECKSUM_ERROR);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case FWU_PS_OBJ2_EXECUTE:
|
|
if (tmpPrivateProcessRequest == FWU_PR_RECEIVED_RESPONSE) {
|
|
fwu->privateDataObjectOffset += fwu->privateDataObjectSize;
|
|
if (fwu->privateDataObjectOffset == fwu->dataObjectLen) {
|
|
fwu->privateProcessState = FWU_PS_DONE;
|
|
fwu->processStatus = FWU_STATUS_COMPLETION;
|
|
|
|
} else {
|
|
// We'll create and execute multiple data objects, so it's ok if the
|
|
// actual size is greater than max size.
|
|
fwu->privateDataObjectSize =
|
|
(fwu->dataObjectLen -
|
|
fwu->privateDataObjectOffset); // nof bytes remaining
|
|
if (fwu->privateDataObjectSize > fwu->privateDataObjectMaxSize) {
|
|
fwu->privateDataObjectSize = fwu->privateDataObjectMaxSize;
|
|
}
|
|
sCreateObjectRequest[1] = 0x02; // create type 2 object (COMMAND)
|
|
fwuHostToLittleEndian32(fwu->privateDataObjectSize,
|
|
&sCreateObjectRequest[2]);
|
|
fwuPrepareSendBuffer(fwu, sCreateObjectRequest,
|
|
sCreateObjectRequestLen);
|
|
fwu->privateProcessState = FWU_PS_OBJ2_WAIT_FOR_CHUNK;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fwu->privateProcessState = FWU_PS_FAIL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void fwuYieldCommandFsm(TFwu *fwu, uint32_t elapsedMillisec) {
|
|
uint8_t toSend;
|
|
|
|
// Automatically return from final states to IDLE.
|
|
if (fwu->privateCommandState == FWU_CS_DONE ||
|
|
fwu->privateCommandState == FWU_CS_FAIL) {
|
|
fwu->privateCommandState = FWU_CS_IDLE;
|
|
}
|
|
|
|
// Timeout?
|
|
if (fwu->privateCommandState != FWU_CS_IDLE) {
|
|
if (fwu->privateCommandTimeoutRemainingMillisec < elapsedMillisec) {
|
|
fwu->privateCommandTimeoutRemainingMillisec = 0;
|
|
} else {
|
|
fwu->privateCommandTimeoutRemainingMillisec -= elapsedMillisec;
|
|
}
|
|
if (fwu->privateCommandTimeoutRemainingMillisec == 0) {
|
|
fwuSignalFailure(fwu, FWU_RSP_TIMEOUT);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Catch errors
|
|
if (fwu->privateCommandRequest == FWU_CR_RX_OVERFLOW) {
|
|
fwuSignalFailure(fwu, FWU_RSP_RX_OVERFLOW);
|
|
return;
|
|
}
|
|
if (fwu->privateCommandRequest == FWU_CR_INVALID_ESCAPE_SEQ) {
|
|
fwuSignalFailure(fwu, FWU_RSP_RX_INVALID_ESCAPE_SEQ);
|
|
return;
|
|
}
|
|
|
|
switch (fwu->privateCommandState) {
|
|
case FWU_CS_IDLE:
|
|
// Ready and waiting for a transmission request.
|
|
if (fwu->privateCommandRequest == FWU_CR_SEND ||
|
|
fwu->privateCommandRequest == FWU_CR_SENDONLY) {
|
|
fwu->privateCommandSendOnly =
|
|
fwu->privateCommandRequest == FWU_CR_SENDONLY ? 1 : 0;
|
|
fwu->privateCommandRequest = FWU_CR_NONE;
|
|
fwu->privateCommandState = FWU_CS_SEND;
|
|
fwu->privateCommandTimeoutRemainingMillisec =
|
|
fwu->responseTimeoutMillisec;
|
|
}
|
|
break;
|
|
case FWU_CS_SEND:
|
|
// Continue sending data until the entire request has been sent.
|
|
toSend = fwu->privateRequestLen - fwu->privateRequestIx;
|
|
if (toSend == 0) {
|
|
if (fwu->privateCommandSendOnly) {
|
|
// This was a fire-and-forget request; we don't expect a response.
|
|
fwu->privateProcessRequest = FWU_PR_REQUEST_SENT;
|
|
fwu->privateCommandState = FWU_CS_DONE;
|
|
} else {
|
|
// The request has been sent; wait for response.
|
|
fwu->privateCommandState = FWU_CS_RECEIVE;
|
|
}
|
|
} else if (fwu->privateSendBufSpace > 0) {
|
|
uint8_t n = fwu->privateSendBufSpace;
|
|
if (n > toSend) {
|
|
n = toSend;
|
|
}
|
|
fwu->txFunction(fwu, &fwu->privateRequestBuf[fwu->privateRequestIx], n);
|
|
fwu->privateRequestIx += n;
|
|
}
|
|
break;
|
|
case FWU_CS_RECEIVE:
|
|
// Continue receiving data until the end-of-message marker has been
|
|
// received.
|
|
if (fwu->privateCommandRequest == FWU_CR_EOM_RECEIVED) {
|
|
fwu->privateCommandRequest = FWU_CR_NONE;
|
|
EFwuResponseStatus responseStatus = fwuTestReceivedPacketValid(fwu);
|
|
if (responseStatus == FWU_RSP_OK) {
|
|
// Inform the process state machine that command reception has
|
|
// completed.
|
|
fwu->privateProcessRequest = FWU_PR_RECEIVED_RESPONSE;
|
|
fwu->privateCommandState = FWU_CS_DONE;
|
|
} else {
|
|
fwu->responseStatus = responseStatus;
|
|
fwu->privateCommandState = FWU_CS_FAIL;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
fwu->privateCommandState = FWU_CS_FAIL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static EFwuResponseStatus fwuTestReceivedPacketValid(TFwu *fwu) {
|
|
// 60 <cmd> <ok> C0
|
|
if (fwu->privateResponseLen < 4) {
|
|
return FWU_RSP_TOO_SHORT;
|
|
}
|
|
if (fwu->privateResponseBuf[0] != FWU_RESPONSE_START) {
|
|
return FWU_RSP_START_MARKER_MISSING;
|
|
}
|
|
if (fwu->privateResponseBuf[1] != fwu->privateRequestBuf[0]) {
|
|
return FWU_RSP_REQUEST_REFERENCE_INVALID;
|
|
}
|
|
if (fwu->privateResponseBuf[2] != FWU_RESPONSE_SUCCESS) {
|
|
return FWU_RSP_ERROR_RESPONSE;
|
|
}
|
|
if (fwu->privateResponseBuf[fwu->privateResponseLen - 1] != FWU_EOM) {
|
|
return FWU_RSP_END_MARKER_MISSING;
|
|
}
|
|
return FWU_RSP_OK;
|
|
}
|
|
|
|
static void fwuPrepareLargeObjectSendBuffer(TFwu *fwu, uint8_t requestCode) {
|
|
uint16_t bytesTodo = fwu->privateObjectLen - fwu->privateObjectIx;
|
|
uint16_t bufSpace = FWU_REQUEST_BUF_SIZE - 2;
|
|
|
|
uint16_t i;
|
|
uint8_t *p = &fwu->privateRequestBuf[0];
|
|
|
|
*p++ = requestCode;
|
|
fwu->privateRequestLen = 2; // including requestCode and FWU_EOM
|
|
fwu->privateRequestIx = 0;
|
|
|
|
if (bytesTodo > 32) {
|
|
bytesTodo = 32;
|
|
}
|
|
|
|
for (i = 0; i < bytesTodo && bufSpace >= 2; i++) {
|
|
uint8_t b = fwu->privateObjectBuf[fwu->privateObjectIx];
|
|
// SLIP escape characters: C0->DBDC, DB->DBDD
|
|
if (b == 0xC0 || b == 0xDB) {
|
|
*p++ = 0xDB;
|
|
*p++ = (b == 0xC0) ? 0xDC : 0xDD;
|
|
fwu->privateRequestLen += 2;
|
|
bufSpace -= 2;
|
|
} else {
|
|
*p++ = b;
|
|
fwu->privateRequestLen++;
|
|
bufSpace--;
|
|
}
|
|
updateCrc(fwu, b);
|
|
fwu->privateObjectIx++;
|
|
}
|
|
|
|
*p = FWU_EOM;
|
|
fwu->privateCommandRequest = FWU_CR_SENDONLY;
|
|
}
|
|
|
|
static void fwuPrepareSendBuffer(TFwu *fwu, uint8_t *data, uint8_t len) {
|
|
// TODO assert privateCommandState == FWU_CS_IDLE | _DONE | _FAIL
|
|
// TODO assert len <= FWU_REQUEST_BUF_SIZE
|
|
|
|
uint8_t i;
|
|
uint8_t *p = &fwu->privateRequestBuf[0];
|
|
|
|
fwu->privateRequestIx = 0;
|
|
fwu->privateRequestLen = len + 1;
|
|
fwu->privateResponseLen = 0;
|
|
|
|
// Copy the data into our internal buffer.
|
|
for (i = 0; i < len; i++) {
|
|
*p++ = *data++;
|
|
}
|
|
|
|
// Add the end-of-message marker.
|
|
*p = FWU_EOM;
|
|
|
|
// Ready to send!
|
|
fwu->privateCommandRequest = FWU_CR_SEND;
|
|
}
|
|
|
|
static void updateCrc(TFwu *fwu, uint8_t b) {
|
|
uint8_t i;
|
|
uint32_t crc = fwu->privateObjectCrc;
|
|
crc ^= b;
|
|
for (i = 0; i < 8; i++) {
|
|
uint32_t m = (crc & 1) ? 0xffffffff : 0;
|
|
crc = (crc >> 1) ^ (0xedb88320u & m);
|
|
}
|
|
fwu->privateObjectCrc = crc;
|
|
}
|
|
|
|
static void fwuSignalFailure(TFwu *fwu, EFwuResponseStatus reason) {
|
|
fwu->responseStatus = reason;
|
|
fwu->privateCommandState = FWU_CS_FAIL;
|
|
// Signal failure to process state machine
|
|
fwu->privateProcessRequest = FWU_PR_REQUEST_FAILED;
|
|
}
|
|
|
|
static inline uint16_t fwuLittleEndianToHost16(uint8_t *bytes) {
|
|
return bytes[0] | ((uint16_t)bytes[1] << 8);
|
|
}
|
|
|
|
static inline uint32_t fwuLittleEndianToHost32(uint8_t *bytes) {
|
|
return bytes[0] | ((uint16_t)bytes[1] << 8) | ((uint32_t)bytes[2] << 16) |
|
|
((uint32_t)bytes[3] << 24);
|
|
}
|
|
|
|
static inline void fwuHostToLittleEndian32(uint32_t v, uint8_t *bytes) {
|
|
uint8_t i;
|
|
for (i = 0; i < 4; i++) {
|
|
*bytes++ = v & 0xff;
|
|
v = v >> 8;
|
|
}
|
|
}
|