Program Listing for File MicroBitPartialFlashingService.cpp#
↰ Return to documentation for file (libraries/codal-microbit-v2/source/bluetooth/MicroBitPartialFlashingService.cpp
)
/*
The MIT License (MIT)
Copyright (c) 2016 British Broadcasting Corporation.
This software is provided by Lancaster University by arrangement with the BBC.
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 "MicroBitConfig.h"
#if CONFIG_ENABLED(DEVICE_BLE)
#include "MicroBitPartialFlashingService.h"
#include "MicroBit.h"
#include "nrf_sdm.h"
#include "nrf_dfu_types.h"
#include "crc32.h"
using namespace codal;
const uint8_t MicroBitPartialFlashingService::base_uuid[ 16] =
{ 0xe9,0x7d,0x00,0x00,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 };
const uint16_t MicroBitPartialFlashingService::serviceUUID = 0xd91d;
const uint16_t MicroBitPartialFlashingService::charUUID[ mbbs_cIdxCOUNT] = { 0x3b10 };
uint32_t micropython_fs_start = 0x00;
uint32_t micropython_fs_end = 0x00;
MicroBitPartialFlashingService::MicroBitPartialFlashingService( BLEDevice &_ble, EventModel &_messageBus, MicroBitStorage &_storage) :
messageBus(_messageBus), storage(_storage)
{
// Set up partial flashing characteristic
memclr( characteristicValue, sizeof( characteristicValue));
// Register the base UUID and create the service.
RegisterBaseUUID( base_uuid);
CreateService( serviceUUID);
CreateCharacteristic( mbbs_cIdxCTRL, charUUID[ mbbs_cIdxCTRL],
characteristicValue,
sizeof(characteristicValue), sizeof(characteristicValue),
microbit_propWRITE_WITHOUT | microbit_propNOTIFY);
// Set up listener for SD writing
messageBus.listen( MICROBIT_ID_PARTIAL_FLASHING, MICROBIT_EVT_ANY, this, &MicroBitPartialFlashingService::partialFlashingEvent);
}
void MicroBitPartialFlashingService::onDataWritten(const microbit_ble_evt_write_t *params)
{
// Get data from BLE callback params
uint8_t *data = (uint8_t *)params->data;
if(params->handle == valueHandle( mbbs_cIdxCTRL) && params->len > 0)
{
// Switch CONTROL byte
switch(data[0]){
case REGION_INFO:
{
// Create instance of Memory Map to return info
MicroBitMemoryMap memoryMap;
// Get and set MicroPython FS start/end
// Region with id 3 is MicroPython FS
if(micropython_fs_end == 0x00 &&
memoryMap.memoryMapStore.memoryMap[2].startAddress != 0x00 &&
memoryMap.memoryMapStore.memoryMap[2].regionId == REGION_PYTHON
) {
micropython_fs_start = memoryMap.memoryMapStore.memoryMap[2].startAddress;
micropython_fs_end = memoryMap.memoryMapStore.memoryMap[2].endAddress;
}
uint8_t buffer[18];
// Response:
// Region and Region #
buffer[0] = 0x00;
buffer[1] = data[1];
// Start Address
buffer[2] = (memoryMap.memoryMapStore.memoryMap[data[1]].startAddress & 0xFF000000) >> 24;
buffer[3] = (memoryMap.memoryMapStore.memoryMap[data[1]].startAddress & 0x00FF0000) >> 16;
buffer[4] = (memoryMap.memoryMapStore.memoryMap[data[1]].startAddress & 0x0000FF00) >> 8;
buffer[5] = (memoryMap.memoryMapStore.memoryMap[data[1]].startAddress & 0x000000FF);
// End Address
buffer[6] = (memoryMap.memoryMapStore.memoryMap[data[1]].endAddress & 0xFF000000) >> 24;
buffer[7] = (memoryMap.memoryMapStore.memoryMap[data[1]].endAddress & 0x00FF0000) >> 16;
buffer[8] = (memoryMap.memoryMapStore.memoryMap[data[1]].endAddress & 0x0000FF00) >> 8;
buffer[9] = (memoryMap.memoryMapStore.memoryMap[data[1]].endAddress & 0x000000FF);
// Region Hash
memcpy(&buffer[10], &memoryMap.memoryMapStore.memoryMap[data[1]].hash, 8);
MICROBIT_DEBUG_DMESGF( "REGION_INFO %x - %x",
(unsigned int) memoryMap.memoryMapStore.memoryMap[data[1]].startAddress,
(unsigned int) memoryMap.memoryMapStore.memoryMap[data[1]].endAddress);
// Send BLE Notification
notifyChrValue( mbbs_cIdxCTRL, (const uint8_t *)buffer, 18);
// Reset packet count
packetCount = 0;
blockPacketCount = 0;
blockNum = 0;
offset = 0;
break;
}
case FLASH_DATA:
{
// Process FLASH data packet
flashData(data);
break;
}
case END_OF_TRANSMISSION:
{
/* Start of embedded source isn't always on a page border so client must
* inform the micro:bit that it's the last packet.
* - Write final packet
* The END_OF_TRANSMISSION packet contains no data. Write any data left in the buffer.
*/
MicroBitEvent evt(MICROBIT_ID_PARTIAL_FLASHING, END_OF_TRANSMISSION);
break;
}
case MICROBIT_STATUS:
{
/*
* Return the version of the Partial Flashing Service and the current BLE mode (application / pairing)
*/
uint8_t flashNotificationBuffer[] = {MICROBIT_STATUS, PARTIAL_FLASHING_VERSION, MicroBitBLEManager::manager->getCurrentMode()};
MICROBIT_DEBUG_DMESGF( "MICROBIT_STATUS version %d mode %d", (int)flashNotificationBuffer[1], (int)flashNotificationBuffer[2]);
notifyChrValue( mbbs_cIdxCTRL, (const uint8_t *)flashNotificationBuffer, sizeof(flashNotificationBuffer));
break;
}
case MICROBIT_RESET:
{
/*
* data[1] determines which mode to reset into: MICROBIT_MODE_PAIRING or MICROBIT_MODE_APPLICATION
*/
switch(data[1]) {
case MICROBIT_MODE_PAIRING:
{
MICROBIT_DEBUG_DMESGF( "MICROBIT_RESET pairing");
MicroBitEvent evt(MICROBIT_ID_PARTIAL_FLASHING, MICROBIT_RESET );
break;
}
case MICROBIT_MODE_APPLICATION:
{
MICROBIT_DEBUG_DMESGF( "MICROBIT_RESET application");
microbit_reset();
break;
}
}
break;
}
}
}
}
void MicroBitPartialFlashingService::flashData(uint8_t *data)
{
MICROBIT_DEBUG_DMESGF( "flashData");
MICROBIT_DEBUG_DMESGF( " %x %x %x %x", (int) data[0], (int) data[1], (int) data[2], (int) data[3]);
MICROBIT_DEBUG_DMESGF( " %x %x %x %x", (int) data[4], (int) data[5], (int) data[6], (int) data[7]);
MICROBIT_DEBUG_DMESGF( " %x %x %x %x", (int) data[8], (int) data[9], (int) data[10], (int) data[11]);
MICROBIT_DEBUG_DMESGF( " %x %x %x %x", (int) data[12], (int) data[13], (int) data[14], (int) data[15]);
MICROBIT_DEBUG_DMESGF( " %x %x %x %x", (int) data[16], (int) data[17], (int) data[18], (int) data[19]);
// Receive 16 bytes per packet
// Buffer 4 packets
// When buffer is full trigger partialFlashingEvent
// When write is complete notify app and repeat
// +-----------+---------+---------+----------+
// | 1 Byte | 2 Bytes | 1 Byte | 16 Bytes |
// +-----------+---------+---------+----------+
// | COMMAND | OFFSET | PACKET# | DATA |
// +-----------+---------+---------+----------+
uint8_t packetNum = data[3];
MICROBIT_DEBUG_DMESGF( "flashData packetNum %d packetCount %d", (int) packetNum, (int) packetCount);
if (packetNum != packetCount)
{
if ( packetNum < packetCount ? packetCount - packetNum < 8 : packetNum - packetCount > 248 )
return; // packet is from a previous batch
MICROBIT_DEBUG_DMESGF( "packet error");
uint8_t flashNotificationBuffer[] = {FLASH_DATA, 0xAA};
notifyChrValue( mbbs_cIdxCTRL, (const uint8_t *)flashNotificationBuffer, sizeof(flashNotificationBuffer));
blockPacketCount += 4;
packetCount = blockPacketCount;
blockNum = 0;
return;
}
packetCount++;
// Add to block
memcpy(block + (4*blockNum), data + 4, 16);
MICROBIT_DEBUG_DMESG( "blockNum %d", (int) blockNum);
// Actions
switch(blockNum) {
// blockNum is 0: set up offset
case 0:
{
offset = ((data[1] << 8) | data[2] << 0);
blockNum++;
break;
}
// blockNum is 1: complete the offset
case 1:
{
offset |= ((data[1] << 24) | data[2] << 16);
blockNum++;
break;
}
// blockNum is 3, block is full
case 3:
{
MICROBIT_DEBUG_DMESG( "Fire write event");
// Fire write event
MicroBitEvent evt(MICROBIT_ID_PARTIAL_FLASHING, FLASH_DATA );
// Reset blockNum
blockNum = 0;
blockPacketCount += 4;
break;
}
default:
{
blockNum++;
break;
}
}
}
void MicroBitPartialFlashingService::validateBootloaderSettings()
{
nrf_dfu_settings_t *settings = (nrf_dfu_settings_t *) MICROBIT_BOOTLOADER_SETTINGS;
if (settings->boot_validation_app.type != NO_VALIDATION)
setDefaultBootloaderSettings();
}
/*
* Set bootloader to no validation
*/
void MicroBitPartialFlashingService::setDefaultBootloaderSettings()
{
MicroBitFlash flash;
// make bootloader settings page
nrf_dfu_settings_t settings;
memset( &settings, 0, sizeof( nrf_dfu_settings_t));
// make NO_VALIDATION settings
settings.settings_version = 2;
settings.bank_0.bank_code = NRF_DFU_BANK_VALID_APP;
settings.boot_validation_app.type = NO_VALIDATION;
// make VALIDATE_CRC settings - calculate app size and CRC
//settings.boot_validation_app.type = VALIDATE_CRC;
//settings.bank_0.image_size = offset + 16 * sizeof( uint32_t) - MICROBIT_APP_REGION_START;
//settings.bank_0.image_crc = crc32_compute( (uint8_t*) MICROBIT_APP_REGION_START, settings.bank_0.image_size, NULL);
//memcpy(settings.boot_validation_app.bytes, &settings.bank_0.image_crc, sizeof(uint32_t));
// calculate settings page CRCs
settings.crc = crc32_compute( (const uint8_t *)&settings.settings_version,
offsetof(nrf_dfu_settings_t, init_command) - offsetof(nrf_dfu_settings_t, settings_version),
NULL);
settings.boot_validation_crc = crc32_compute( (const uint8_t *)&settings.boot_validation_softdevice, 3 * sizeof(boot_validation_t), NULL);
uint32_t settingsSizeInWords = ( sizeof( nrf_dfu_settings_t) + sizeof(uint32_t) - 1) / sizeof(uint32_t);
// save settings to flash settings page if different
uint32_t *pSettings = (uint32_t *) MICROBIT_BOOTLOADER_SETTINGS;
if ( memcmp( pSettings, &settings, sizeof( nrf_dfu_settings_t)))
{
flash.erase_page( pSettings);
flash.flash_burn( pSettings, (uint32_t *)&settings, settingsSizeInWords);
}
// save settings to flash settings backup page if different
uint32_t *pBackup = (uint32_t *) MICROBIT_MBR_PARAMS;
if ( memcmp( pBackup, &settings, sizeof( nrf_dfu_settings_t)))
{
flash.erase_page( pBackup);
flash.flash_burn( pBackup, (uint32_t *)&settings, settingsSizeInWords);
}
}
void MicroBitPartialFlashingService::partialFlashingEvent(MicroBitEvent e)
{
MICROBIT_DEBUG_DMESG( "partialFlashingEvent");
MicroBitFlash flash;
switch(e.value){
case FLASH_DATA:
{
MICROBIT_DEBUG_DMESG( "FLASH_DATA offset %x", (unsigned int) offset);
/*
* Set flashIncomplete flag if not already set to boot into BLE mode
* upon a failed flash.
*/
KeyValuePair* flashIncomplete = storage.get("flashIncomplete");
if(flashIncomplete == NULL){
uint8_t flashIncompleteVal = 0x01;
storage.put("flashIncomplete", &flashIncompleteVal, sizeof(flashIncompleteVal));
// Check if FS exists
if(micropython_fs_end != 0x00) {
for(uint32_t *page = (uint32_t *)micropython_fs_start; page < (uint32_t *)(micropython_fs_end); page += (MICROBIT_CODEPAGESIZE / sizeof(uint32_t))) {
// Check if page needs erasing
for(uint32_t i = 0; i < 1024; i++) {
if(*(page + i) != 0xFFFFFFFF) {
DMESG( "Erase page at %x", page);
flash.erase_page(page);
break; // If page has been erased we can skip the remaining bytes
}
}
}
}
}
delete flashIncomplete;
uint32_t *flashPointer = (uint32_t *)(offset);
// If the pointer is on a page boundary check if it needs erasing
if(!((uint32_t)flashPointer % MICROBIT_CODEPAGESIZE)) {
// Check words
for(uint32_t i = 0; i < (MICROBIT_CODEPAGESIZE / sizeof(uint32_t)); i++) {
if(*(flashPointer + i) != 0xFFFFFFFF) {
flash.erase_page(flashPointer);
break; // If page has been erased we can skip the remaining bytes
}
}
}
// Create a pointer to the data block
uint32_t *blockPointer;
blockPointer = block;
// Write to flash
flash.flash_burn(flashPointer, blockPointer, 16);
// Update flash control buffer to send next packet
uint8_t flashNotificationBuffer[] = {FLASH_DATA, 0xFF};
notifyChrValue( mbbs_cIdxCTRL, (const uint8_t *)flashNotificationBuffer, sizeof(flashNotificationBuffer));
break;
}
case END_OF_TRANSMISSION:
{
MICROBIT_DEBUG_DMESG( "END_OF_TRANSMISSION offset %x", (unsigned int) offset);
// Write final packet
uint32_t *blockPointer;
uint32_t *flashPointer = (uint32_t *) offset;
blockPointer = block;
flash.flash_burn(flashPointer, blockPointer, 16);
// Set no validation
setDefaultBootloaderSettings();
MICROBIT_DEBUG_DMESG( "rebooting");
// Once the final packet has been written remove the BLE mode flag and reset
// the micro:bit
storage.remove("flashIncomplete");
// Clear any persistent data that the user may have stored
if (microbit_device_instance)
((MicroBit *)microbit_device_instance)->eraseUserStorage(true);
microbit_reset();
break;
}
case MICROBIT_RESET:
{
MICROBIT_DEBUG_DMESG( "Calling restartInBLEMode");
MicroBitBLEManager::manager->restartInBLEMode();
break;
}
}
}
#endif