summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Fennema <fennema@google.com>2016-05-26 12:29:19 -0700
committerBen Fennema <fennema@google.com>2016-05-27 14:48:36 -0700
commit019ae84cb5b18417772b683fded7719450349272 (patch)
tree263f0dc3fabdb83f22c9ac435cd8cee3d11952e7
parentd61f9ebd0671c1066118199537cd6057ecbf8697 (diff)
downloadcontexthub-019ae84cb5b18417772b683fded7719450349272.tar.gz
hostIntf: Schedule appSec processing in the background
Bug: 28982986 Bug: 28917000 Change-Id: I05769ac3d476f5a197ad6813df8f4c0f9b9f309e Signed-off-by: Ben Fennema <fennema@google.com>
-rw-r--r--firmware/src/nanohubCommand.c217
-rw-r--r--firmware/src/platform/stm32f4xx/bl.c42
-rw-r--r--firmware/src/seos.c3
3 files changed, 146 insertions, 116 deletions
diff --git a/firmware/src/nanohubCommand.c b/firmware/src/nanohubCommand.c
index 838d4ba4..5e5f33bd 100644
--- a/firmware/src/nanohubCommand.c
+++ b/firmware/src/nanohubCommand.c
@@ -57,7 +57,13 @@
#define SYNC_DATAPOINTS 16
#define SYNC_RESET 10000000000ULL /* 10 seconds, ~100us drift */
-#define REQUIRE_SIGNED_IMAGE true
+// maximum number of bytes to feed into appSecRxData at once
+// The bigger the number, the more time we block other event processing
+// appSecRxData only feeds 16 bytes at a time into writeCbk, so large
+// numbers don't buy us that much
+#define MAX_APP_SEC_RX_DATA_LEN 64
+
+#define REQUIRE_SIGNED_IMAGE true
struct DownloadState
{
@@ -70,6 +76,7 @@ struct DownloadState
uint32_t srcCrc; // current state of CRC-32 we generate from input
uint8_t data[NANOHUB_PACKET_PAYLOAD_MAX];
uint8_t len;
+ uint8_t lenLeft;
uint8_t chunkReply;
bool erase;
bool eraseScheduled;
@@ -299,16 +306,6 @@ static uint32_t startFirmwareUpload(void *rx, uint8_t rx_len, void *tx, uint64_t
return sizeof(*resp);
}
-static AppSecErr giveAppSecTimeIfNeeded(struct AppSecState *state, AppSecErr prevRet)
-{
- /* XXX: this will need to go away for real asynchronicity */
-
- while (prevRet == APP_SEC_NEED_MORE_TIME)
- prevRet = appSecDoSomeProcessing(state);
-
- return prevRet;
-}
-
static void deferredUpdateOs(void *cookie)
{
const struct AppHdr *app = cookie;
@@ -354,49 +351,103 @@ static AppSecErr updateKey(const struct AppHdr *app)
return ret;
}
+static uint32_t appSecErrToNanohubReply(AppSecErr status)
+{
+ uint32_t reply;
+
+ switch (status) {
+ case APP_SEC_NO_ERROR:
+ reply = NANOHUB_FIRMWARE_UPLOAD_SUCCESS;
+ break;
+ case APP_SEC_KEY_NOT_FOUND:
+ reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_KEY_NOT_FOUND;
+ break;
+ case APP_SEC_HEADER_ERROR:
+ reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_HEADER_ERROR;
+ break;
+ case APP_SEC_TOO_MUCH_DATA:
+ reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_MUCH_DATA;
+ break;
+ case APP_SEC_TOO_LITTLE_DATA:
+ reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_LITTLE_DATA;
+ break;
+ case APP_SEC_SIG_VERIFY_FAIL:
+ reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_VERIFY_FAIL;
+ break;
+ case APP_SEC_SIG_DECODE_FAIL:
+ reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_DECODE_FAIL;
+ break;
+ case APP_SEC_SIG_ROOT_UNKNOWN:
+ reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_ROOT_UNKNOWN;
+ break;
+ case APP_SEC_MEMORY_ERROR:
+ reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_MEMORY_ERROR;
+ break;
+ case APP_SEC_INVALID_DATA:
+ reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_INVALID_DATA;
+ break;
+ case APP_SEC_VERIFY_FAILED:
+ reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_VERIFY_FAILED;
+ break;
+ default:
+ reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_BAD;
+ break;
+ }
+ return reply;
+}
+
static uint32_t firmwareFinish(bool valid)
{
struct AppHdr *app;
struct Segment *storageSeg;
uint32_t segState;
- uint32_t ret = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
-
- if (!mDownloadState)
- return mAppSecStatus;
+ uint32_t ret = NANOHUB_FIRMWARE_UPLOAD_SUCCESS;
- mAppSecStatus = appSecRxDataOver(mDownloadState->appSecState);
- mAppSecStatus = giveAppSecTimeIfNeeded(mDownloadState->appSecState, mAppSecStatus);
+ if (!mDownloadState) {
+ ret = appSecErrToNanohubReply(mAppSecStatus);
+ osLog(LOG_INFO, "%s: no DL status; decoding secure status: %" PRIu32 "\n", __func__, ret);
+ return ret;
+ }
app = mDownloadState->start;
storageSeg = osGetSegment(app);
if (mAppSecStatus == APP_SEC_NO_ERROR && valid) {
+ osLog(LOG_INFO, "%s: Secure verification passed\n", __func__);
if (storageSeg->state != SEG_ST_RESERVED ||
mDownloadState->size < sizeof(struct FwCommonHdr) ||
app->hdr.magic != APP_HDR_MAGIC ||
app->hdr.fwVer != APP_HDR_VER_CUR) {
segState = SEG_ST_ERASED;
+ osLog(LOG_INFO, "%s: Header verification failed\n", __func__);
} else {
segState = SEG_ST_VALID;
}
} else {
segState = SEG_ST_ERASED;
+ osLog(LOG_INFO, "%s: Secure verification failed: valid=%d; status=%" PRIu32 "\n", __func__, valid, mAppSecStatus);
}
- if (!osAppSegmentClose(app, mDownloadState->dstOffset, segState))
- ret = NANOHUB_FIRMWARE_CHUNK_REPLY_RESEND;
-
- segState = osAppSegmentGetState(app);
+ if (!osAppSegmentClose(app, mDownloadState->dstOffset, segState)) {
+ osLog(LOG_INFO, "%s: Failed to close segment\n", __func__);
+ valid = false;
+ } else {
+ segState = osAppSegmentGetState(app);
+ valid = (segState == SEG_ST_VALID);
+ }
osLog(LOG_INFO, "Loaded %s image type %" PRIu8 ": %" PRIu32
" bytes @ %p; state=%02" PRIX32 "\n",
- segState == SEG_ST_VALID ? "valid" : "invalid",
+ valid ? "valid" : "invalid",
app->hdr.payInfoType, mDownloadState->size,
mDownloadState->start, segState);
freeDownloadState(); // no more access to mDownloadState
+ if (!valid)
+ ret = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_BAD;
+
// take extra care about some special payload types
- if (ret == APP_SEC_NO_ERROR) {
+ if (ret == NANOHUB_FIRMWARE_UPLOAD_SUCCESS) {
switch(app->hdr.payInfoType) {
case LAYOUT_OS:
osLog(LOG_INFO, "Performing OS update\n");
@@ -404,17 +455,23 @@ static uint32_t firmwareFinish(bool valid)
osDefer(deferredUpdateOs, (void*)app, false);
break;
case LAYOUT_KEY:
- ret = updateKey(app);
+ ret = appSecErrToNanohubReply(updateKey(app));
break;
}
}
- if (ret != APP_SEC_NO_ERROR || (app->hdr.fwFlags & FL_APP_HDR_VOLATILE)) {
- if ((app->hdr.fwFlags | FL_APP_HDR_SECURE))
+ if (ret != NANOHUB_FIRMWARE_UPLOAD_SUCCESS || (app->hdr.fwFlags & FL_APP_HDR_VOLATILE)) {
+ if ((app->hdr.fwFlags & FL_APP_HDR_SECURE))
osAppWipeData((struct AppHdr*)app);
osAppSegmentSetState(app, SEG_ST_ERASED);
}
+ // if any error happened after we downloaded and verified image, we say it is unknown fault
+ // we don't have download status, so e have to save returned value in secure status field, because
+ // host may request the same status multiple times
+ if (ret != NANOHUB_FIRMWARE_UPLOAD_SUCCESS)
+ mAppSecStatus = APP_SEC_BAD;
+
return ret;
}
@@ -432,25 +489,34 @@ static void firmwareErase(void *cookie)
mDownloadState->eraseScheduled = false;
}
-static bool firmwareWrite(bool checkCrc)
+static void firmwareWrite(void *cookie)
{
- /* XXX: this will need to change for real asynchronicity */
- const uint8_t *data = mDownloadState->data;
- uint32_t len = mDownloadState->len, lenLeft;
- mAppSecStatus = APP_SEC_NO_ERROR;
bool valid;
bool finished = false;
+ struct NanohubHalContUploadTx *resp = cookie;
+ // only check crc when cookie is NULL (write came from kernel, not HAL)
+ bool checkCrc = !cookie;
+
+ if (mAppSecStatus == APP_SEC_NEED_MORE_TIME) {
+ mAppSecStatus = appSecDoSomeProcessing(mDownloadState->appSecState);
+ } else if (mDownloadState->lenLeft) {
+ const uint8_t *data = mDownloadState->data + mDownloadState->len - mDownloadState->lenLeft;
+ uint32_t len = mDownloadState->lenLeft, lenLeft, lenRem = 0;
+
+ if (len > MAX_APP_SEC_RX_DATA_LEN) {
+ lenRem = len - MAX_APP_SEC_RX_DATA_LEN;
+ len = MAX_APP_SEC_RX_DATA_LEN;
+ }
- while (len) {
mAppSecStatus = appSecRxData(mDownloadState->appSecState, data, len, &lenLeft);
- data += len - lenLeft;
- len = lenLeft;
-
- mAppSecStatus = giveAppSecTimeIfNeeded(mDownloadState->appSecState, mAppSecStatus);
+ mDownloadState->lenLeft = lenLeft + lenRem;
}
valid = (mAppSecStatus == APP_SEC_NO_ERROR);
- if (valid) {
+ if (mAppSecStatus == APP_SEC_NEED_MORE_TIME || mDownloadState->lenLeft) {
+ osDefer(firmwareWrite, cookie, false);
+ return;
+ } else if (valid) {
if (mDownloadState->srcOffset == mDownloadState->size) {
finished = true;
valid = !checkCrc || mDownloadState->crc == ~mDownloadState->srcCrc;
@@ -460,18 +526,24 @@ static bool firmwareWrite(bool checkCrc)
}
if (!valid)
finished = true;
- if (finished)
- firmwareFinish(valid);
-
- return valid;
+ if (finished) {
+ if (firmwareFinish(valid) != NANOHUB_FIRMWARE_UPLOAD_SUCCESS)
+ valid = false;
+ }
+ if (resp) {
+ resp->success = valid;
+ osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
+ }
}
-static uint32_t doFirmwareChunk(uint8_t *data, uint32_t offset, uint32_t len, bool checkCrc)
+static uint32_t doFirmwareChunk(uint8_t *data, uint32_t offset, uint32_t len, void *cookie)
{
uint32_t reply;
if (!mDownloadState) {
reply = NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL_NO_RETRY;
+ } else if (mAppSecStatus == APP_SEC_NEED_MORE_TIME || mDownloadState->lenLeft) {
+ reply = NANOHUB_FIRMWARE_CHUNK_REPLY_RESEND;
} else if (mDownloadState->chunkReply != NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED) {
reply = mDownloadState->chunkReply;
firmwareFinish(false);
@@ -488,13 +560,13 @@ static uint32_t doFirmwareChunk(uint8_t *data, uint32_t offset, uint32_t len, bo
reply = NANOHUB_FIRMWARE_CHUNK_REPLY_RESTART;
resetDownloadState(false);
} else {
- if (checkCrc)
+ if (!cookie)
mDownloadState->srcCrc = crc32(data, len, mDownloadState->srcCrc);
mDownloadState->srcOffset += len;
memcpy(mDownloadState->data, data, len);
- mDownloadState->len = len;
- reply = firmwareWrite(checkCrc) ? NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED :
- NANOHUB_FIRMWARE_CHUNK_REPLY_CANCEL;
+ mDownloadState->lenLeft = mDownloadState->len = len;
+ reply = NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED;
+ osDefer(firmwareWrite, cookie, false);
}
}
@@ -508,7 +580,7 @@ static uint32_t firmwareChunk(void *rx, uint8_t rx_len, void *tx, uint64_t times
uint32_t offset = le32toh(req->offset);
uint8_t len = rx_len - sizeof(req->offset);
- resp->chunkReply = doFirmwareChunk(req->data, offset, len, true);
+ resp->chunkReply = doFirmwareChunk(req->data, offset, len, NULL);
return sizeof(*resp);
}
@@ -518,44 +590,7 @@ static uint32_t doFinishFirmwareUpload()
uint32_t reply;
if (!mDownloadState) {
- switch (mAppSecStatus) {
- case APP_SEC_NO_ERROR:
- reply = NANOHUB_FIRMWARE_UPLOAD_SUCCESS;
- break;
- case APP_SEC_KEY_NOT_FOUND:
- reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_KEY_NOT_FOUND;
- break;
- case APP_SEC_HEADER_ERROR:
- reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_HEADER_ERROR;
- break;
- case APP_SEC_TOO_MUCH_DATA:
- reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_MUCH_DATA;
- break;
- case APP_SEC_TOO_LITTLE_DATA:
- reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_TOO_LITTLE_DATA;
- break;
- case APP_SEC_SIG_VERIFY_FAIL:
- reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_VERIFY_FAIL;
- break;
- case APP_SEC_SIG_DECODE_FAIL:
- reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_DECODE_FAIL;
- break;
- case APP_SEC_SIG_ROOT_UNKNOWN:
- reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_SIG_ROOT_UNKNOWN;
- break;
- case APP_SEC_MEMORY_ERROR:
- reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_MEMORY_ERROR;
- break;
- case APP_SEC_INVALID_DATA:
- reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_INVALID_DATA;
- break;
- case APP_SEC_VERIFY_FAILED:
- reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_VERIFY_FAILED;
- break;
- default:
- reply = NANOHUB_FIRMWARE_UPLOAD_APP_SEC_BAD;
- break;
- }
+ reply = appSecErrToNanohubReply(mAppSecStatus);
} else if (mDownloadState->srcOffset == mDownloadState->size) {
reply = NANOHUB_FIRMWARE_UPLOAD_PROCESSING;
} else {
@@ -569,7 +604,8 @@ static uint32_t finishFirmwareUpload(void *rx, uint8_t rx_len, void *tx, uint64_
{
struct NanohubFinishFirmwareUploadResponse *resp = tx;
resp->uploadReply = doFinishFirmwareUpload();
- osLog(LOG_INFO, "%s: reply=%" PRIu8 "\n", __func__, resp->uploadReply);
+ if (resp->uploadReply != NANOHUB_FIRMWARE_UPLOAD_PROCESSING)
+ osLog(LOG_INFO, "%s: reply=%" PRIu8 "\n", __func__, resp->uploadReply);
return sizeof(*resp);
}
@@ -1093,14 +1129,15 @@ static void halContUpload(void *rx, uint8_t rx_len)
} else {
offset = le32toh(req->offset);
len = rx_len - sizeof(req->offset);
- reply = doFirmwareChunk(req->data, offset, len, false);
+ reply = doFirmwareChunk(req->data, offset, len, resp);
}
- if (reply != NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED)
+ if (reply != NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED) {
osLog(LOG_ERROR, "%s: reply=%" PRIu32 "\n", __func__, reply);
- resp->success = (reply == NANOHUB_FIRMWARE_CHUNK_REPLY_ACCEPTED);
+ resp->success = false;
- osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
+ osEnqueueEvtOrFree(EVT_APP_TO_HOST, resp, heapFree);
+ }
}
static void halFinishUpload(void *rx, uint8_t rx_len)
diff --git a/firmware/src/platform/stm32f4xx/bl.c b/firmware/src/platform/stm32f4xx/bl.c
index ac2d4ec9..2786c365 100644
--- a/firmware/src/platform/stm32f4xx/bl.c
+++ b/firmware/src/platform/stm32f4xx/bl.c
@@ -430,8 +430,7 @@ static bool blProgramFlash(uint8_t *dst, const uint8_t *src, uint32_t length, ui
{
struct StmFlash *flash = (struct StmFlash *)FLASH_BASE;
const uint32_t sector_cnt = sizeof(mBlFlashTable) / sizeof(struct blFlashTable);
- uint32_t acr_cache, cr_cache, offset, i, j = 0, int_state = 0, erase_cnt = 0;
- uint8_t erase_mask[sector_cnt];
+ uint32_t acr_cache, cr_cache, offset, i, j = 0, int_state = 0;
uint8_t *ptr;
if (((length == 0)) ||
@@ -442,15 +441,6 @@ static bool blProgramFlash(uint8_t *dst, const uint8_t *src, uint32_t length, ui
return false;
}
- // disable interrupts
- // otherwise an interrupt during flash write/erase will stall the processor
- // until the write/erase completes
- int_state = blDisableInts();
-
- // figure out which (if any) blocks we have to erase
- for (i = 0; i < sector_cnt; i++)
- erase_mask[i] = 0;
-
// compute which flash block we are starting from
for (i = 0; i < sector_cnt; i++) {
if (dst >= mBlFlashTable[i].address &&
@@ -460,7 +450,7 @@ static bool blProgramFlash(uint8_t *dst, const uint8_t *src, uint32_t length, ui
}
// now loop through all the flash blocks and see if we have to do any
- // 0 -> 1 transitions of a bit. If so, we must erase that block
+ // 0 -> 1 transitions of a bit. If so, return false
// 1 -> 0 transitions of a bit do not require an erase
offset = (uint32_t)(dst - mBlFlashTable[i].address);
ptr = mBlFlashTable[i].address;
@@ -472,16 +462,18 @@ static bool blProgramFlash(uint8_t *dst, const uint8_t *src, uint32_t length, ui
}
if ((ptr[offset] & src[j]) != src[j]) {
- erase_mask[i] = 1;
- erase_cnt++;
- j += mBlFlashTable[i].length - offset;
- offset = mBlFlashTable[i].length;
+ return false;
} else {
j++;
offset++;
}
}
+ // disable interrupts
+ // otherwise an interrupt during flash write will stall the processor
+ // until the write completes
+ int_state = blDisableInts();
+
// wait for flash to not be busy (should never be set at this point)
while (flash->SR & FLASH_SR_BSY);
@@ -508,9 +500,6 @@ static bool blProgramFlash(uint8_t *dst, const uint8_t *src, uint32_t length, ui
flash->ACR &= ~(FLASH_ACR_DCEN | FLASH_ACR_ICEN);
flash->ACR |= (FLASH_ACR_DCRST | FLASH_ACR_ICRST);
- if (erase_cnt)
- blEraseSectors(sector_cnt, erase_mask);
-
blWriteBytes(dst, src, length);
flash->ACR = acr_cache;
@@ -521,7 +510,7 @@ static bool blProgramFlash(uint8_t *dst, const uint8_t *src, uint32_t length, ui
return !memcmp(dst, src, length);
}
-static bool blProgramTypedArea(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t key1, uint32_t key2, uint32_t type)
+static bool blProgramTypedArea(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t type, uint32_t key1, uint32_t key2)
{
const uint32_t sector_cnt = sizeof(mBlFlashTable) / sizeof(struct blFlashTable);
uint32_t i;
@@ -542,12 +531,12 @@ static bool blProgramTypedArea(uint8_t *dst, const uint8_t *src, uint32_t length
static bool blExtApiProgramSharedArea(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t key1, uint32_t key2)
{
- return blProgramTypedArea(dst, src, length, key1, key2, BL_FLASH_SHARED);
+ return blProgramTypedArea(dst, src, length, BL_FLASH_SHARED, key1, key2);
}
static bool blExtApiProgramEe(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t key1, uint32_t key2)
{
- return blProgramTypedArea(dst, src, length, key1, key2, BL_FLASH_EEDATA);
+ return blProgramTypedArea(dst, src, length, BL_FLASH_EEDATA, key1, key2);
}
static bool blEraseTypedArea(uint32_t type, uint32_t key1, uint32_t key2)
@@ -716,15 +705,16 @@ const struct BlVecTable __attribute__((section(".blvec"))) __BL_VECTORS =
static void blApplyVerifiedUpdate(const struct OsUpdateHdr *os) //only called if an update has been found to exist and be valid, signed, etc!
{
//copy shared to code, and if successful, erase shared area
- if (blProgramFlash(__code_start, (const uint8_t*)(os + 1), os->size, BL_FLASH_KEY1, BL_FLASH_KEY2))
- (void)blExtApiEraseSharedArea(BL_FLASH_KEY1, BL_FLASH_KEY2);
+ if (blEraseTypedArea(BL_FLASH_KERNEL, BL_FLASH_KEY1, BL_FLASH_KEY2))
+ if (blProgramTypedArea(__code_start, (const uint8_t*)(os + 1), os->size, BL_FLASH_KERNEL, BL_FLASH_KEY1, BL_FLASH_KEY2))
+ (void)blExtApiEraseSharedArea(BL_FLASH_KEY1, BL_FLASH_KEY2);
}
static void blWriteMark(struct OsUpdateHdr *hdr, uint32_t mark)
{
uint8_t dstVal = mark;
- (void)blProgramFlash(&hdr->marker, &dstVal, sizeof(hdr->marker), BL_FLASH_KEY1, BL_FLASH_KEY2);
+ (void)blExtApiProgramSharedArea(&hdr->marker, &dstVal, sizeof(hdr->marker), BL_FLASH_KEY1, BL_FLASH_KEY2);
}
static void blUpdateMark(uint32_t old, uint32_t new)
@@ -1071,7 +1061,7 @@ static void blSpiLoader(bool force)
break;
//do it
- ack = blProgramFlash(__shared_start + addr, data, len, BL_FLASH_KEY1, BL_FLASH_KEY2);
+ ack = blExtApiProgramSharedArea(__shared_start + addr, data, len, BL_FLASH_KEY1, BL_FLASH_KEY2);
blSpiLoaderDrainRxFifo(spi);
nextAddr += len;
break;
diff --git a/firmware/src/seos.c b/firmware/src/seos.c
index c0a0dd46..cc245760 100644
--- a/firmware/src/seos.c
+++ b/firmware/src/seos.c
@@ -508,6 +508,9 @@ bool osWriteShared(void *dest, const void *src, uint32_t len)
mpuAllowRomWrite(false);
mpuAllowRamExecution(false);
+ if (!ret)
+ osLog(LOG_ERROR, "osWriteShared: blProgramShared return false\n");
+
return ret;
}