blob: 0b4fa1615fa7f22b2f80465fec3b90a8059c4fb0 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mw320_ota.h"
#include "fsl_debug_console.h"
#include "task.h"
#if (MW320_OTA_TEST == 1)
#include "gpio_fw.h"
unsigned char * mw320_ota_mcufw = gpio_fw_bin;
unsigned int mw320_ota_len = gpio_fw_bin_len;
#endif // MW320_OTA_TEST
extern "C" {
#include "crc32.h"
#include "mflash_drv.h"
#include "partition.h"
typedef int (*data_fetch)(unsigned char * buf, size_t len);
struct partition_entry * part_get_passive_partition(struct partition_entry * p1, struct partition_entry * p2);
}
#define FIRMWARE_UPDATE_BUF_SIZE 256 // Max buffer size can be written in 1 transaction
#define SEC_FW_SIG_LEN 284
#define FW_MAGIC_STR (('M' << 0) | ('R' << 8) | ('V' << 16) | ('L' << 24))
#define FW_MAGIC_SIG ((0x7BUL << 0) | (0xF1UL << 8) | (0x9CUL << 16) | (0x2EUL << 24))
#define SEC_FW_MAGIC_SIG (('S' << 0) | ('B' << 8) | ('F' << 16) | ('H' << 24))
/*
* Firmware magic signature
*
* First word is the string "MRVL" and is endian invariant.
* Second word = magic value 0x2e9cf17b.
* Third word = time stamp (seconds since 00:00:00 UTC, January 1, 1970).
*/
struct img_hdr
{
uint32_t magic_str;
uint32_t magic_sig;
uint32_t time;
uint32_t seg_cnt;
uint32_t entry;
};
struct seg_hdr
{
uint32_t type;
uint32_t offset;
uint32_t len;
uint32_t laddr;
uint32_t crc;
};
struct tlv_hdr
{
uint32_t magic;
uint32_t len;
uint32_t crc;
};
typedef struct tlv_hdr img_sec_hdr;
static uint32_t calculate_image_crc(uint32_t flash_addr, uint32_t size)
{
int32_t result;
uint32_t buf[32];
uint32_t addr = flash_addr;
uint32_t crc = 0U;
PRINTF("Calculate image CRC\r\n");
for (addr = flash_addr; addr < flash_addr + size - sizeof(buf); addr += sizeof(buf))
{
result = mflash_drv_read(addr, buf, sizeof(buf));
if (result != WM_SUCCESS)
{
assert(false);
}
crc = soft_crc32(buf, sizeof(buf), crc);
}
/* Remaining data */
result = mflash_drv_read(addr, buf, flash_addr + size - addr);
if (result != WM_SUCCESS)
{
assert(false);
}
crc = soft_crc32(buf, flash_addr + size - addr, crc);
return crc;
}
static struct partition_entry * get_passive_firmware(void)
{
short history = 0;
struct partition_entry *f1, *f2;
f1 = part_get_layout_by_id(FC_COMP_FW, &history);
f2 = part_get_layout_by_id(FC_COMP_FW, &history);
if (f1 == NULL || f2 == NULL)
{
PRINTF("Unable to retrieve flash layout\r\n");
return NULL;
}
return (struct partition_entry *) part_get_passive_partition(f1, f2);
}
static int fw_update_begin(struct partition_entry * p)
{
/* Erase the passive partition before updating it */
if (mflash_drv_erase(p->start, p->size))
{
PRINTF("Failed to erase partition\r\n");
return -WM_FAIL;
}
return WM_SUCCESS;
}
static int fw_update_data(struct partition_entry * p, const char * data, size_t datalen)
{
int32_t result;
result = mflash_drv_write(p->start, (uint32_t *) data, datalen);
if (result != WM_SUCCESS)
{
PRINTF("Failed to write partition\r\n");
return -WM_FAIL;
}
p->start += datalen;
return WM_SUCCESS;
}
static int verify_load_firmware(uint32_t flash_addr, uint32_t size)
{
struct img_hdr ih;
struct seg_hdr sh;
int32_t result;
if (size < (sizeof(ih) + sizeof(sh)))
{
return -WM_FAIL;
}
result = mflash_drv_read(flash_addr, (uint32_t *) &ih, sizeof(ih));
if (result != WM_SUCCESS)
{
assert(false);
}
/* MCUXpresso SDK image has only 1 segment */
if ((ih.magic_str != FW_MAGIC_STR) || (ih.magic_sig != FW_MAGIC_SIG) || ih.seg_cnt != 1U)
{
return -WM_FAIL;
}
result = mflash_drv_read(flash_addr + sizeof(ih), (uint32_t *) &sh, sizeof(sh));
if (result != kStatus_Success)
{
assert(false);
}
/* Image size should just cover segment end. */
if (sh.len + sh.offset != size)
{
return -WM_FAIL;
}
if (calculate_image_crc(flash_addr + sh.offset, sh.len) != sh.crc)
{
return -WM_FAIL;
}
return WM_SUCCESS;
}
static void mw320_dev_reset(unsigned int delay_ms)
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
NVIC_SystemReset();
return;
}
// ============================================================================
typedef struct _fw_update_param
{
// Accumulated size
uint32_t pump_size; // How many bytes has been written
uint32_t p_start; // Start address of the partition
img_sec_hdr ish; // ish from downloaded firmware buffer
// int hdr_size;
// Pointer to passive firmware
struct partition_entry part_arg;
// Callback function to write the data
// data_fetch data_fetch_cb;
} fw_update_param_t;
static fw_update_param_t g_fmup_param;
/*
Initialize the ota function by getting an id
*/
fw_update_id_t mw320_fw_update_begin(void)
{
int error;
struct partition_entry * p;
if (g_fmup_param.pump_size != 0)
{
PRINTF("Warning, wt_addr is not 0. last fw_abort may be missing \r\n");
}
memset(&g_fmup_param, 0, sizeof(g_fmup_param));
// ----------------------------------------------------
// Initialize the firmware update parameters
p = get_passive_firmware();
memcpy(&g_fmup_param.part_arg, p, sizeof(struct partition_entry));
g_fmup_param.p_start = p->start;
// ----------------------------------------------------
// Erase the partition
error = fw_update_begin(&g_fmup_param.part_arg);
if (error != WM_SUCCESS)
{
return NULL;
}
return &g_fmup_param;
}
/*
Write the received chunk to the partition
*/
int mw320_fw_update_wrblock(fw_update_id_t fwup_id, unsigned char * pblock, unsigned int blksize)
{
fw_update_param_t * pfwup_parm = (fw_update_param_t *) fwup_id;
unsigned int wr_size = 0; // How many bytes has been written
unsigned char * buf = pblock;
int error;
if (pfwup_parm != &g_fmup_param)
{
PRINTF("Incorrect fw update id \r\n");
return -WM_FAIL;
}
// Check if the size is available
if ((pfwup_parm->pump_size + blksize) > pfwup_parm->part_arg.size)
{
PRINTF("[Error], Firmware size is too big. (limit, current, new)=(%u, %u, %u) \r\n", pfwup_parm->part_arg.size,
pfwup_parm->pump_size, blksize);
// purge_stream_bytes(pfwup_parm->data_fetch_cb, blksize, pblock, FIRMWARE_UPDATE_BUF_SIZE);
return -WM_FAIL;
}
// If it's the 1st block
if (pfwup_parm->p_start == pfwup_parm->part_arg.start)
{
memcpy(&pfwup_parm->ish, buf, sizeof(img_sec_hdr));
}
/*
Write the data chunk to firmware partition
*/
while (wr_size < blksize)
{
unsigned int blk_left = blksize - wr_size;
unsigned int chk_size = (blk_left < FIRMWARE_UPDATE_BUF_SIZE) ? blk_left : FIRMWARE_UPDATE_BUF_SIZE;
error = fw_update_data(&pfwup_parm->part_arg, (char *) buf, chk_size);
if (error)
{
PRINTF("Failed to write firmware data \r\n");
return -WM_FAIL;
}
wr_size += chk_size;
pfwup_parm->pump_size += chk_size;
buf += chk_size;
}
return WM_SUCCESS;
}
/*
Finalize the firmware update.
- rst_delay_sec: Delay to reset the device
>= 0: Will reset the device after rst_delay_sec seconds
<0: No reset
*/
int mw320_fw_update_end(fw_update_id_t fwup_id, int rst_delay_sec)
{
fw_update_param_t * pfwup_parm = (fw_update_param_t *) fwup_id;
int error;
if (pfwup_parm != &g_fmup_param)
{
PRINTF("Incorrect fw update id \r\n");
return -WM_FAIL;
}
// Validate the firmware data in flash
PRINTF("Firmware verification start ... filesize = %d\r\n", pfwup_parm->pump_size);
if (pfwup_parm->ish.magic == SEC_FW_MAGIC_SIG)
{
error = verify_load_firmware((pfwup_parm->p_start + SEC_FW_SIG_LEN), (pfwup_parm->pump_size - SEC_FW_SIG_LEN));
}
else
{
error = verify_load_firmware(pfwup_parm->p_start, pfwup_parm->pump_size);
}
if (error != WM_SUCCESS)
{
PRINTF("FW verification fail! Keep the same firmware \r\n");
return -WM_FAIL;
}
PRINTF("FW verification pass, switch the firmware partition \r\n");
// Restore the start
pfwup_parm->part_arg.start = pfwup_parm->p_start;
// Switch the active partition to the new one
part_set_active_partition(&pfwup_parm->part_arg);
// Reset the device if requested
if (rst_delay_sec > 0)
{
mw320_dev_reset(rst_delay_sec * 1000);
}
return WM_SUCCESS;
}
/*
Abort the ota function
*/
int mw320_fw_update_abort(fw_update_id_t fwup_id)
{
fw_update_param_t * pfwup_parm = (fw_update_param_t *) fwup_id;
if (pfwup_parm != &g_fmup_param)
{
PRINTF("Incorrect fw update id \r\n");
return -WM_FAIL;
}
memcpy(&g_fmup_param, 0, sizeof(g_fmup_param));
return WM_SUCCESS;
}
#if (MW320_OTA_TEST == 1)
// Module Test function
// This is also an example that how to use the interface
void mw320_fw_update_test(void)
{
unsigned int wr_size = 0;
int error;
fw_update_id_t fwupid = mw320_fw_update_begin();
while (wr_size < mw320_ota_len)
{
unsigned int chk_size = ((mw320_ota_len - wr_size) < 1024) ? (mw320_ota_len - wr_size) : 1024;
PRINTF("===> Wt (%d, %d)\r\n", wr_size, chk_size);
error = mw320_fw_update_wrblock(fwupid, &mw320_ota_mcufw[wr_size], chk_size);
if (error != WM_SUCCESS)
{
break;
}
wr_size += chk_size;
}
if (error == WM_SUCCESS)
{
mw320_fw_update_end(fwupid, -1);
}
return;
}
#endif // MW320_OTA_TEST