//***************************************************************************** // // appl_amota.c //! @file //! //! @brief Appl notification center service client. // //***************************************************************************** //***************************************************************************** // // Copyright (c) 2020, Ambiq Micro // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its // contributors may be used to endorse or promote products derived from this // software without specific prior written permission. // // Third party software included in this distribution is subject to the // additional license terms as defined in the /docs/licenses directory. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // This is part of revision 2.4.2 of the AmbiqSuite Development Package. // //***************************************************************************** /** * \file appl_amota.c * * This file contains the AMOTA application. * Sample applications detailed below: * a. The Sensor, as defined by the Sepcification plays the GAP Peripheral * role. * b. The Sensor application has following sevice records: * - GAP * - GATT * - Battery * - Device Information and * - AMOTA * [NOTE]: Please see gatt_db.c for more details of the record. * c. appl_manage_transfer routine takes care of handling peer * configuration. This handling would be needed: * - When Peer Configures Measurement Transfer by writting to the * Characteristic Client Configuration of AMOTA Tx. * - Subsequent reconnection with bonded device that had already * configured the device for transfer. Please note it is mandatory * for GATT Servers to remember the configurations of bonded GATT * clients. * In order to ensure the above mentioned configurations are correctly * handled, the routine, appl_manage_transfer, is therefore called from: * - gatt_db_amotas_handler and * - appl_amotas_connect * [NOTE]: If link does not have the needed secruity for the service, * transfer will not be initiated. */ /* --------------------------------------------- Header File Inclusion */ #include "appl.h" #include "am_mcu_apollo.h" #include "am_bsp.h" #include "am_util.h" #include "am_bootloader.h" #include "amota_profile_config.h" #include "am_multi_boot.h" #include "amota_crc32.h" #ifdef AMOTAS static am_multiboot_flash_info_t *g_pFlash = &g_intFlash; #if defined(AM_PART_APOLLO3) || defined(AM_PART_APOLLO3P) #if (AMOTAS_SUPPORT_EXT_FLASH == 1) #error "External flash based OTA not supported in Apollo3" #endif #endif #define BT_MODULE_BIT_MASK_AMOTA 0x00010000 #define BT_MODULE_ID_AMOTA (BT_MODULE_PAGE_2 | BT_MODULE_BIT_MASK_AMOTA) #define AMOTA_ERR(...) BT_debug_error(BT_MODULE_BIT_MASK_AMOTA, __VA_ARGS__) #define AMOTA_TRC(...) BT_debug_trace(BT_MODULE_BIT_MASK_AMOTA, __VA_ARGS__) // Need temporary buf equal to one flash page size (larger of int or ext, if ext flash is supported) // We accumulate data in this buffer and perform Writes only on page boundaries in // multiple of page lentghs #if (AMOTAS_SUPPORT_EXT_FLASH == 1) && (AM_DEVICES_SPIFLASH_PAGE_SIZE > AM_HAL_FLASH_PAGE_SIZE) #define AMOTAS_TEMP_BUFSIZE AM_DEVICES_SPIFLASH_PAGE_SIZE #else #define AMOTAS_TEMP_BUFSIZE AM_HAL_FLASH_PAGE_SIZE #endif // Protection against NULL pointer #define FLASH_OPERATE(pFlash, func) ((pFlash)->func ? (pFlash)->func() : 0) // // Data structure for flash operation // typedef struct { uint8_t writeBuffer[AMOTAS_TEMP_BUFSIZE] __attribute__((aligned(4))); // needs to be 32-bit word aligned. uint16_t bufferIndex; }amotasFlashOp_t; amotasFlashOp_t amotasFlash = { .bufferIndex = 0, }; // Temporary scratch buffer used to read from flash uint32_t amotasTmpBuf[AMOTA_PACKET_SIZE / 4]; //***************************************************************************** // // Macro definitions // //***************************************************************************** #define DISCONNECT_TIMEOUT_DEFAULT 1 // 1 second #define RESET_TIMEOUT_DEFAULT 1 // 1 second // // amota states // typedef enum { AMOTA_STATE_INIT, AMOTA_STATE_GETTING_FW, AMOTA_STATE_MAX }eAmotaState; // // amota commands // typedef enum { AMOTA_CMD_UNKNOWN, AMOTA_CMD_FW_HEADER, AMOTA_CMD_FW_DATA, AMOTA_CMD_FW_VERIFY, AMOTA_CMD_FW_RESET, AMOTA_CMD_MAX }eAmotaCommand; // // amota status // typedef enum { AMOTA_STATUS_SUCCESS, AMOTA_STATUS_CRC_ERROR, AMOTA_STATUS_INVALID_HEADER_INFO, AMOTA_STATUS_INVALID_PKT_LENGTH, AMOTA_STATUS_INSUFFICIENT_BUFFER, AMOTA_STATUS_INSUFFICIENT_FLASH, AMOTA_STATUS_UNKNOWN_ERROR, AMOTA_STATUS_FLASH_WRITE_ERROR, AMOTA_STATUS_MAX }eAmotaStatus; // // FW header information // typedef struct { uint32_t encrypted; uint32_t fwStartAddr; // Address to install the image uint32_t fwLength; uint32_t fwCrc; uint32_t secInfoLen; uint32_t resvd1; uint32_t resvd2; uint32_t resvd3; uint32_t version; uint32_t fwDataType; //binary type uint32_t storageType; uint32_t resvd4; } amotaHeaderInfo_t; // // FW header information // typedef struct { uint16_t offset; uint16_t len; // data plus checksum eAmotaCommand type; uint8_t data[AMOTA_PACKET_SIZE] __attribute__((aligned(4))); // needs to be 32-bit word aligned. } amotaPacket_t; // // Firmware Address // typedef struct { uint32_t addr; uint32_t offset; } amotasNewFwFlashInfo_t; /* Control block */ static struct { BOOLEAN txReady; // TRUE if ready to send notifications AmotasCfg_t cfg; // configurable parameters eAmotaState state; amotaHeaderInfo_t fwHeader; amotaPacket_t pkt; amotasNewFwFlashInfo_t newFwFlashInfo; BT_timer_handle resetTimer_handle; // reset timer after OTA update done BT_timer_handle disconnectTimer_handle; // disconnect timer after OTA update done } amotasCb; /* --------------------------------------------- Static Global Variables */ static GATT_DB_HANDLE amotas_gatt_db_handle; static ATT_ATTR_HANDLE amotas_tx_char_handle; static APPL_HANDLE amotas_appl_handle; /* --------------------------------------------- Functions */ // Erases the flash based on ui32Addr & ui32NumBytes void erase_flash(uint32_t ui32Addr, uint32_t ui32NumBytes) { // Erase the image while ( ui32NumBytes ) { g_pFlash->flash_erase_sector(ui32Addr); if ( ui32NumBytes > g_pFlash->flashSectorSize ) { ui32NumBytes -= g_pFlash->flashSectorSize; ui32Addr += g_pFlash->flashSectorSize; } else { break; } } } void amotas_reset_timer_expired(void *data, UINT16 datalen) { AMOTA_TRC("amotas_reset_board\n"); // reset reset timer handle here amotasCb.resetTimer_handle = BT_TIMER_HANDLE_INIT_VAL; am_util_delay_ms(10); #if AM_APOLLO3_RESET am_hal_reset_control(AM_HAL_RESET_CONTROL_SWPOI, 0); #else // AM_APOLLO3_RESET am_hal_reset_poi(); #endif // AM_APOLLO3_RESET } void amotas_disconnect_timer_expired(void *data, UINT16 datalen) { API_RESULT retval; UINT16 appl_ble_connection_handle; AMOTA_TRC("Disconnect BLE connection\n"); appl_ble_connection_handle = APPL_GET_CONNECTION_HANDLE(amotas_appl_handle); BT_hci_disconnect (appl_ble_connection_handle, HC_OTHER_END_TERMINATED_USER); // reset disconnect timer handle here amotasCb.disconnectTimer_handle = BT_TIMER_HANDLE_INIT_VAL; // // Delay here to let disconnect req go through // the RF before we reboot. // if ( BT_TIMER_HANDLE_INIT_VAL != amotasCb.resetTimer_handle ) { retval = BT_stop_timer (amotasCb.resetTimer_handle); AMOTA_TRC ( "[AMOTA]: Stopping Timer with result 0x%04X, timer handle %p\n", retval, amotasCb.resetTimer_handle); amotasCb.resetTimer_handle = BT_TIMER_HANDLE_INIT_VAL; } retval = BT_start_timer ( &amotasCb.resetTimer_handle, RESET_TIMEOUT_DEFAULT, amotas_reset_timer_expired, NULL, 0 ); if (API_SUCCESS != retval) { // we have to reset from here directly. amotas_reset_timer_expired(NULL, 0); } } void appl_amotas_init(void) { memset(&amotasCb, 0, sizeof(amotasCb)); amotasCb.txReady = false; amotasCb.state = AMOTA_STATE_INIT; amotasCb.resetTimer_handle = BT_TIMER_HANDLE_INIT_VAL; amotasCb.disconnectTimer_handle = BT_TIMER_HANDLE_INIT_VAL; amotasCb.pkt.offset = 0; amotasCb.pkt.len = 0; amotasCb.pkt.type = AMOTA_CMD_UNKNOWN; } void appl_amotas_connect(APPL_HANDLE * appl_handle) { ATT_VALUE value; UINT16 cli_cnfg; cli_cnfg = 0; // save application handle amotas_appl_handle = *appl_handle; amotas_gatt_db_handle.device_id = APPL_GET_DEVICE_HANDLE((*appl_handle)); amotas_gatt_db_handle.char_id = GATT_CHAR_AMOTA_TX; amotas_gatt_db_handle.service_id = GATT_SER_AMOTA_INST; BT_gatt_db_get_char_val_hndl(&amotas_gatt_db_handle, &amotas_tx_char_handle); BT_gatt_db_get_char_cli_cnfg(&amotas_gatt_db_handle, &value); BT_UNPACK_LE_2_BYTE (&cli_cnfg, value.val); AMOTA_TRC ( "[APPL]: Fetched Client Configuration (0x%04X) for Device (0x%02X)\n", cli_cnfg, APPL_GET_DEVICE_HANDLE((*appl_handle))); appl_manage_trasnfer(amotas_gatt_db_handle, cli_cnfg); } void appl_manage_trasnfer(GATT_DB_HANDLE handle, UINT16 config) { API_RESULT retval; UCHAR security, ekey_size; AMOTA_TRC("[AMOTA]: appl_manage_trasnfer+ \n"); /* Get required security for service */ /* Get required security level */ BT_gatt_db_get_service_security (&handle, &security); /* Get required encryption key size */ BT_gatt_db_get_service_enc_key_size (&handle, &ekey_size); /* Verify if security requirements are available with the link */ retval = appl_smp_assert_security ( &handle.device_id, security, ekey_size ); /* Security requirements satisfied? */ if ( API_SUCCESS != retval ) { /* No. Return */ return; } if ( GATT_CLI_CNFG_NOTIFICATION == config ) { amotasCb.txReady = true; AMOTA_TRC("[AMOTA]: notify registered \n"); } else if ( GATT_CLI_CNFG_DEFAULT == config ) { amotasCb.txReady = false; AMOTA_TRC("[AMOTA]: notify unregistered \n"); } else { /* Incorrect Configuration */ } } void amota_mtu_update(APPL_HANDLE * appl_handle, UINT16 t_mtu) { UINT16 mtu = 0; BT_att_access_mtu(&APPL_GET_ATT_INSTANCE(*appl_handle), &mtu); AMOTA_TRC("appl_handle 0x%x t_mtu = %d %d\n", *appl_handle, t_mtu, mtu); } //***************************************************************************** // // Send Notification to Client // //***************************************************************************** void appl_amotas_send_data(UCHAR *data, UINT16 len) { ATT_HANDLE_VALUE_PAIR hndl_val_param; API_RESULT retval; //AMOTA_TRC("Sending Tx On Handle 0x%04X\n", amotas_tx_char_handle); hndl_val_param.handle = amotas_tx_char_handle; hndl_val_param.value.val = data; hndl_val_param.value.len = len; retval = BT_att_send_hndl_val_ntf ( &APPL_GET_ATT_INSTANCE(amotas_appl_handle), &hndl_val_param ); if ( API_SUCCESS != retval ) { AMOTA_ERR( "[** ERR **]: Failed to send measurement, reason 0x%04X", retval); } } //***************************************************************************** // // Send Reply to Client // //***************************************************************************** static void amotas_reply_to_client(eAmotaCommand cmd, eAmotaStatus status, uint8_t *data, uint16_t len) { uint8_t buf[20] = {0}; buf[0] = (len + 1) & 0xff; buf[1] = ((len + 1) >> 8) & 0xff; buf[2] = cmd; buf[3] = status; if ( len > 0 ) { memcpy(buf + 4, data, len); } appl_amotas_send_data(buf, len + 4); } //***************************************************************************** // // Set Firmware Address // //return true if success, otherwise false //***************************************************************************** static bool amotas_set_fw_addr(void) { bool bResult = false; amotasCb.newFwFlashInfo.addr = 0; amotasCb.newFwFlashInfo.offset = 0; // // Check storage type // if ( amotasCb.fwHeader.storageType == AMOTA_FW_STORAGE_INTERNAL ) { // storage in internal flash uint32_t storeAddr = (AMOTA_INT_FLASH_OTA_ADDRESS + AM_HAL_FLASH_PAGE_SIZE - 1) & ~(AM_HAL_FLASH_PAGE_SIZE - 1); uint32_t maxSize = AMOTA_INT_FLASH_OTA_MAX_SIZE & ~(AM_HAL_FLASH_PAGE_SIZE - 1); #if !defined(AM_PART_APOLLO3) && !defined(AM_PART_APOLLO3P) // There is no easy way to get the information about the main image in Apollo3 uint32_t ui32CurLinkAddr; uint32_t ui32CurLen; // Get information about current main image if ( am_multiboot_get_main_image_info(&ui32CurLinkAddr, &ui32CurLen) == false ) { return false; } // Length in flash page size multiples ui32CurLen = (ui32CurLen + (AM_HAL_FLASH_PAGE_SIZE - 1)) & ~(AM_HAL_FLASH_PAGE_SIZE - 1) ; // Make sure the OTA area does not overwrite the main image if ( !((storeAddr + maxSize) <= ui32CurLinkAddr) && !(storeAddr >= (ui32CurLinkAddr + ui32CurLen)) ) { AMOTA_TRC("OTA memory overlaps with main firmware\n"); return false; } #endif // Check to make sure the incoming image will fit in the space allocated for OTA if ( amotasCb.fwHeader.fwLength > maxSize ) { AMOTA_TRC("not enough OTA space allocated = %d bytes, Desired = %d bytes\n", maxSize, amotasCb.fwHeader.fwLength); return false; } g_pFlash = &g_intFlash; amotasCb.newFwFlashInfo.addr = storeAddr; bResult = true; } else if ( amotasCb.fwHeader.storageType == AMOTA_FW_STORAGE_EXTERNAL ) { //storage in external flash #if (AMOTAS_SUPPORT_EXT_FLASH == 1) // // update target address information // g_pFlash = &g_extFlash; if ( g_pFlash->flash_read_page && g_pFlash->flash_write_page && g_pFlash->flash_erase_sector && (amotasCb.fwHeader.fwLength <= AMOTA_EXT_FLASH_OTA_MAX_SIZE) ) { amotasCb.newFwFlashInfo.addr = AMOTA_EXT_FLASH_OTA_ADDRESS; bResult = true; } else #endif { bResult = false; } } else { // reserved state bResult = false; } if ( bResult == true ) { // // Initialize the flash device. // if ( FLASH_OPERATE(g_pFlash, flash_init) == 0 ) { if ( FLASH_OPERATE(g_pFlash, flash_enable) != 0 ) { FLASH_OPERATE(g_pFlash, flash_deinit); bResult = false; } // // Erase necessary sectors in the flash according to length of the image. // erase_flash(amotasCb.newFwFlashInfo.addr, amotasCb.fwHeader.fwLength); FLASH_OPERATE(g_pFlash, flash_disable); } else { bResult = false; } } return bResult; } static int verify_flash_content(uint32_t flashAddr, uint32_t *pSram, uint32_t len, am_multiboot_flash_info_t *pFlash) { // read back and check uint32_t offset = 0; uint32_t remaining = len; int ret = 0; while (remaining) { uint32_t tmpSize = (remaining > AMOTA_PACKET_SIZE) ? AMOTA_PACKET_SIZE : remaining; pFlash->flash_read_page((uint32_t)amotasTmpBuf, (uint32_t *)(flashAddr + offset), tmpSize); ret = memcmp(amotasTmpBuf, (uint8_t*)((uint32_t)pSram + offset), tmpSize); if ( ret != 0 ) { // there is write failure happened. AMOTA_TRC("flash write verify failed. address 0x%x. length %d\n", flashAddr, len); break; } offset += tmpSize; remaining -= tmpSize; } return ret; } //***************************************************************************** // // Write to Flash // //return true if success, otherwise false //***************************************************************************** #if 1 static bool amotas_write2flash(uint16_t len, uint8_t *buf, uint32_t addr, bool lastPktFlag) { uint16_t ui16BytesRemaining = len; uint32_t ui32TargetAddress = 0; uint8_t ui8PageCount = 0; bool bResult = true; uint16_t i; addr -= amotasFlash.bufferIndex; // // Check the target flash address to ensure we do not operation the wrong address // make sure to write to page boundary // if ( ((uint32_t)amotasCb.newFwFlashInfo.addr > addr) || (addr & (g_pFlash->flashPageSize - 1)) ) { // // application is trying to write to wrong address // return false; } FLASH_OPERATE(g_pFlash, flash_enable); while (ui16BytesRemaining) { uint16_t ui16Bytes2write = g_pFlash->flashPageSize - amotasFlash.bufferIndex; if ( ui16Bytes2write > ui16BytesRemaining ) { ui16Bytes2write = ui16BytesRemaining; } // move data into buffer for ( i = 0; i < ui16Bytes2write; i++ ) { // avoid using memcpy amotasFlash.writeBuffer[amotasFlash.bufferIndex++] = buf[i]; } ui16BytesRemaining -= ui16Bytes2write; buf += ui16Bytes2write; // // Write to flash when there is data more than 1 page size // For last fragment write even if it is less than one page // if ( lastPktFlag || (amotasFlash.bufferIndex == g_pFlash->flashPageSize) ) { ui32TargetAddress = (addr + ui8PageCount*g_pFlash->flashPageSize); // Always write whole pages if ( (g_pFlash->flash_write_page(ui32TargetAddress, (uint32_t *)amotasFlash.writeBuffer, g_pFlash->flashPageSize) != 0) || (verify_flash_content(ui32TargetAddress, (uint32_t *)amotasFlash.writeBuffer, amotasFlash.bufferIndex, g_pFlash) != 0) ) { bResult = false; break; } AMOTA_TRC("Flash write succeeded to address 0x%x. length %d\n", ui32TargetAddress, amotasFlash.bufferIndex); ui8PageCount++; amotasFlash.bufferIndex = 0; bResult = true; } } FLASH_OPERATE(g_pFlash, flash_disable); // // If we get here, operations are done correctly // return bResult; } #else static bool amotas_write2flash(uint16_t len, uint8_t *buf, uint32_t addr, bool lastPktFlag) { // // Program the flash page with the data. // Start a critical section. // if ( am_hal_flash_program_main(AM_HAL_FLASH_PROGRAM_KEY, (uint32_t *)buf, (uint32_t *)addr, len / 4) != 0 ) { // flash helpers return non-zero for false, zero for success return false; } else { return true; } } #endif //***************************************************************************** // // Verify Firmware Image CRC // //return true if success, otherwise false //***************************************************************************** static BOOLEAN amotas_verify_firmware_crc(void) { // read back the whole firmware image from flash and calculate CRC uint32_t ui32CRC = 0; // // Check crc in external flash // FLASH_OPERATE(g_pFlash, flash_enable); // read from spi flash and calculate CRC32 for ( uint16_t i = 0; i < (amotasCb.fwHeader.fwLength / AMOTA_PACKET_SIZE); i++ ) { g_pFlash->flash_read_page((uint32_t)amotasTmpBuf, (uint32_t *)(amotasCb.newFwFlashInfo.addr + i*AMOTA_PACKET_SIZE), AMOTA_PACKET_SIZE); am_bootloader_partial_crc32(amotasTmpBuf, AMOTA_PACKET_SIZE, &ui32CRC); } uint32_t ui32Remainder = amotasCb.fwHeader.fwLength % AMOTA_PACKET_SIZE; if ( ui32Remainder ) { g_pFlash->flash_read_page((uint32_t)amotasTmpBuf, (uint32_t *)(amotasCb.newFwFlashInfo.addr + amotasCb.fwHeader.fwLength - ui32Remainder), ui32Remainder); am_bootloader_partial_crc32(amotasTmpBuf, ui32Remainder, &ui32CRC); } FLASH_OPERATE(g_pFlash, flash_disable); return (ui32CRC == amotasCb.fwHeader.fwCrc); } //***************************************************************************** // // Update OTA information with Firmware Information. // //***************************************************************************** #if defined(AM_PART_APOLLO3) || defined(AM_PART_APOLLO3P) static void amotas_update_ota(void) { uint32_t *pOtaDesc = (uint32_t *)(OTA_POINTER_LOCATION & ~(AM_HAL_FLASH_PAGE_SIZE - 1)); uint8_t magic = *((uint8_t *)(amotasCb.newFwFlashInfo.addr + 3)); // Initialize OTA descriptor - This should ideally be initiated through a separate command // to facilitate multiple image upgrade in a single reboot // Will need change in the AMOTA app to do so am_hal_ota_init(AM_HAL_FLASH_PROGRAM_KEY, pOtaDesc); // Set OTAPOINTER am_hal_ota_add(AM_HAL_FLASH_PROGRAM_KEY, magic, (uint32_t *)amotasCb.newFwFlashInfo.addr); } #else static void amotas_update_ota(void) { am_multiboot_ota_t otaInfo; am_multiboot_ota_t *pOtaInfo = &otaInfo; uint32_t otaDescAddr; // We do not want to disturb the content of the flash page where OTA descriptor // resides, other than the OTA descriptor itself // This is to avoid needing a separate flash page just for this purpose // It allows the flash page to be shared with some other information if needed uint32_t otaPtrPageAddr = (OTA_POINTER_LOCATION & ~(AM_HAL_FLASH_PAGE_SIZE - 1)); uint32_t otaPtrOffset = OTA_POINTER_LOCATION - otaPtrPageAddr; // Use the temporary accumulation buffer as scrach space // Take a backup of image info memcpy(amotasFlash.writeBuffer, (uint8_t *)otaPtrPageAddr, AM_HAL_FLASH_PAGE_SIZE); pOtaInfo->pui32LinkAddress = (uint32_t*)amotasCb.fwHeader.fwStartAddr; // When security info is present, it is prepended to the image in the blob // Actual image starts at an offset in the blob, and the actual image size // needs to be adjusted accordingly pOtaInfo->secInfoLen = amotasCb.fwHeader.secInfoLen; pOtaInfo->ui32NumBytes = amotasCb.fwHeader.fwLength - amotasCb.fwHeader.secInfoLen; pOtaInfo->ui32ImageCrc = amotasCb.fwHeader.fwCrc; pOtaInfo->pui32ImageAddr = (uint32_t*)(amotasCb.newFwFlashInfo.addr + amotasCb.fwHeader.secInfoLen); pOtaInfo->magicNum = OTA_INFO_MAGIC_NUM; #if (AMOTAS_SUPPORT_EXT_FLASH == 1) if ( amotasCb.fwHeader.storageType == AMOTA_FW_STORAGE_EXTERNAL ) { pOtaInfo->ui32Options = OTA_INFO_OPTIONS_EXT_FLASH; g_pFlash->flash_read_page((uint32_t)amotasTmpBuf, (uint32_t *)amotasCb.newFwFlashInfo.addr, pOtaInfo->secInfoLen); // Store secInfo immediately following the OTA descriptor pOtaInfo->pui32SecInfoPtr = (uint32_t*)(OTA_POINTER_LOCATION + 4 + sizeof(otaInfo)); // Copy the secInfo am_bootloader_write_flash_within_page((uint32_t)pOtaInfo->pui32SecInfoPtr, amotasTmpBuf, pOtaInfo->secInfoLen / 4); } else #endif { pOtaInfo->ui32Options = 0; pOtaInfo->pui32SecInfoPtr = (uint32_t*)(amotasCb.newFwFlashInfo.addr); } if ( amotasCb.fwHeader.fwDataType != 0 ) { pOtaInfo->ui32Options |= OTA_INFO_OPTIONS_DATA; } // Compute CRC of the OTA Descriptor pOtaInfo->ui32Crc = 0; am_bootloader_partial_crc32((uint32_t *)pOtaInfo, sizeof(*pOtaInfo) - 4, &pOtaInfo->ui32Crc); otaDescAddr = OTA_POINTER_LOCATION + 4; // Copy the OTA Pointer memcpy(&amotasFlash.writeBuffer[otaPtrOffset], (uint8_t *)&otaDescAddr, 4); // Copy the OTA descriptor memcpy(&amotasFlash.writeBuffer[otaPtrOffset + 4], (uint8_t *)pOtaInfo, sizeof(otaInfo)); // Write the flash Page am_bootloader_program_flash_page(otaPtrPageAddr, (uint32_t *)amotasFlash.writeBuffer, AM_HAL_FLASH_PAGE_SIZE); } #endif //***************************************************************************** // // Handle the various packet types from the Client // //***************************************************************************** void amotas_packet_handler(eAmotaCommand cmd, uint16_t len, uint8_t *buf) { eAmotaStatus status = AMOTA_STATUS_SUCCESS; uint8_t data[4] = {0}; bool bResult = false; uint32_t ver, fwCrc; ver = fwCrc = 0; BOOLEAN resumeTransfer = false; switch(cmd) { case AMOTA_CMD_FW_HEADER: if ( len < AMOTA_FW_HEADER_SIZE ) { status = AMOTA_STATUS_INVALID_HEADER_INFO; amotas_reply_to_client(cmd, status, NULL, 0); break; } if ( amotasCb.state == AMOTA_STATE_GETTING_FW ) { BT_UNPACK_LE_4_BYTE(&ver, buf + 32); BT_UNPACK_LE_4_BYTE(&fwCrc, buf + 12); if ( ver == amotasCb.fwHeader.version && fwCrc == amotasCb.fwHeader.fwCrc ) { resumeTransfer = true; } } BT_UNPACK_LE_4_BYTE(&amotasCb.fwHeader.encrypted, buf); BT_UNPACK_LE_4_BYTE(&amotasCb.fwHeader.fwStartAddr, buf + 4); BT_UNPACK_LE_4_BYTE(&amotasCb.fwHeader.fwLength, buf + 8); BT_UNPACK_LE_4_BYTE(&amotasCb.fwHeader.fwCrc, buf + 12); BT_UNPACK_LE_4_BYTE(&amotasCb.fwHeader.secInfoLen, buf + 16); BT_UNPACK_LE_4_BYTE(&amotasCb.fwHeader.version, buf + 32); BT_UNPACK_LE_4_BYTE(&amotasCb.fwHeader.fwDataType, buf + 36); BT_UNPACK_LE_4_BYTE(&amotasCb.fwHeader.storageType, buf + 40); if ( resumeTransfer ) { AMOTA_TRC("OTA process start from offset = 0x%x\n", amotasCb.newFwFlashInfo.offset); AMOTA_TRC("beginning of flash addr = 0x%x\n", amotasCb.newFwFlashInfo.addr); } else { AMOTA_TRC("OTA process start from beginning\n"); amotasFlash.bufferIndex = 0; bResult = amotas_set_fw_addr(); if ( bResult == false ) { amotas_reply_to_client(cmd, AMOTA_STATUS_INSUFFICIENT_FLASH, NULL, 0); amotasCb.state = AMOTA_STATE_INIT; return; } amotasCb.state = AMOTA_STATE_GETTING_FW; } #ifdef AMOTA_DEBUG_ON AMOTA_TRC("============= fw header start ===============\n"); AMOTA_TRC("encrypted = 0x%x\n", amotasCb.fwHeader.encrypted); AMOTA_TRC("version = 0x%x\n", amotasCb.fwHeader.version); AMOTA_TRC("fwLength = 0x%x\n", amotasCb.fwHeader.fwLength); AMOTA_TRC("fwCrc = 0x%x\n", amotasCb.fwHeader.fwCrc); AMOTA_TRC("fwStartAddr = 0x%x\n", amotasCb.fwHeader.fwStartAddr); AMOTA_TRC("fwDataType = 0x%x\n", amotasCb.fwHeader.fwDataType); AMOTA_TRC("storageType = 0x%x\n", amotasCb.fwHeader.storageType); AMOTA_TRC("============= fw header end ===============\n"); #endif // AMOTA_DEBUG_ON data[0] = ((amotasCb.newFwFlashInfo.offset) & 0xff); data[1] = ((amotasCb.newFwFlashInfo.offset >> 8) & 0xff); data[2] = ((amotasCb.newFwFlashInfo.offset >> 16) & 0xff); data[3] = ((amotasCb.newFwFlashInfo.offset >> 24) & 0xff); amotas_reply_to_client(cmd, AMOTA_STATUS_SUCCESS, data, sizeof(data)); break; case AMOTA_CMD_FW_DATA: bResult = amotas_write2flash(len, buf, amotasCb.newFwFlashInfo.addr + amotasCb.newFwFlashInfo.offset, ((amotasCb.newFwFlashInfo.offset + len) == amotasCb.fwHeader.fwLength)); if ( bResult == false ) { data[0] = ((amotasCb.newFwFlashInfo.offset) & 0xff); data[1] = ((amotasCb.newFwFlashInfo.offset >> 8) & 0xff); data[2] = ((amotasCb.newFwFlashInfo.offset >> 16) & 0xff); data[3] = ((amotasCb.newFwFlashInfo.offset >> 24) & 0xff); amotas_reply_to_client(cmd, AMOTA_STATUS_FLASH_WRITE_ERROR, data, sizeof(data)); } else { amotasCb.newFwFlashInfo.offset += len; data[0] = ((amotasCb.newFwFlashInfo.offset) & 0xff); data[1] = ((amotasCb.newFwFlashInfo.offset >> 8) & 0xff); data[2] = ((amotasCb.newFwFlashInfo.offset >> 16) & 0xff); data[3] = ((amotasCb.newFwFlashInfo.offset >> 24) & 0xff); amotas_reply_to_client(cmd, AMOTA_STATUS_SUCCESS, data, sizeof(data)); } break; case AMOTA_CMD_FW_VERIFY: if ( amotas_verify_firmware_crc() ) { AMOTA_TRC("crc verify success\n"); amotas_reply_to_client(cmd, AMOTA_STATUS_SUCCESS, NULL, 0); // // Update flash flag page here // amotas_update_ota(); } else { AMOTA_TRC("crc verify failed\n"); amotas_reply_to_client(cmd, AMOTA_STATUS_CRC_ERROR, NULL, 0); } FLASH_OPERATE(g_pFlash, flash_deinit); amotasCb.state = AMOTA_STATE_INIT; g_pFlash = &g_intFlash; break; case AMOTA_CMD_FW_RESET: { API_RESULT retval = API_SUCCESS; AMOTA_TRC("Apollo will reset itself in 500ms.\n"); amotas_reply_to_client(cmd, AMOTA_STATUS_SUCCESS, NULL, 0); // // Delay here to let packet go through the RF before we reset // if ( BT_TIMER_HANDLE_INIT_VAL != amotasCb.disconnectTimer_handle ) { retval = BT_stop_timer (amotasCb.disconnectTimer_handle); AMOTA_TRC ( "[AMOTA]: Stopping Timer with result 0x%04X, timer handle %p\n", retval, amotasCb.disconnectTimer_handle); amotasCb.disconnectTimer_handle = BT_TIMER_HANDLE_INIT_VAL; } retval = BT_start_timer ( &amotasCb.disconnectTimer_handle, DISCONNECT_TIMEOUT_DEFAULT, amotas_disconnect_timer_expired, NULL, 0 ); AMOTA_TRC ( "[AMOTA]: Started Timer with result 0x%04X, timer handle %d\n", retval, amotasCb.disconnectTimer_handle); } break; default: break; } } void appl_amotas_disconnect (void) { // we don't need disconnect timer anymore if ( BT_TIMER_HANDLE_INIT_VAL != amotasCb.disconnectTimer_handle ) { BT_stop_timer (amotasCb.disconnectTimer_handle); amotasCb.disconnectTimer_handle = BT_TIMER_HANDLE_INIT_VAL; } amotasCb.resetTimer_handle = BT_TIMER_HANDLE_INIT_VAL; amotasCb.pkt.offset = 0; amotasCb.pkt.len = 0; amotasCb.pkt.type = AMOTA_CMD_UNKNOWN; } API_RESULT appl_amotas_write_cback ( GATT_DB_HANDLE * handle, ATT_VALUE * value ) { API_RESULT retval = API_SUCCESS; uint8_t dataIdx = 0; uint32_t calDataCrc = 0; #ifdef AMOTA_DEBUG_ON uint16_t i = 0; AMOTA_TRC("============= data arrived start ===============\n"); for (i = 0; i < value->len; i++) { AMOTA_TRC("%x\t", value->val[i]); } AMOTA_TRC("============= data arrived end ===============\n"); #endif if ( amotasCb.pkt.offset == 0 && value->len < AMOTA_HEADER_SIZE_IN_PKT ) { AMOTA_TRC("Invalid packet!!!\n"); amotas_reply_to_client(AMOTA_CMD_FW_HEADER, AMOTA_STATUS_INVALID_PKT_LENGTH, NULL, 0); return retval; } // new packet if ( amotasCb.pkt.offset == 0 ) { BT_UNPACK_LE_2_BYTE(&amotasCb.pkt.len, value->val); amotasCb.pkt.type = (eAmotaCommand) value->val[2]; dataIdx = 3; #ifdef AMOTA_DEBUG_ON AMOTA_TRC("pkt.len = 0x%x\n", amotasCb.pkt.len); AMOTA_TRC("pkt.type = 0x%x\n", amotasCb.pkt.type); #endif if (dataIdx > amotasCb.pkt.len) { AMOTA_TRC("packet length is wrong since it's smaller than 3!"); return retval; } } // make sure we have enough space for new data if ( amotasCb.pkt.offset + value->len - dataIdx > AMOTA_PACKET_SIZE ) { AMOTA_TRC("not enough buffer size!!!\n"); amotas_reply_to_client(amotasCb.pkt.type, AMOTA_STATUS_INSUFFICIENT_BUFFER, NULL, 0); return retval; } // copy new data into buffer and also save crc into it if it's the last frame in a packet // 4 bytes crc is included in pkt length memcpy(amotasCb.pkt.data + amotasCb.pkt.offset, value->val + dataIdx, value->len - dataIdx); amotasCb.pkt.offset += (value->len - dataIdx); // whole packet received if ( amotasCb.pkt.offset >= amotasCb.pkt.len ) { uint32_t peerCrc = 0; // check CRC BT_UNPACK_LE_4_BYTE(&peerCrc, amotasCb.pkt.data + amotasCb.pkt.len - AMOTA_CRC_SIZE_IN_PKT); calDataCrc = AmotaCrc32(0xFFFFFFFFU, amotasCb.pkt.len - AMOTA_CRC_SIZE_IN_PKT, amotasCb.pkt.data); #ifdef AMOTA_DEBUG_ON AMOTA_TRC("calDataCrc = 0x%x\n", calDataCrc); AMOTA_TRC("peerCrc = 0x%x\n", peerCrc); #endif if ( peerCrc != calDataCrc ) { amotas_reply_to_client(amotasCb.pkt.type, AMOTA_STATUS_CRC_ERROR, NULL, 0); // clear pkt amotasCb.pkt.offset = 0; amotasCb.pkt.type = AMOTA_CMD_UNKNOWN; amotasCb.pkt.len = 0; return retval; } amotas_packet_handler(amotasCb.pkt.type, amotasCb.pkt.len - AMOTA_CRC_SIZE_IN_PKT, amotasCb.pkt.data); // clear pkt after handled amotasCb.pkt.offset = 0; amotasCb.pkt.type = AMOTA_CMD_UNKNOWN; amotasCb.pkt.len = 0; } return retval; } #endif /* AMOTAS */