Merge branch 'nimble-ota' of JF/PineTime into develop
commit
82b4ddc25b
@ -0,0 +1,50 @@
|
||||
# Bootloader
|
||||
|
||||
## Bootloader binary
|
||||
The binary comes from https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v4.1.7
|
||||
|
||||
It must be flash at address **0x00** in the internal flash memory.
|
||||
|
||||
Using OpenOCD:
|
||||
|
||||
`
|
||||
program mynewt_nosemi.elf_4.1.7.bin 0
|
||||
`
|
||||
|
||||
## Application firmware image
|
||||
Build the binary compatible with the booloader:
|
||||
|
||||
`
|
||||
make pinetime-mcuboot-app
|
||||
`
|
||||
|
||||
The binary is located in *<build directory>/src/pinetime-mcuboot-app.bin*.
|
||||
|
||||
It must me converted into a MCUBoot image using *imgtool.py* from [MCUBoot](https://github.com/JuulLabs-OSS/mcuboot/tree/master/scripts).
|
||||
|
||||
`
|
||||
imgtool.py create --align 4 --version 1.0.0 --header-size 32 --slot-size 475136 --pad-header <build directory>/src/pinetime-mcuboot-app.bin image.bin
|
||||
`
|
||||
|
||||
The image must be then flashed at address **0x8000** in the internal flash memory.
|
||||
|
||||
Using OpenOCD:
|
||||
|
||||
`
|
||||
program image.bin 0x8000
|
||||
`
|
||||
|
||||
## OTA and DFU
|
||||
Pack the image into a .zip file for the NRF DFU protocol:
|
||||
|
||||
`
|
||||
adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application image.bin dfu.zip
|
||||
`
|
||||
|
||||
Use NRFConnect or dfu.py to upload the zip file to the device:
|
||||
|
||||
`
|
||||
sudo dfu.py -z /home/jf/nrf52/bootloader/dfu.zip -a <pinetime MAC address> --legacy
|
||||
`
|
||||
|
||||
**TODO** : dfu.py
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,136 @@
|
||||
/* Linker script to configure memory regions. */
|
||||
|
||||
SEARCH_DIR(.)
|
||||
GROUP(-lgcc -lc -lnosys)
|
||||
|
||||
MEMORY
|
||||
{
|
||||
FLASH (rx) : ORIGIN = 0x08020, LENGTH = 0x78000
|
||||
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x10000
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = ALIGN(4);
|
||||
.mem_section_dummy_ram :
|
||||
{
|
||||
}
|
||||
.cli_sorted_cmd_ptrs :
|
||||
{
|
||||
PROVIDE(__start_cli_sorted_cmd_ptrs = .);
|
||||
KEEP(*(.cli_sorted_cmd_ptrs))
|
||||
PROVIDE(__stop_cli_sorted_cmd_ptrs = .);
|
||||
} > RAM
|
||||
.fs_data :
|
||||
{
|
||||
PROVIDE(__start_fs_data = .);
|
||||
KEEP(*(.fs_data))
|
||||
PROVIDE(__stop_fs_data = .);
|
||||
} > RAM
|
||||
.log_dynamic_data :
|
||||
{
|
||||
PROVIDE(__start_log_dynamic_data = .);
|
||||
KEEP(*(SORT(.log_dynamic_data*)))
|
||||
PROVIDE(__stop_log_dynamic_data = .);
|
||||
} > RAM
|
||||
.log_filter_data :
|
||||
{
|
||||
PROVIDE(__start_log_filter_data = .);
|
||||
KEEP(*(SORT(.log_filter_data*)))
|
||||
PROVIDE(__stop_log_filter_data = .);
|
||||
} > RAM
|
||||
|
||||
} INSERT AFTER .data;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
.mem_section_dummy_rom :
|
||||
{
|
||||
}
|
||||
.sdh_soc_observers :
|
||||
{
|
||||
PROVIDE(__start_sdh_soc_observers = .);
|
||||
KEEP(*(SORT(.sdh_soc_observers*)))
|
||||
PROVIDE(__stop_sdh_soc_observers = .);
|
||||
} > FLASH
|
||||
.sdh_ble_observers :
|
||||
{
|
||||
PROVIDE(__start_sdh_ble_observers = .);
|
||||
KEEP(*(SORT(.sdh_ble_observers*)))
|
||||
PROVIDE(__stop_sdh_ble_observers = .);
|
||||
} > FLASH
|
||||
.sdh_req_observers :
|
||||
{
|
||||
PROVIDE(__start_sdh_req_observers = .);
|
||||
KEEP(*(SORT(.sdh_req_observers*)))
|
||||
PROVIDE(__stop_sdh_req_observers = .);
|
||||
} > FLASH
|
||||
.sdh_state_observers :
|
||||
{
|
||||
PROVIDE(__start_sdh_state_observers = .);
|
||||
KEEP(*(SORT(.sdh_state_observers*)))
|
||||
PROVIDE(__stop_sdh_state_observers = .);
|
||||
} > FLASH
|
||||
.sdh_stack_observers :
|
||||
{
|
||||
PROVIDE(__start_sdh_stack_observers = .);
|
||||
KEEP(*(SORT(.sdh_stack_observers*)))
|
||||
PROVIDE(__stop_sdh_stack_observers = .);
|
||||
} > FLASH
|
||||
.nrf_queue :
|
||||
{
|
||||
PROVIDE(__start_nrf_queue = .);
|
||||
KEEP(*(.nrf_queue))
|
||||
PROVIDE(__stop_nrf_queue = .);
|
||||
} > FLASH
|
||||
.nrf_balloc :
|
||||
{
|
||||
PROVIDE(__start_nrf_balloc = .);
|
||||
KEEP(*(.nrf_balloc))
|
||||
PROVIDE(__stop_nrf_balloc = .);
|
||||
} > FLASH
|
||||
.cli_command :
|
||||
{
|
||||
PROVIDE(__start_cli_command = .);
|
||||
KEEP(*(.cli_command))
|
||||
PROVIDE(__stop_cli_command = .);
|
||||
} > FLASH
|
||||
.crypto_data :
|
||||
{
|
||||
PROVIDE(__start_crypto_data = .);
|
||||
KEEP(*(SORT(.crypto_data*)))
|
||||
PROVIDE(__stop_crypto_data = .);
|
||||
} > FLASH
|
||||
.pwr_mgmt_data :
|
||||
{
|
||||
PROVIDE(__start_pwr_mgmt_data = .);
|
||||
KEEP(*(SORT(.pwr_mgmt_data*)))
|
||||
PROVIDE(__stop_pwr_mgmt_data = .);
|
||||
} > FLASH
|
||||
.log_const_data :
|
||||
{
|
||||
PROVIDE(__start_log_const_data = .);
|
||||
KEEP(*(SORT(.log_const_data*)))
|
||||
PROVIDE(__stop_log_const_data = .);
|
||||
} > FLASH
|
||||
.log_backends :
|
||||
{
|
||||
PROVIDE(__start_log_backends = .);
|
||||
KEEP(*(SORT(.log_backends*)))
|
||||
PROVIDE(__stop_log_backends = .);
|
||||
} > FLASH
|
||||
.nrf_balloc :
|
||||
{
|
||||
PROVIDE(__start_nrf_balloc = .);
|
||||
KEEP(*(.nrf_balloc))
|
||||
PROVIDE(__stop_nrf_balloc = .);
|
||||
} > FLASH
|
||||
|
||||
} INSERT AFTER .text
|
||||
|
||||
|
||||
INCLUDE "./nrf_common.ld"
|
@ -1,101 +1,340 @@
|
||||
#include "DeviceInformationService.h"
|
||||
#include <Components/Ble/BleController.h>
|
||||
#include <SystemTask/SystemTask.h>
|
||||
#include <cstring>
|
||||
#include "DfuService.h"
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
constexpr ble_uuid16_t DeviceInformationService::manufacturerNameUuid;
|
||||
constexpr ble_uuid16_t DeviceInformationService::modelNumberUuid;
|
||||
constexpr ble_uuid16_t DeviceInformationService::serialNumberUuid;
|
||||
constexpr ble_uuid16_t DeviceInformationService::fwRevisionUuid;
|
||||
constexpr ble_uuid16_t DeviceInformationService::deviceInfoUuid;
|
||||
constexpr ble_uuid16_t DeviceInformationService::hwRevisionUuid;
|
||||
constexpr ble_uuid128_t DfuService::serviceUuid;
|
||||
constexpr ble_uuid128_t DfuService::controlPointCharacteristicUuid;
|
||||
constexpr ble_uuid128_t DfuService::revisionCharacteristicUuid;
|
||||
constexpr ble_uuid128_t DfuService::packetCharacteristicUuid;
|
||||
|
||||
int DeviceInformationCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
||||
auto deviceInformationService = static_cast<DeviceInformationService*>(arg);
|
||||
return deviceInformationService->OnDeviceInfoRequested(conn_handle, attr_handle, ctxt);
|
||||
int DfuServiceCallback(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
||||
auto dfuService = static_cast<DfuService*>(arg);
|
||||
return dfuService->OnServiceData(conn_handle, attr_handle, ctxt);
|
||||
}
|
||||
|
||||
void DeviceInformationService::Init() {
|
||||
ble_gatts_count_cfg(serviceDefinition);
|
||||
ble_gatts_add_svcs(serviceDefinition);
|
||||
}
|
||||
|
||||
|
||||
int DeviceInformationService::OnDeviceInfoRequested(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt) {
|
||||
const char *str;
|
||||
|
||||
switch (ble_uuid_u16(ctxt->chr->uuid)) {
|
||||
case manufacturerNameId:
|
||||
str = manufacturerName;
|
||||
break;
|
||||
case modelNumberId:
|
||||
str = modelNumber;
|
||||
break;
|
||||
case serialNumberId:
|
||||
str = serialNumber;
|
||||
break;
|
||||
case fwRevisionId:
|
||||
str = fwRevision;
|
||||
break;
|
||||
case hwRevisionId:
|
||||
str = hwRevision;
|
||||
break;
|
||||
default:
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
int res = os_mbuf_append(ctxt->om, str, strlen(str));
|
||||
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
|
||||
DeviceInformationService::DeviceInformationService() :
|
||||
DfuService::DfuService(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::Ble& bleController, Pinetime::Drivers::SpiNorFlash& spiNorFlash) :
|
||||
systemTask{systemTask},
|
||||
bleController{bleController},
|
||||
spiNorFlash{spiNorFlash},
|
||||
characteristicDefinition{
|
||||
{
|
||||
.uuid = (ble_uuid_t *) &manufacturerNameUuid,
|
||||
.access_cb = DeviceInformationCallback,
|
||||
.uuid = (ble_uuid_t *) &packetCharacteristicUuid,
|
||||
.access_cb = DfuServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
.flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
|
||||
.val_handle = nullptr,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t *) &modelNumberUuid,
|
||||
.access_cb = DeviceInformationCallback,
|
||||
.uuid = (ble_uuid_t *) &controlPointCharacteristicUuid,
|
||||
.access_cb = DfuServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY,
|
||||
.val_handle = nullptr,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t *) &serialNumberUuid,
|
||||
.access_cb = DeviceInformationCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t *) &fwRevisionUuid,
|
||||
.access_cb = DeviceInformationCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
},
|
||||
{
|
||||
.uuid = (ble_uuid_t *) &hwRevisionUuid,
|
||||
.access_cb = DeviceInformationCallback,
|
||||
.uuid = (ble_uuid_t *) &revisionCharacteristicUuid,
|
||||
.access_cb = DfuServiceCallback,
|
||||
.arg = this,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
.val_handle = &revision,
|
||||
|
||||
},
|
||||
{
|
||||
0
|
||||
}
|
||||
|
||||
},
|
||||
serviceDefinition{
|
||||
serviceDefinition {
|
||||
{
|
||||
/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = (ble_uuid_t *) &deviceInfoUuid,
|
||||
.uuid = (ble_uuid_t *) &serviceUuid,
|
||||
.characteristics = characteristicDefinition
|
||||
},
|
||||
{
|
||||
0
|
||||
},
|
||||
}
|
||||
{
|
||||
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void DfuService::Init() {
|
||||
ble_gatts_count_cfg(serviceDefinition);
|
||||
ble_gatts_add_svcs(serviceDefinition);
|
||||
}
|
||||
|
||||
int DfuService::OnServiceData(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context) {
|
||||
|
||||
ble_gatts_find_chr((ble_uuid_t*)&serviceUuid, (ble_uuid_t*)&packetCharacteristicUuid, nullptr, &packetCharacteristicHandle);
|
||||
ble_gatts_find_chr((ble_uuid_t*)&serviceUuid, (ble_uuid_t*)&controlPointCharacteristicUuid, nullptr, &controlPointCharacteristicHandle);
|
||||
ble_gatts_find_chr((ble_uuid_t*)&serviceUuid, (ble_uuid_t*)&revisionCharacteristicUuid, nullptr, &revisionCharacteristicHandle);
|
||||
|
||||
if(attributeHandle == packetCharacteristicHandle) {
|
||||
if(context->op == BLE_GATT_ACCESS_OP_WRITE_CHR)
|
||||
return WritePacketHandler(connectionHandle, context->om);
|
||||
else return 0;
|
||||
} else if(attributeHandle == controlPointCharacteristicHandle) {
|
||||
if(context->op == BLE_GATT_ACCESS_OP_WRITE_CHR)
|
||||
return ControlPointHandler(connectionHandle, context->om);
|
||||
else return 0;
|
||||
} else if(attributeHandle == revisionCharacteristicHandle) {
|
||||
if(context->op == BLE_GATT_ACCESS_OP_READ_CHR)
|
||||
return SendDfuRevision(context->om);
|
||||
else return 0;
|
||||
} else {
|
||||
NRF_LOG_INFO("[DFU] Unknown Characteristic : %d", attributeHandle);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int DfuService::SendDfuRevision(os_mbuf *om) const {
|
||||
int res = os_mbuf_append(om, &revision, sizeof(revision));
|
||||
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
|
||||
int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf *om) {
|
||||
switch(state) {
|
||||
case States::Start: {
|
||||
softdeviceSize = om->om_data[0] + (om->om_data[1] << 8) + (om->om_data[2] << 16) + (om->om_data[3] << 24);
|
||||
bootloaderSize = om->om_data[4] + (om->om_data[5] << 8) + (om->om_data[6] << 16) + (om->om_data[7] << 24);
|
||||
applicationSize = om->om_data[8] + (om->om_data[9] << 8) + (om->om_data[10] << 16) + (om->om_data[11] << 24);
|
||||
bleController.FirmwareUpdateTotalBytes(applicationSize);
|
||||
NRF_LOG_INFO("[DFU] -> Start data received : SD size : %d, BT size : %d, app size : %d", softdeviceSize, bootloaderSize, applicationSize);
|
||||
|
||||
for(int erased = 0; erased < maxImageSize; erased += 0x1000) {
|
||||
#if 1
|
||||
spiNorFlash.SectorErase(writeOffset + erased);
|
||||
|
||||
auto p = spiNorFlash.ProgramFailed();
|
||||
auto e = spiNorFlash.EraseFailed();
|
||||
NRF_LOG_INFO("[DFU] Erasing sector %d - %d-%d", erased, p, e);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t data[] {16, 1, 1};
|
||||
SendNotification(connectionHandle, data, 3);
|
||||
state = States::Init;
|
||||
}
|
||||
return 0;
|
||||
case States::Init: {
|
||||
uint16_t deviceType = om->om_data[0] + (om->om_data[1] << 8);
|
||||
uint16_t deviceRevision = om->om_data[2] + (om->om_data[3] << 8);
|
||||
uint32_t applicationVersion = om->om_data[4] + (om->om_data[5] << 8) + (om->om_data[6] << 16) + (om->om_data[7] << 24);
|
||||
uint16_t softdeviceArrayLength = om->om_data[8] + (om->om_data[9] << 8);
|
||||
uint16_t sd[softdeviceArrayLength];
|
||||
for(int i = 0; i < softdeviceArrayLength; i++) {
|
||||
sd[i] = om->om_data[10 + (i*2)] + (om->om_data[(i*2)+1] << 8);
|
||||
}
|
||||
uint16_t crc = om->om_data[10 + (softdeviceArrayLength*2)] + (om->om_data[10 + (softdeviceArrayLength*2)] << 8);
|
||||
|
||||
NRF_LOG_INFO("[DFU] -> Init data received : deviceType = %d, deviceRevision = %d, applicationVersion = %d, nb SD = %d, First SD = %d, CRC = %u",
|
||||
deviceType, deviceRevision, applicationVersion, softdeviceArrayLength, sd[0], crc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
case States::Data: {
|
||||
nbPacketReceived++;
|
||||
auto offset = ((nbPacketReceived-1) % nbPacketsToNotify)*20;
|
||||
std::memcpy(tempBuffer + offset, om->om_data, om->om_len);
|
||||
if(firstCrc) {
|
||||
tempCrc = ComputeCrc(om->om_data, om->om_len, NULL);
|
||||
firstCrc = false;
|
||||
}
|
||||
else
|
||||
tempCrc = ComputeCrc(om->om_data, om->om_len, &tempCrc);
|
||||
|
||||
if(nbPacketReceived > 0 && (nbPacketReceived % nbPacketsToNotify) == 0) {
|
||||
#if 1
|
||||
spiNorFlash.Write(writeOffset + ((nbPacketReceived-nbPacketsToNotify)*20), tempBuffer, 200);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bytesReceived += om->om_len;
|
||||
bleController.FirmwareUpdateCurrentBytes(bytesReceived);
|
||||
//NRF_LOG_INFO("[DFU] -> Bytes received : %d in %d packets", bytesReceived, nbPacketReceived);
|
||||
|
||||
|
||||
|
||||
if((nbPacketReceived % nbPacketsToNotify) == 0) {
|
||||
uint8_t data[5]{static_cast<uint8_t>(Opcodes::PacketReceiptNotification),
|
||||
(uint8_t)(bytesReceived&0x000000FFu),(uint8_t)(bytesReceived>>8u), (uint8_t)(bytesReceived>>16u),(uint8_t)(bytesReceived>>24u) };
|
||||
NRF_LOG_INFO("[DFU] -> Send packet notification: %d bytes received",bytesReceived);
|
||||
SendNotification(connectionHandle, data, 5);
|
||||
}
|
||||
if(bytesReceived == applicationSize) {
|
||||
if((nbPacketReceived % nbPacketsToNotify) != 0) {
|
||||
auto remaningPacket = nbPacketReceived % nbPacketsToNotify;
|
||||
uint32_t spiOffset = writeOffset + ((nbPacketReceived-remaningPacket)*20);
|
||||
|
||||
spiNorFlash.Write(writeOffset + ((nbPacketReceived-remaningPacket)*20), tempBuffer, remaningPacket * 20);
|
||||
}
|
||||
if(applicationSize < maxImageSize) {
|
||||
WriteMagicNumber();
|
||||
}
|
||||
|
||||
uint8_t data[3]{static_cast<uint8_t>(Opcodes::Response),
|
||||
static_cast<uint8_t>(Opcodes::ReceiveFirmwareImage),
|
||||
static_cast<uint8_t>(ErrorCodes::NoError)};
|
||||
NRF_LOG_INFO("[DFU] -> Send packet notification : all bytes received! CRC = %u", tempCrc);
|
||||
SendNotification(connectionHandle, data, 3);
|
||||
state = States::Validate;
|
||||
|
||||
Validate();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
default:
|
||||
// Invalid state
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DfuService::ControlPointHandler(uint16_t connectionHandle, os_mbuf *om) {
|
||||
auto opcode = static_cast<Opcodes>(om->om_data[0]);
|
||||
NRF_LOG_INFO("[DFU] -> ControlPointHandler");
|
||||
|
||||
switch(opcode) {
|
||||
case Opcodes::StartDFU: {
|
||||
if(state != States::Idle && state != States::Start) {
|
||||
NRF_LOG_INFO("[DFU] -> Start DFU requested, but we are not in Idle state");
|
||||
return 0;
|
||||
}
|
||||
if(state == States::Start) {
|
||||
NRF_LOG_INFO("[DFU] -> Start DFU requested, but we are already in Start state");
|
||||
return 0;
|
||||
}
|
||||
auto imageType = static_cast<ImageTypes>(om->om_data[1]);
|
||||
if(imageType == ImageTypes::Application) {
|
||||
NRF_LOG_INFO("[DFU] -> Start DFU, mode = Application");
|
||||
state = States::Start;
|
||||
bleController.StartFirmwareUpdate();
|
||||
bleController.FirmwareUpdateTotalBytes(0xffffffffu);
|
||||
bleController.FirmwareUpdateCurrentBytes(0);
|
||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleFirmwareUpdateStarted);
|
||||
return 0;
|
||||
} else {
|
||||
NRF_LOG_INFO("[DFU] -> Start DFU, mode %d not supported!", imageType);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Opcodes::InitDFUParameters: {
|
||||
if (state != States::Init) {
|
||||
NRF_LOG_INFO("[DFU] -> Init DFU requested, but we are not in Init state");
|
||||
return 0;
|
||||
}
|
||||
bool isInitComplete = (om->om_data[1] != 0);
|
||||
NRF_LOG_INFO("[DFU] -> Init DFU parameters %s", isInitComplete ? " complete" : " not complete");
|
||||
|
||||
if (isInitComplete) {
|
||||
uint8_t data[3]{static_cast<uint8_t>(Opcodes::Response),
|
||||
static_cast<uint8_t>(Opcodes::InitDFUParameters),
|
||||
(isInitComplete ? uint8_t{1} : uint8_t{0})};
|
||||
NRF_LOG_INFO("SEND NOTIF : %d %d %d", data[0], data[1], data[2]);
|
||||
SendNotification(connectionHandle, data, 3);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
case Opcodes::PacketReceiptNotificationRequest:
|
||||
nbPacketsToNotify = om->om_data[1];
|
||||
NRF_LOG_INFO("[DFU] -> Receive Packet Notification Request, nb packet = %d", nbPacketsToNotify);
|
||||
return 0;
|
||||
case Opcodes::ReceiveFirmwareImage:
|
||||
if(state != States::Init) {
|
||||
NRF_LOG_INFO("[DFU] -> Receive firmware image requested, but we are not in Start Init");
|
||||
return 0;
|
||||
}
|
||||
NRF_LOG_INFO("[DFU] -> Starting receive firmware");
|
||||
state = States::Data;
|
||||
return 0;
|
||||
case Opcodes::ValidateFirmware: {
|
||||
if(state != States::Validate) {
|
||||
NRF_LOG_INFO("[DFU] -> Validate firmware image requested, but we are not in Data state");
|
||||
return 0;
|
||||
}
|
||||
NRF_LOG_INFO("[DFU] -> Validate firmware");
|
||||
state = States::Validated;
|
||||
uint8_t data[3]{static_cast<uint8_t>(Opcodes::Response),
|
||||
static_cast<uint8_t>(Opcodes::ValidateFirmware),
|
||||
static_cast<uint8_t>(ErrorCodes::NoError)};
|
||||
SendNotification(connectionHandle, data, 3);
|
||||
return 0;
|
||||
}
|
||||
case Opcodes::ActivateImageAndReset:
|
||||
if(state != States::Validated) {
|
||||
NRF_LOG_INFO("[DFU] -> Activate image and reset requested, but we are not in Validated state");
|
||||
return 0;
|
||||
}
|
||||
NRF_LOG_INFO("[DFU] -> Activate image and reset!");
|
||||
bleController.StopFirmwareUpdate();
|
||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleFirmwareUpdateFinished);
|
||||
return 0;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DfuService::SendNotification(uint16_t connectionHandle, const uint8_t *data, const size_t size) {
|
||||
auto *om = ble_hs_mbuf_from_flat(data, size);
|
||||
auto ret = ble_gattc_notify_custom(connectionHandle, controlPointCharacteristicHandle, om);
|
||||
ASSERT(ret == 0);
|
||||
}
|
||||
|
||||
uint16_t DfuService::ComputeCrc(uint8_t const * p_data, uint32_t size, uint16_t const * p_crc)
|
||||
{
|
||||
uint16_t crc = (p_crc == NULL) ? 0xFFFF : *p_crc;
|
||||
|
||||
for (uint32_t i = 0; i < size; i++)
|
||||
{
|
||||
crc = (uint8_t)(crc >> 8) | (crc << 8);
|
||||
crc ^= p_data[i];
|
||||
crc ^= (uint8_t)(crc & 0xFF) >> 4;
|
||||
crc ^= (crc << 8) << 4;
|
||||
crc ^= ((crc & 0xFF) << 4) << 1;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
void DfuService::Validate() {
|
||||
uint32_t chunkSize = 200;
|
||||
int currentOffset = 0;
|
||||
uint16_t crc = 0;
|
||||
|
||||
bool first = true;
|
||||
while(currentOffset < applicationSize) {
|
||||
uint32_t readSize = (applicationSize - currentOffset) > chunkSize ? chunkSize : (applicationSize - currentOffset);
|
||||
|
||||
spiNorFlash.Read(writeOffset + currentOffset, tempBuffer, readSize);
|
||||
if(first) {
|
||||
crc = ComputeCrc(tempBuffer, readSize, NULL);
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
crc = ComputeCrc(tempBuffer, readSize, &crc);
|
||||
currentOffset += readSize;
|
||||
}
|
||||
|
||||
NRF_LOG_INFO("CRC : %u", crc);
|
||||
}
|
||||
|
||||
void DfuService::WriteMagicNumber() {
|
||||
uint32_t magic[4] = {
|
||||
0xf395c277,
|
||||
0x7fefd260,
|
||||
0x0f505235,
|
||||
0x8079b62c,
|
||||
};
|
||||
|
||||
uint32_t offset = writeOffset + (maxImageSize - (4 * sizeof(uint32_t)));
|
||||
spiNorFlash.Write(offset, reinterpret_cast<uint8_t *>(magic), 4 * sizeof(uint32_t));
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
#include <libs/lvgl/lvgl.h>
|
||||
#include "FirmwareUpdate.h"
|
||||
#include "../DisplayApp.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
extern lv_font_t jetbrains_mono_extrabold_compressed;
|
||||
extern lv_font_t jetbrains_mono_bold_20;
|
||||
|
||||
|
||||
FirmwareUpdate::FirmwareUpdate(Pinetime::Applications::DisplayApp *app, Pinetime::Controllers::Ble& bleController) :
|
||||
Screen(app), bleController{bleController} {
|
||||
|
||||
titleLabel = lv_label_create(lv_scr_act(), NULL);
|
||||
lv_label_set_text(titleLabel, "Firmware update");
|
||||
lv_obj_set_auto_realign(titleLabel, true);
|
||||
lv_obj_align(titleLabel, NULL, LV_ALIGN_IN_TOP_MID, 0, 50);
|
||||
|
||||
bar1 = lv_bar_create(lv_scr_act(), NULL);
|
||||
lv_obj_set_size(bar1, 200, 30);
|
||||
lv_obj_align(bar1, NULL, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_bar_set_anim_time(bar1, 10);
|
||||
lv_bar_set_range(bar1, 0, 100);
|
||||
lv_bar_set_value(bar1, 0, LV_ANIM_OFF);
|
||||
|
||||
percentLabel = lv_label_create(lv_scr_act(), NULL);
|
||||
lv_label_set_text(percentLabel, "");
|
||||
lv_obj_set_auto_realign(percentLabel, true);
|
||||
lv_obj_align(percentLabel, bar1, LV_ALIGN_OUT_TOP_MID, 0, 60);
|
||||
}
|
||||
|
||||
FirmwareUpdate::~FirmwareUpdate() {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
bool FirmwareUpdate::Refresh() {
|
||||
float current = bleController.FirmwareUpdateCurrentBytes() / 1024.0f;
|
||||
float total = bleController.FirmwareUpdateTotalBytes() / 1024.0f;
|
||||
int16_t pc = (current / total) * 100.0f;
|
||||
sprintf(percentStr, "%d %%", pc);
|
||||
lv_label_set_text(percentLabel, percentStr);
|
||||
|
||||
lv_bar_set_value(bar1, pc, LV_ANIM_OFF);
|
||||
return running;
|
||||
}
|
||||
|
||||
bool FirmwareUpdate::OnButtonPushed() {
|
||||
running = false;
|
||||
return true;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <Components/Gfx/Gfx.h>
|
||||
#include "Screen.h"
|
||||
#include <bits/unique_ptr.h>
|
||||
#include <libs/lvgl/src/lv_core/lv_style.h>
|
||||
#include <libs/lvgl/src/lv_core/lv_obj.h>
|
||||
#include <Components/Battery/BatteryController.h>
|
||||
#include <Components/Ble/BleController.h>
|
||||
#include "../Fonts/lcdfont14.h"
|
||||
#include "../Fonts/lcdfont70.h"
|
||||
#include "../../Version.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
|
||||
class FirmwareUpdate : public Screen{
|
||||
public:
|
||||
FirmwareUpdate(DisplayApp* app, Pinetime::Controllers::Ble& bleController);
|
||||
~FirmwareUpdate() override;
|
||||
|
||||
bool Refresh() override;
|
||||
bool OnButtonPushed() override;
|
||||
|
||||
private:
|
||||
Pinetime::Controllers::Ble& bleController;
|
||||
lv_obj_t * bar1;
|
||||
lv_obj_t * percentLabel;
|
||||
lv_obj_t * titleLabel;
|
||||
char percentStr[10];
|
||||
bool running = true;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
#include <hal/nrf_gpio.h>
|
||||
#include "Spi.h"
|
||||
|
||||
using namespace Pinetime::Drivers;
|
||||
|
||||
Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) :
|
||||
spiMaster{spiMaster}, pinCsn{pinCsn} {
|
||||
nrf_gpio_cfg_output(pinCsn);
|
||||
nrf_gpio_pin_set(pinCsn);
|
||||
}
|
||||
|
||||
bool Spi::Write(const uint8_t *data, size_t size) {
|
||||
return spiMaster.Write(pinCsn, data, size);
|
||||
}
|
||||
|
||||
bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t *data, size_t dataSize) {
|
||||
return spiMaster.Read(pinCsn, cmd, cmdSize, data, dataSize);
|
||||
}
|
||||
|
||||
void Spi::Sleep() {
|
||||
// TODO sleep spi
|
||||
nrf_gpio_cfg_default(pinCsn);
|
||||
}
|
||||
|
||||
bool Spi::Init() {
|
||||
nrf_gpio_pin_set(pinCsn); /* disable Set slave select (inactive high) */
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Spi::WriteCmdAndBuffer(uint8_t *cmd, size_t cmdSize, uint8_t *data, size_t dataSize) {
|
||||
return spiMaster.WriteCmdAndBuffer(pinCsn, cmd, cmdSize, data, dataSize);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include <FreeRTOS.h>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <task.h>
|
||||
|
||||
#include "BufferProvider.h"
|
||||
#include "SpiMaster.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Drivers {
|
||||
class Spi {
|
||||
public:
|
||||
Spi(SpiMaster& spiMaster, uint8_t pinCsn);
|
||||
Spi(const Spi&) = delete;
|
||||
Spi& operator=(const Spi&) = delete;
|
||||
Spi(Spi&&) = delete;
|
||||
Spi& operator=(Spi&&) = delete;
|
||||
|
||||
bool Init();
|
||||
bool Write(const uint8_t* data, size_t size);
|
||||
bool Read(uint8_t* cmd, size_t cmdSize, uint8_t *data, size_t dataSize);
|
||||
bool WriteCmdAndBuffer(uint8_t* cmd, size_t cmdSize, uint8_t *data, size_t dataSize);
|
||||
void Sleep();
|
||||
void Wakeup();
|
||||
|
||||
private:
|
||||
SpiMaster& spiMaster;
|
||||
uint8_t pinCsn;
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
#include <hal/nrf_gpio.h>
|
||||
#include <libraries/delay/nrf_delay.h>
|
||||
#include <libraries/log/nrf_log.h>
|
||||
#include "SpiNorFlash.h"
|
||||
#include "Spi.h"
|
||||
|
||||
using namespace Pinetime::Drivers;
|
||||
|
||||
SpiNorFlash::SpiNorFlash(Spi& spi) : spi{spi} {
|
||||
|
||||
}
|
||||
|
||||
void SpiNorFlash::Init() {
|
||||
auto id = ReadIdentificaion();
|
||||
NRF_LOG_INFO("[SPI FLASH] Manufacturer : %d, Memory type : %d, memory density : %d", id.manufacturer, id.type, id.density);
|
||||
}
|
||||
|
||||
void SpiNorFlash::Uninit() {
|
||||
|
||||
}
|
||||
|
||||
void SpiNorFlash::Sleep() {
|
||||
|
||||
}
|
||||
|
||||
void SpiNorFlash::Wakeup() {
|
||||
|
||||
}
|
||||
|
||||
SpiNorFlash::Identification SpiNorFlash::ReadIdentificaion() {
|
||||
auto cmd = static_cast<uint8_t>(Commands::ReadIdentification);
|
||||
Identification identification;
|
||||
spi.Read(&cmd, 1, reinterpret_cast<uint8_t *>(&identification), sizeof(Identification));
|
||||
return identification;
|
||||
}
|
||||
|
||||
uint8_t SpiNorFlash::ReadStatusRegister() {
|
||||
auto cmd = static_cast<uint8_t>(Commands::ReadStatusRegister);
|
||||
uint8_t status;
|
||||
spi.Read(&cmd, sizeof(cmd), &status, sizeof(uint8_t));
|
||||
return status;
|
||||
}
|
||||
|
||||
bool SpiNorFlash::WriteInProgress() {
|
||||
return (ReadStatusRegister() & 0x01u) == 0x01u;
|
||||
}
|
||||
|
||||
bool SpiNorFlash::WriteEnabled() {
|
||||
return (ReadStatusRegister() & 0x02u) == 0x02u;
|
||||
}
|
||||
|
||||
uint8_t SpiNorFlash::ReadConfigurationRegister() {
|
||||
auto cmd = static_cast<uint8_t>(Commands::ReadConfigurationRegister);
|
||||
uint8_t status;
|
||||
spi.Read(&cmd, sizeof(cmd), &status, sizeof(uint8_t));
|
||||
return status;
|
||||
}
|
||||
|
||||
void SpiNorFlash::Read(uint32_t address, uint8_t *buffer, size_t size) {
|
||||
static constexpr uint8_t cmdSize = 4;
|
||||
uint8_t cmd[cmdSize] = { static_cast<uint8_t>(Commands::Read), (uint8_t)(address >> 16U), (uint8_t)(address >> 8U),
|
||||
(uint8_t)address };
|
||||
spi.Read(reinterpret_cast<uint8_t *>(&cmd), cmdSize, buffer, size);
|
||||
}
|
||||
|
||||
void SpiNorFlash::WriteEnable() {
|
||||
auto cmd = static_cast<uint8_t>(Commands::WriteEnable);
|
||||
spi.Read(&cmd, sizeof(cmd), nullptr, 0);
|
||||
}
|
||||
|
||||
void SpiNorFlash::SectorErase(uint32_t sectorAddress) {
|
||||
static constexpr uint8_t cmdSize = 4;
|
||||
uint8_t cmd[cmdSize] = { static_cast<uint8_t>(Commands::SectorErase), (uint8_t)(sectorAddress >> 16U), (uint8_t)(sectorAddress >> 8U),
|
||||
(uint8_t)sectorAddress };
|
||||
|
||||
WriteEnable();
|
||||
while(!WriteEnabled()) vTaskDelay(1);
|
||||
|
||||
spi.Read(reinterpret_cast<uint8_t *>(&cmd), cmdSize, nullptr, 0);
|
||||
|
||||
while(WriteInProgress()) vTaskDelay(1);
|
||||
}
|
||||
|
||||
uint8_t SpiNorFlash::ReadSecurityRegister() {
|
||||
auto cmd = static_cast<uint8_t>(Commands::ReadSecurityRegister);
|
||||
uint8_t status;
|
||||
spi.Read(&cmd, sizeof(cmd), &status, sizeof(uint8_t));
|
||||
return status;
|
||||
}
|
||||
|
||||
bool SpiNorFlash::ProgramFailed() {
|
||||
return (ReadSecurityRegister() & 0x20u) == 0x20u;
|
||||
}
|
||||
|
||||
bool SpiNorFlash::EraseFailed() {
|
||||
return (ReadSecurityRegister() & 0x40u) == 0x40u;
|
||||
}
|
||||
|
||||
void SpiNorFlash::Write(uint32_t address, uint8_t *buffer, size_t size) {
|
||||
static constexpr uint8_t cmdSize = 4;
|
||||
|
||||
size_t len = size;
|
||||
uint32_t addr = address;
|
||||
uint8_t* b = buffer;
|
||||
while(len > 0) {
|
||||
uint32_t pageLimit = (addr & ~(pageSize - 1u)) + pageSize;
|
||||
uint32_t toWrite = pageLimit - addr > len ? len : pageLimit - addr;
|
||||
|
||||
uint8_t cmd[cmdSize] = { static_cast<uint8_t>(Commands::PageProgram), (uint8_t)(addr >> 16U), (uint8_t)(addr >> 8U),
|
||||
(uint8_t)addr };
|
||||
|
||||
WriteEnable();
|
||||
while(!WriteEnabled()) vTaskDelay(1);
|
||||
|
||||
spi.WriteCmdAndBuffer(cmd, cmdSize, b, toWrite);
|
||||
|
||||
while(WriteInProgress()) vTaskDelay(1);
|
||||
|
||||
addr += toWrite;
|
||||
b += toWrite;
|
||||
len -= toWrite;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Drivers {
|
||||
class Spi;
|
||||
class SpiNorFlash {
|
||||
public:
|
||||
explicit SpiNorFlash(Spi& spi);
|
||||
SpiNorFlash(const SpiNorFlash&) = delete;
|
||||
SpiNorFlash& operator=(const SpiNorFlash&) = delete;
|
||||
SpiNorFlash(SpiNorFlash&&) = delete;
|
||||
SpiNorFlash& operator=(SpiNorFlash&&) = delete;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t manufacturer = 0;
|
||||
uint8_t type = 0;
|
||||
uint8_t density = 0;
|
||||
} Identification;
|
||||
|
||||
Identification ReadIdentificaion();
|
||||
uint8_t ReadStatusRegister();
|
||||
bool WriteInProgress();
|
||||
bool WriteEnabled();
|
||||
uint8_t ReadConfigurationRegister();
|
||||
void Read(uint32_t address, uint8_t* buffer, size_t size);
|
||||
void Write(uint32_t address, uint8_t *buffer, size_t size);
|
||||
void WriteEnable();
|
||||
void SectorErase(uint32_t sectorAddress);
|
||||
uint8_t ReadSecurityRegister();
|
||||
bool ProgramFailed();
|
||||
bool EraseFailed();
|
||||
|
||||
|
||||
void Init();
|
||||
void Uninit();
|
||||
|
||||
|
||||
void Sleep();
|
||||
void Wakeup();
|
||||
private:
|
||||
enum class Commands : uint8_t {
|
||||
PageProgram = 0x02,
|
||||
Read = 0x03,
|
||||
ReadStatusRegister = 0x05,
|
||||
WriteEnable = 0x06,
|
||||
ReadConfigurationRegister = 0x15,
|
||||
SectorErase = 0x20,
|
||||
ReadSecurityRegister = 0x2B,
|
||||
ReadIdentification = 0x9F,
|
||||
};
|
||||
static constexpr uint16_t pageSize = 256;
|
||||
|
||||
Spi& spi;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue