mirror of
https://github.com/oltaco/Adafruit_nRF52_Bootloader_OTAFIX.git
synced 2026-10-24 06:25:42 +00:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e49b3fd2e4 | ||
|
|
51f241024e | ||
|
|
241675ea0d | ||
|
|
656585e9d2 | ||
|
|
711e6d7e22 | ||
|
|
54a9e85788 | ||
|
|
5cb23173c6 | ||
|
|
7e69127005 | ||
|
|
02fc9bdeb9 | ||
|
|
db8dc4d720 | ||
|
|
afc721a5d8 |
No files matched your search
@@ -1,88 +1,158 @@
|
||||
# Adafruit nRF52 Bootloader with Enhanced OTA DFU
|
||||
|
||||
# Adafruit nRF52 Bootloader w/ Enhanced OTA DFU
|
||||
## Changes in OTAFIX 2.1
|
||||
|
||||
The main of this fork is to change the behaviour of the bootloader when a DFU fails, along with some other patches that make it work on nRF52840.
|
||||
When this bootloader *detects* an invalid application firmware after a DFU update it will boot to OTA mode instead of UF2/CDC mode. This isn't totally bulletproof (sometimes the device will just hang at the end of DFU) but if your device is difficult to access then this is a lot better than the default behaviour.
|
||||
- **Defaults to OTA DFU mode**
|
||||
When no valid application is present, the bootloader defaults to OTA DFU mode.
|
||||
This prevents devices from becoming stuck in UF2 mode after a failed OTA update.
|
||||
|
||||
- **High-MTU BLE support**
|
||||
Enables larger DFU packets for improved throughput when supported by the client.
|
||||
The Android DFU app and [`dfu.py`](https://github.com/recrof/nrf_dfu_py) support large packets; the iOS DFU app is limited to 20-byte packets.
|
||||
|
||||
- **Lazy flash erase**
|
||||
Flash pages are erased on demand during the transfer instead of upfront, significantly reducing the delay during DFU initialisation before the transfer begins.
|
||||
|
||||
- **Small-packet accumulation**
|
||||
Packets smaller than 64 bytes are combined at the transport layer and written to flash in chunks of up to 240 bytes.
|
||||
This improves OTA performance from iOS devices and other small-packet DFU hosts by reducing flash write overhead.
|
||||
|
||||
- **Automatic application boot after OTA over USB**
|
||||
When connected to a USB host, devices now automatically reboot into the application after a successful OTA update, instead of requiring a manual reset.
|
||||
|
||||
- **Unique BLE advertising names per board**
|
||||
In OTA DFU mode, devices advertise using a board-specific name instead of the generic `AdaDFU`:
|
||||
- **Heltec T114** → `T114_DFU`
|
||||
- **ProMicro NRF52840** → `PROM_DFU`
|
||||
- **T1000e** → `T1KE_DFU`
|
||||
- **WioTracker L1** → `WTL1_DFU`
|
||||
- **RAK 4631** → `4631_DFU`
|
||||
- **RAK WisMesh Tag** → `RTAG_DFU`
|
||||
- **XIAO NRF52 BLE / SENSE** → `XIAO_DFU`
|
||||
|
||||
---
|
||||
#### Boards supported:
|
||||
|
||||
## Boards supported
|
||||
|
||||
- Heltec Automation Mesh Node T114 / HT-nRF5262
|
||||
- Nologo ProMicro NRF52840 (aka SuperMini NRF52840)
|
||||
- Seeed Studio SenseCAP Card Tracker T1000-E
|
||||
- Seeed Studio Wio Tracker L1
|
||||
- Seeed Studio XIAO nRF52840 BLE
|
||||
- Seeed Studio XIAO nRF52840 BLE (and Seeed SenseCAP Solar Node)
|
||||
- Seeed Studio XIAO nRF52840 BLE SENSE
|
||||
- RAK 4631 ([See note](#notes-on-RAK4631-bootloader))
|
||||
- RAK WisMesh Tag (new 28/11/2025)
|
||||
|
||||
Any board already supported by the Adafruit nrf52 bootloader can be added, or if there's another nRF52840-based board you're interested in please raise an issue.
|
||||
Any board already supported by the Adafruit nRF52 bootloader can be added.
|
||||
If there is another nRF52840-based board you are interested in, please raise an issue.
|
||||
|
||||
---
|
||||
#### Installation:
|
||||
|
||||
## Installation
|
||||
|
||||
**IMPORTANT:** If you are running a MeshCore companion firmware or Ripple firmware on your device **you will need to run an erase after flashing a new bootloader**. Use the MeshCore web flasher to do the erase, it will guide you to the correct erase firmware for your device. Other erase firmwares will not work, they will not erase the ExtraFS area.
|
||||
|
||||
The recommended way to install the bootloader is using the UF2 file.
|
||||
Download the UF2 file for your board, enter UF2 mode (usually by double pressing the reset button within 0.5s) and copy the UF2 file across.
|
||||
|
||||
If you have somehow managed to accidentally flash an incorrect bootloader to your device you will likely require flashing a full bootloader and SoftDevice zip package using ``adafruit-nrfutil``
|
||||
The recommended way to install the bootloader is using the UF2 file.
|
||||
Download the UF2 file for your board (they can be found in the releases with filenames beginning with `update-`), enter UF2 mode (usually by double pressing the reset button within 0.5s) and copy the UF2 file across.
|
||||
|
||||
If you have somehow managed to accidentally flash an incorrect bootloader to your device you will likely require flashing a full bootloader and SoftDevice zip package using ``adafruit-nrfutil``
|
||||
|
||||
---
|
||||
#### Recommended settings and notes for doing OTA update:
|
||||
To perform the OTA update you can use either "nRF Connect" ([Android](https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp&hl=en&gl=US)/[iOS](https://apps.apple.com/gb/app/nrf-connect-for-mobile/id1054362403)) or "nRF Device Firmware Update" ([Android](https://play.google.com/store/apps/details?id=no.nordicsemi.android.dfu&hl=en&gl=US)/[iOS](https://apps.apple.com/sa/app/device-firmware-update/id1624454660)). My preference is the "nRF Device Firmware Update" app on Android, but I have no experience with flashing from iOS devices.
|
||||
|
||||
We recommend the following settings for the DFU app.<br/><br/>
|
||||
## Troubleshooting
|
||||
|
||||
### Device does not appear as a USB drive or serial port
|
||||
|
||||
If the device does not show up on your computer after flashing the bootloader or performing an OTA update, it may be **waiting in OTA DFU mode**.
|
||||
|
||||
In **OTAFIX 2.0**, OTA DFU is the default state when no valid application is present.
|
||||
In this mode:
|
||||
- No UF2 drive is exposed
|
||||
- No serial port is available
|
||||
- The device is waiting for an OTA firmware update over BLE
|
||||
|
||||
**What to do:**
|
||||
- Perform an OTA update using a supported DFU app, **or**
|
||||
- Explicitly request UF2/serial mode using **double-reset**.
|
||||
|
||||
This behaviour is intentional and prevents devices from getting stuck in UF2 mode after failed OTA updates.
|
||||
|
||||
---
|
||||
|
||||
### OTA update fails with `Error: Operation Failed`
|
||||
|
||||
If an OTA update consistently fails early with `Error: Operation Failed`, this is often caused by BLE stack incompatibilities when **Request High MTU** is enabled.
|
||||
|
||||
**What to try:**
|
||||
- Experiment with different PRN settings. Try 12, 8, 1, or even off altogether!
|
||||
- Disable **Request High MTU** in your DFU app
|
||||
|
||||
While high MTU significantly improves performance on supported devices, it is not required for a successful OTA update.
|
||||
|
||||
---
|
||||
|
||||
## Recommended OTA DFU settings
|
||||
|
||||
To perform the OTA update you can use **nRF Device Firmware Update**
|
||||
([Android](https://play.google.com/store/apps/details?id=no.nordicsemi.android.dfu&hl=en&gl=US) / [iOS](https://apps.apple.com/sa/app/device-firmware-update/id1624454660))
|
||||
or **nRF Connect**
|
||||
([Android](https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp&hl=en&gl=US) / [iOS](https://apps.apple.com/gb/app/nrf-connect-for-mobile/id1054362403)).
|
||||
|
||||
My preference is the **nRF Device Firmware Update** app.
|
||||
|
||||
For **OTAFIX 2.0**, the following settings are recommended (these may change — feel free to experiment and report your findings):
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td valign="top">
|
||||
|
||||
**Packets Receipt Notification:** OFF
|
||||
**Number of packets:** 8
|
||||
**Packet Receipt Notification (PRN):** ON
|
||||
**Number of packets:** 30
|
||||
**Reboot time:** 0ms
|
||||
**Scan timeout:** 2000ms
|
||||
**Request high MTU:** OFF
|
||||
**Request high MTU:** ON for Android (See notes below) / Not available on iOS
|
||||
**Disable resume:** ON
|
||||
**Prepare object delay:** 400ms
|
||||
**Force Scanning:** ON
|
||||
**Prepare object delay:** 0ms
|
||||
**Force scanning:** ON
|
||||
**Keep bond:** OFF
|
||||
**External MCU DFU:** OFF
|
||||
**External MCU DFU:** OFF
|
||||
|
||||
</td>
|
||||
<td valign="top">
|
||||
|
||||
Click the screenshot to view full.
|
||||
|
||||
<a href="docs/Screenshot_20251114_142334_DFU.jpg">
|
||||
<img src="docs/Screenshot_20251114_142334_DFU.jpg" width="200">
|
||||
</a>
|
||||
**Notes:**
|
||||
- Some Android devices and BLE stacks do not behave well with **Request high MTU** enabled.
|
||||
If the transfer fails early with `ERROR: Operation Failed`, retry with **Request high MTU turned OFF**.
|
||||
- For maximum speed, Packet Receipt Notification can be disabled, and the number of packets increased.
|
||||
Android is generally more tolerant of higher values; on iOS and other small-packet hosts, values above ~60 are not recommended.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
[Recommended settings for versions prior to 2.0 can be found here](docs/oldsettings.md).
|
||||
|
||||
**IMPORTANT:** If you do an OTA update while your device is plugged into a computer the device will update but <U>it will not boot into the new application firmware</u>. It will require a manual reset in order to start. Plugged into a USB charger is fine.
|
||||
**IMPORTANT:**
|
||||
On <u>older versions</u> of the bootloader, performing an OTA update while the device was connected to a computer USB host would complete successfully but **would not automatically boot into the new application firmware**, requiring a manual reset.
|
||||
This issue is fixed in **OTAFIX 2.0**.
|
||||
|
||||
---
|
||||
#### Performing an OTA update on a MeshCore repeater
|
||||
First you will need to login to the repeater and issue the 'start ota' CLI command.
|
||||
|
||||
Next open the nRF Device Firmware Update app, select the appropriate MeshCore firmware zip file for your device, select your device (it will be called ProMicro_OTA/RAK4631_OTA etc), and press start.
|
||||
## OTA update on a MeshCore repeater
|
||||
|
||||
Now cross your fingers and hope for the best!
|
||||
First you will need to login to the repeater and issue the `start ota` CLI command.
|
||||
|
||||
Next, open the nRF Device Firmware Update app, select the appropriate MeshCore firmware zip file for your device, select your device (it will be advertised as `ProMicro_OTA` / `RAK4631_OTA`, etc), and press start.
|
||||
|
||||
---
|
||||
|
||||
## Donations
|
||||
|
||||
----
|
||||
#### Donations:
|
||||
Although it's not necessary, if you find this useful please consider donating to support my work!
|
||||
|
||||
[](https://ko-fi.com/oltaco)
|
||||
|
||||
---
|
||||
#### Notes on RAK4631 bootloader
|
||||
|
||||
## Notes on RAK4631 bootloader
|
||||
|
||||
This version of the RAK4631 bootloader is based on a much newer version (0.9.2) of the Adafruit nRF52 bootloader than what RAK Wireless uses on their official bootloader (0.6.2-11).
|
||||
|
||||
|
||||
I haven't looked to see what changes (if any) that RAK made to the Adafruit bootloader, so I'm not sure if there's any difference but I have tested this bootloader and I haven't found any problems thus far. If you would rather use the original RAK bootloader but with these patches included you can find that [here](https://github.com/oltaco/WisCore_RAK4631_Bootloader/releases).
|
||||
|
||||
|
||||
I haven't looked to see what changes (if any) that RAK made to the Adafruit bootloader, so I'm not sure if there's any difference but I have tested this bootloader and I haven't found any problems thus far. If you would rather use the original RAK bootloader but with these patches included you can find that [here](https://github.com/oltaco/WisCore_RAK4631_Bootloader/releases).
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
These are the recommended settings for OTAFIX-v1.2 and earlier.<br/><br/>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td valign="top">
|
||||
|
||||
**Packets Receipt Notification:** ON
|
||||
**Number of packets:** 8
|
||||
**Reboot time:** 0ms
|
||||
**Scan timeout:** 2000ms
|
||||
**Request high MTU:** OFF
|
||||
**Disable resume:** ON
|
||||
**Prepare object delay:** 400ms
|
||||
**Force Scanning:** ON
|
||||
**Keep bond:** OFF
|
||||
**External MCU DFU:** OFF
|
||||
|
||||
</td>
|
||||
<td valign="top">
|
||||
|
||||
Click the screenshot to view full.
|
||||
|
||||
<a href="Screenshot_20251114_142334_DFU.jpg">
|
||||
<img src="Screenshot_20251114_142334_DFU.jpg" width="200">
|
||||
</a>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <stddef.h>
|
||||
#include "sdk_common.h"
|
||||
|
||||
#define MAX_DFU_PKT_LEN 20 /**< Maximum length (in bytes) of the DFU Packet characteristic. */
|
||||
#define MAX_DFU_PKT_LEN (247 - 3) // MTU - 3 bytes for ATT header /**< Maximum length (in bytes) of the DFU Packet characteristic. */
|
||||
#define PKT_START_DFU_PARAM_LEN 2 /**< Length (in bytes) of the parameters for Packet Start DFU Request. */
|
||||
#define PKT_INIT_DFU_PARAM_LEN 2 /**< Length (in bytes) of the parameters for Packet Init DFU Request. */
|
||||
#define PKT_RCPT_NOTIF_REQ_LEN 3 /**< Length (in bytes) of the Packet Receipt Notification Request. */
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
|
||||
#include "pstorage_platform.h"
|
||||
|
||||
extern uint8_t* dfu_page_erased;
|
||||
extern uint32_t dfu_image_page_count;
|
||||
extern int32_t dfu_pending_erase_page;
|
||||
extern uint32_t dfu_base_address;
|
||||
|
||||
/**@defgroup ps_opcode Persistent Storage Access Operation Codes
|
||||
* @{
|
||||
|
||||
@@ -47,6 +47,15 @@
|
||||
#error No target defined
|
||||
#endif
|
||||
|
||||
#define PENDING_PACKET_SIZE 256 /**< Maximum size of a single pending packet data buffer. */
|
||||
#define PENDING_PACKET_COUNT 8 /**< Maximum number of packets that can be buffered during lazy erase. */
|
||||
|
||||
uint8_t * dfu_page_erased; /**< Pointer to page erased status array, set by DFU layer. */
|
||||
uint32_t dfu_image_page_count; /**< Number of pages in DFU image, set by DFU layer. */
|
||||
uint32_t dfu_base_address; /**< Base address for DFU image, set by DFU layer. */
|
||||
|
||||
static bool lazy_erase_check_and_trigger(pstorage_handle_t * p_dest, pstorage_size_t size, pstorage_size_t offset);
|
||||
static void process_pending_packets(void);
|
||||
|
||||
/**
|
||||
* @brief Application registration information.
|
||||
@@ -93,10 +102,29 @@ typedef struct
|
||||
cmd_queue_element_t cmd[PSTORAGE_CMD_QUEUE_SIZE]; /**< Array to maintain flash access operation details */
|
||||
}cmd_queue_t;
|
||||
|
||||
static cmd_queue_t m_cmd_queue; /**< Flash operation request queue. */
|
||||
static pstorage_module_table_t m_app_table[PSTORAGE_NUM_OF_PAGES]; /**< Registered application information table. */
|
||||
static pstorage_size_t m_next_app_instance; /**< Points to the application module instance that can be allocated next */
|
||||
static pstorage_size_t m_round_val; /**< Round value for multiple round operations. For erase operations, the round value will contain current round counter which is identical to number of pages erased. For store operations, the round value contains current round of operation * SOC_MAX_WRITE_SIZE to ensure each store to the SoC Flash API is within the SoC limit. */
|
||||
/**@brief Structure for buffering packets during lazy page erase. */
|
||||
typedef struct
|
||||
{
|
||||
bool valid; /**< Whether this slot contains valid data. */
|
||||
pstorage_handle_t handle; /**< Storage handle for destination. */
|
||||
uint32_t offset; /**< Offset within the storage block. */
|
||||
uint32_t size; /**< Size of data in bytes. */
|
||||
uint8_t * p_data_orig; /**< Original data pointer for callback. */
|
||||
uint8_t data[PENDING_PACKET_SIZE] __attribute__((aligned(4))); /**< Buffered packet data. */
|
||||
} pending_packet_t;
|
||||
|
||||
static cmd_queue_t m_cmd_queue; /**< Flash operation request queue. */
|
||||
static pstorage_module_table_t m_app_table[PSTORAGE_NUM_OF_PAGES]; /**< Registered application information table. */
|
||||
static pstorage_size_t m_next_app_instance; /**< Points to the application module instance that can be allocated next */
|
||||
static pstorage_size_t m_round_val; /**< Round value for multiple round operations. For erase operations, the round value will contain current round counter which is identical to number of pages erased. For store operations, the round value contains current round of operation * SOC_MAX_WRITE_SIZE to ensure each store to the SoC Flash API is within the SoC limit. */
|
||||
|
||||
static pending_packet_t m_pending_packets[PENDING_PACKET_COUNT]; /**< Ring buffer for pending packets. */
|
||||
static uint8_t m_pending_read_idx; /**< Read index for pending packet ring buffer. */
|
||||
static uint8_t m_pending_write_idx; /**< Write index for pending packet ring buffer. */
|
||||
static uint8_t m_pending_count; /**< Number of pending packets in buffer. */
|
||||
static bool m_lazy_erase_active; /**< True if a lazy erase is in progress. */
|
||||
static uint32_t m_lazy_erase_page; /**< Page index currently being erased. */
|
||||
static bool m_processing_pending; /**< True if processing a pending packet store. */
|
||||
|
||||
/**
|
||||
* @brief Function for processing of commands and issuing flash access request to the SoftDevice.
|
||||
@@ -160,6 +188,82 @@ static void cmd_queue_init(void)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function for initializing the pending packets buffer.
|
||||
*/
|
||||
static void pending_packets_init(void)
|
||||
{
|
||||
for (int i = 0; i < PENDING_PACKET_COUNT; i++)
|
||||
{
|
||||
m_pending_packets[i].valid = false;
|
||||
}
|
||||
m_pending_read_idx = 0;
|
||||
m_pending_write_idx = 0;
|
||||
m_pending_count = 0;
|
||||
m_lazy_erase_active = false;
|
||||
m_lazy_erase_page = 0;
|
||||
m_processing_pending = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function for adding a packet to the pending buffer.
|
||||
*
|
||||
* @param[in] p_handle Pointer to the storage handle.
|
||||
* @param[in] p_data Pointer to the data to buffer.
|
||||
* @param[in] size Size of data in bytes.
|
||||
* @param[in] offset Offset within the storage block.
|
||||
*
|
||||
* @retval true If the packet was successfully added.
|
||||
* @retval false If the buffer is full or size exceeds maximum.
|
||||
*/
|
||||
static bool pending_packet_add(pstorage_handle_t * p_handle, uint8_t * p_data,
|
||||
uint32_t size, uint32_t offset)
|
||||
{
|
||||
if (m_pending_count >= PENDING_PACKET_COUNT || size > PENDING_PACKET_SIZE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pending_packet_t * p = &m_pending_packets[m_pending_write_idx];
|
||||
p->handle = *p_handle;
|
||||
p->offset = offset;
|
||||
p->size = size;
|
||||
p->p_data_orig = p_data;
|
||||
memcpy(p->data, p_data, size);
|
||||
p->valid = true;
|
||||
|
||||
m_pending_write_idx = (m_pending_write_idx + 1) % PENDING_PACKET_COUNT;
|
||||
m_pending_count++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function for getting a pointer to the next pending packet without removing it.
|
||||
*
|
||||
* @return Pointer to the next pending packet, or NULL if buffer is empty.
|
||||
*/
|
||||
static pending_packet_t * pending_packet_peek(void)
|
||||
{
|
||||
if (m_pending_count == 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return &m_pending_packets[m_pending_read_idx];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function for removing the next pending packet from the buffer.
|
||||
*/
|
||||
static void pending_packet_pop(void)
|
||||
{
|
||||
if (m_pending_count > 0)
|
||||
{
|
||||
m_pending_packets[m_pending_read_idx].valid = false;
|
||||
m_pending_read_idx = (m_pending_read_idx + 1) % PENDING_PACKET_COUNT;
|
||||
m_pending_count--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function for enqueueing a flash access operation.
|
||||
@@ -196,6 +300,7 @@ static uint32_t cmd_queue_enqueue(uint8_t opcode,
|
||||
m_cmd_queue.cmd[write_index].storage_addr = (*p_storage_addr);
|
||||
m_cmd_queue.cmd[write_index].size = size;
|
||||
m_cmd_queue.cmd[write_index].offset = offset;
|
||||
m_cmd_queue.count++;
|
||||
retval = NRF_SUCCESS;
|
||||
if (m_cmd_queue.flash_access == false)
|
||||
{
|
||||
@@ -206,7 +311,6 @@ static uint32_t cmd_queue_enqueue(uint8_t opcode,
|
||||
retval = NRF_SUCCESS;
|
||||
}
|
||||
}
|
||||
m_cmd_queue.count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -261,15 +365,132 @@ static void app_notify(uint32_t result, cmd_queue_element_t * p_elem)
|
||||
{
|
||||
pstorage_ntf_cb_t ntf_cb;
|
||||
uint8_t op_code = p_elem->op_code;
|
||||
|
||||
ntf_cb = m_app_table[p_elem->storage_addr.module_id].cb;
|
||||
|
||||
// Indicate result to client.
|
||||
ntf_cb(&p_elem->storage_addr,
|
||||
op_code,
|
||||
result,
|
||||
p_elem->p_data_addr,
|
||||
p_elem->size);
|
||||
if (p_elem->storage_addr.module_id < PSTORAGE_NUM_OF_PAGES)
|
||||
{
|
||||
ntf_cb = m_app_table[p_elem->storage_addr.module_id].cb;
|
||||
if (ntf_cb != NULL)
|
||||
{
|
||||
ntf_cb(&p_elem->storage_addr,
|
||||
op_code,
|
||||
result,
|
||||
p_elem->p_data_addr,
|
||||
p_elem->size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function for notifying application of buffered store completion.
|
||||
*
|
||||
* @param[in] result Result of event being notified.
|
||||
* @param[in] p_handle Pointer to the storage handle.
|
||||
* @param[in] p_data Pointer to the data buffer.
|
||||
* @param[in] size Size of data in bytes.
|
||||
*/
|
||||
static void app_notify_buffered(uint32_t result, pstorage_handle_t * p_handle,
|
||||
uint8_t * p_data, uint32_t size)
|
||||
{
|
||||
if (p_handle->module_id < PSTORAGE_NUM_OF_PAGES)
|
||||
{
|
||||
pstorage_ntf_cb_t ntf_cb = m_app_table[p_handle->module_id].cb;
|
||||
if (ntf_cb != NULL)
|
||||
{
|
||||
ntf_cb(p_handle, PSTORAGE_STORE_OP_CODE, result, p_data, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function for processing packets from the pending buffer.
|
||||
*
|
||||
* @details Attempts to process the next pending packet. If the target page needs
|
||||
* erasing, triggers a lazy erase. Otherwise enqueues the store operation.
|
||||
*/
|
||||
static void process_pending_packets(void)
|
||||
{
|
||||
if (m_processing_pending || m_lazy_erase_active || m_cmd_queue.count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pending_packet_t * p = pending_packet_peek();
|
||||
if (p == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this packet needs an erase first
|
||||
if (lazy_erase_check_and_trigger(&p->handle, p->size, p->offset))
|
||||
{
|
||||
// Erase triggered, don't process this packet yet
|
||||
return;
|
||||
}
|
||||
|
||||
// Page is ready, enqueue the store
|
||||
m_processing_pending = true;
|
||||
|
||||
uint32_t ret = cmd_queue_enqueue(PSTORAGE_STORE_OP_CODE, &p->handle, p->data, p->size, p->offset);
|
||||
|
||||
if (ret != NRF_SUCCESS)
|
||||
{
|
||||
m_processing_pending = false;
|
||||
pending_packet_pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Function for checking and triggering lazy erase if needed.
|
||||
* @param[in] p_dest Destination address where data is to be stored persistently.
|
||||
* @param[in] size Size of data to be stored expressed in bytes.
|
||||
* @param[in] offset Offset in bytes to be applied when writing to the block.
|
||||
* @retval true Lazy erase was triggered.
|
||||
* @retval false No lazy erase was needed.
|
||||
*/
|
||||
static bool lazy_erase_check_and_trigger(pstorage_handle_t * p_handle, pstorage_size_t size, pstorage_size_t offset)
|
||||
{
|
||||
uint32_t target_addr = p_handle->block_id + offset;
|
||||
|
||||
if (target_addr < dfu_base_address || dfu_page_erased == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t rel_addr = target_addr - dfu_base_address;
|
||||
uint32_t page_index = rel_addr / PSTORAGE_FLASH_PAGE_SIZE;
|
||||
|
||||
// Also check end of packet
|
||||
uint32_t end_addr = target_addr + size - 1;
|
||||
uint32_t end_rel_addr = end_addr - dfu_base_address;
|
||||
uint32_t end_page_index = end_rel_addr / PSTORAGE_FLASH_PAGE_SIZE;
|
||||
|
||||
// Check if start page needs erase
|
||||
bool start_needs_erase = (page_index < dfu_image_page_count && !dfu_page_erased[page_index]);
|
||||
|
||||
// Check if end page needs erase (and is different from start page)
|
||||
bool end_needs_erase = (end_page_index != page_index) &&
|
||||
(end_page_index < dfu_image_page_count && !dfu_page_erased[end_page_index]);
|
||||
|
||||
if (!start_needs_erase && !end_needs_erase)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine which page to erase (start page takes priority)
|
||||
uint32_t erase_page = start_needs_erase ? page_index : end_page_index;
|
||||
|
||||
m_lazy_erase_active = true;
|
||||
m_lazy_erase_page = erase_page;
|
||||
|
||||
pstorage_handle_t erase_handle;
|
||||
erase_handle.block_id = dfu_base_address + (erase_page * PSTORAGE_FLASH_PAGE_SIZE);
|
||||
erase_handle.module_id = p_handle->module_id;
|
||||
|
||||
cmd_queue_enqueue(PSTORAGE_CLEAR_OP_CODE, &erase_handle, NULL,
|
||||
PSTORAGE_FLASH_PAGE_SIZE, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -279,7 +500,7 @@ static void app_notify(uint32_t result, cmd_queue_element_t * p_elem)
|
||||
* @param[in] sys_evt System event received.
|
||||
*/
|
||||
void pstorage_sys_event_handler(uint32_t sys_evt)
|
||||
{
|
||||
{
|
||||
uint32_t retval = NRF_SUCCESS;
|
||||
|
||||
// The event shall only be processed if requested by this module.
|
||||
@@ -293,13 +514,19 @@ void pstorage_sys_event_handler(uint32_t sys_evt)
|
||||
{
|
||||
p_cmd = &m_cmd_queue.cmd[m_cmd_queue.rp];
|
||||
m_round_val++;
|
||||
|
||||
|
||||
bool command_finished = ((m_round_val * SOC_MAX_WRITE_SIZE) >= p_cmd->size);
|
||||
|
||||
if (p_cmd->op_code == PSTORAGE_CLEAR_OP_CODE)
|
||||
{
|
||||
command_finished = true;
|
||||
}
|
||||
|
||||
if (command_finished)
|
||||
{
|
||||
uint8_t queue_rp = m_cmd_queue.rp;
|
||||
|
||||
uint8_t completed_op = p_cmd->op_code;
|
||||
|
||||
m_round_val = 0;
|
||||
m_cmd_queue.count--;
|
||||
m_cmd_queue.rp++;
|
||||
@@ -309,24 +536,78 @@ void pstorage_sys_event_handler(uint32_t sys_evt)
|
||||
m_cmd_queue.rp -= PSTORAGE_CMD_QUEUE_SIZE;
|
||||
}
|
||||
|
||||
app_notify(retval, &m_cmd_queue.cmd[queue_rp]);
|
||||
if (m_lazy_erase_active && completed_op == PSTORAGE_CLEAR_OP_CODE)
|
||||
{
|
||||
// Lazy erase completed
|
||||
if (dfu_page_erased != NULL && m_lazy_erase_page < dfu_image_page_count)
|
||||
{
|
||||
dfu_page_erased[m_lazy_erase_page] = 1;
|
||||
}
|
||||
|
||||
// Initialize/free the element as it is now processed.
|
||||
cmd_queue_element_init(queue_rp);
|
||||
m_lazy_erase_active = false;
|
||||
cmd_queue_element_init(queue_rp);
|
||||
|
||||
process_pending_packets();
|
||||
}
|
||||
else if (m_processing_pending && completed_op == PSTORAGE_STORE_OP_CODE)
|
||||
{
|
||||
// Store from pending buffer completed
|
||||
pending_packet_t * p = pending_packet_peek();
|
||||
if (p != NULL)
|
||||
{
|
||||
pstorage_handle_t cb_handle = p->handle;
|
||||
uint8_t* cb_data = p->p_data_orig;
|
||||
uint32_t cb_size = p->size;
|
||||
|
||||
pending_packet_pop();
|
||||
|
||||
cmd_queue_element_init(queue_rp);
|
||||
|
||||
// Clear flag BEFORE callback
|
||||
m_processing_pending = false;
|
||||
|
||||
// Notify application that buffered this buffered store is done
|
||||
app_notify_buffered(retval, &cb_handle, cb_data, cb_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_processing_pending = false;
|
||||
cmd_queue_element_init(queue_rp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
app_notify(retval, &m_cmd_queue.cmd[queue_rp]);
|
||||
|
||||
// Initialize/free the element as it is now processed.
|
||||
cmd_queue_element_init(queue_rp);
|
||||
}
|
||||
}
|
||||
// Schedule any queued flash access operations.
|
||||
retval = cmd_queue_dequeue();
|
||||
|
||||
// Process next queued command OR next pending packet
|
||||
if (m_cmd_queue.count > 0)
|
||||
{
|
||||
retval = cmd_queue_dequeue();
|
||||
}
|
||||
else if (m_pending_count > 0 && !m_processing_pending && !m_lazy_erase_active)
|
||||
{
|
||||
// Queue is empty but we have pending packets - process one
|
||||
process_pending_packets();
|
||||
}
|
||||
|
||||
if (retval != NRF_SUCCESS)
|
||||
{
|
||||
app_notify(retval, &m_cmd_queue.cmd[m_cmd_queue.rp]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case NRF_EVT_FLASH_OPERATION_ERROR:
|
||||
m_lazy_erase_active = false;
|
||||
m_processing_pending = false;
|
||||
app_notify(NRF_ERROR_TIMEOUT, &m_cmd_queue.cmd[m_cmd_queue.rp]);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
// No implementation needed.
|
||||
break;
|
||||
@@ -348,6 +629,11 @@ static uint32_t cmd_process(void)
|
||||
|
||||
retval = NRF_ERROR_FORBIDDEN;
|
||||
|
||||
if (m_cmd_queue.count == 0)
|
||||
{
|
||||
return NRF_SUCCESS;
|
||||
}
|
||||
|
||||
p_cmd = &m_cmd_queue.cmd[m_cmd_queue.rp];
|
||||
|
||||
storage_addr = p_cmd->storage_addr.block_id;
|
||||
@@ -379,7 +665,7 @@ static uint32_t cmd_process(void)
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case PSTORAGE_CLEAR_OP_CODE:
|
||||
{
|
||||
uint32_t page_number;
|
||||
@@ -390,12 +676,12 @@ static uint32_t cmd_process(void)
|
||||
retval = sd_flash_page_erase(page_number);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
// Should never reach here.
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (retval == NRF_SUCCESS)
|
||||
{
|
||||
m_cmd_queue.flash_access = true;
|
||||
@@ -409,7 +695,8 @@ static uint32_t cmd_process(void)
|
||||
uint32_t pstorage_init(void)
|
||||
{
|
||||
cmd_queue_init();
|
||||
|
||||
pending_packets_init();
|
||||
|
||||
m_next_app_instance = 0;
|
||||
m_round_val = 0;
|
||||
|
||||
@@ -451,11 +738,22 @@ uint32_t pstorage_store(pstorage_handle_t * p_dest,
|
||||
pstorage_size_t offset)
|
||||
{
|
||||
// Verify word alignment.
|
||||
if ((!is_word_aligned(p_src)) || (!is_word_aligned(p_src+offset)))
|
||||
if ((!is_word_aligned(p_src)) || (!is_word_aligned((void *)(p_dest->block_id + offset))))
|
||||
{
|
||||
return NRF_ERROR_INVALID_ADDR;
|
||||
}
|
||||
|
||||
if (m_lazy_erase_active || lazy_erase_check_and_trigger(p_dest, size, offset))
|
||||
{
|
||||
// Buffer this packet - erase in progress or just triggered
|
||||
if (!pending_packet_add(p_dest, p_src, size, offset))
|
||||
{
|
||||
return NRF_ERROR_NO_MEM;
|
||||
}
|
||||
return NRF_SUCCESS;
|
||||
}
|
||||
|
||||
// Page already erased, proceed with store
|
||||
return cmd_queue_enqueue(PSTORAGE_STORE_OP_CODE, p_dest, p_src, size, offset);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,12 @@ static void pstorage_callback_handler(pstorage_handle_t * p_handle,
|
||||
case PSTORAGE_CLEAR_OP_CODE:
|
||||
if (m_dfu_state == DFU_STATE_PREPARING)
|
||||
{
|
||||
// Special case for lazy erase - mark first page as erased
|
||||
if (dfu_page_erased != NULL && dfu_image_page_count > 0)
|
||||
{
|
||||
dfu_page_erased[0] = 1;
|
||||
}
|
||||
|
||||
m_functions.cleared();
|
||||
m_dfu_state = DFU_STATE_RDY;
|
||||
if (m_data_pkt_cb != NULL)
|
||||
@@ -80,39 +86,48 @@ static void pstorage_callback_handler(pstorage_handle_t * p_handle,
|
||||
APP_ERROR_CHECK(result);
|
||||
}
|
||||
|
||||
/**@brief Function for preparing of flash before receiving SoftDevice image.
|
||||
/**@brief Function for preparing flash before receiving image.
|
||||
*
|
||||
* @details This function will erase current application area to ensure sufficient amount of
|
||||
* storage for the SoftDevice image. Upon erase complete a callback will be done.
|
||||
* See \ref dfu_bank_prepare_t for further details.
|
||||
* @details For lazy erase mode (OTA), this sets up the tracking array and erases
|
||||
* only the first page to signal readiness. Subsequent pages are erased
|
||||
* on-demand when stores target them.
|
||||
* For non-OTA mode, all pages are erased upfront.
|
||||
*/
|
||||
static void dfu_prepare_func_app_erase(uint32_t image_size)
|
||||
{
|
||||
mp_storage_handle_active = &m_storage_handle_app;
|
||||
mp_storage_handle_active = &m_storage_handle_app;
|
||||
|
||||
// Doing a SoftDevice update thus current application must be cleared to ensure enough space
|
||||
// for new SoftDevice.
|
||||
m_dfu_state = DFU_STATE_PREPARING;
|
||||
m_dfu_state = DFU_STATE_PREPARING;
|
||||
dfu_base_address = m_storage_handle_app.block_id;
|
||||
|
||||
if ( is_ota() )
|
||||
{
|
||||
uint32_t err_code = pstorage_clear(&m_storage_handle_app, m_image_size);
|
||||
APP_ERROR_CHECK(err_code);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t const page_count = NRFX_CEIL_DIV(m_image_size, CODE_PAGE_SIZE);
|
||||
|
||||
for ( uint32_t i = 0; i < page_count; i++ )
|
||||
if ( is_ota() )
|
||||
{
|
||||
uint32_t const addr = DFU_BANK_0_REGION_START + i * CODE_PAGE_SIZE;
|
||||
PRINTF("Erase 0x%08lX\r\n", addr);
|
||||
nrfx_nvmc_page_erase(addr);
|
||||
}
|
||||
// Setup for lazy erase
|
||||
dfu_image_page_count = NRFX_CEIL_DIV(m_image_size, CODE_PAGE_SIZE);
|
||||
|
||||
// invoke complete callback
|
||||
pstorage_callback_handler(&m_storage_handle_app, PSTORAGE_CLEAR_OP_CODE, NRF_SUCCESS, NULL, 0);
|
||||
}
|
||||
if (dfu_image_page_count < 1) dfu_image_page_count = 1; // This should not happen
|
||||
|
||||
static uint8_t dfu_page_erased_static[256];
|
||||
dfu_page_erased = dfu_page_erased_static;
|
||||
memset(dfu_page_erased, 0, dfu_image_page_count);
|
||||
|
||||
uint32_t err_code = pstorage_clear(&m_storage_handle_app, CODE_PAGE_SIZE);
|
||||
APP_ERROR_CHECK(err_code);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t const page_count = NRFX_CEIL_DIV(m_image_size, CODE_PAGE_SIZE);
|
||||
|
||||
for ( uint32_t i = 0; i < page_count; i++ )
|
||||
{
|
||||
uint32_t const addr = DFU_BANK_0_REGION_START + i * CODE_PAGE_SIZE;
|
||||
PRINTF("Erase 0x%08lX\r\n", addr);
|
||||
nrfx_nvmc_page_erase(addr);
|
||||
}
|
||||
|
||||
// invoke complete callback
|
||||
pstorage_callback_handler(&m_storage_handle_app, PSTORAGE_CLEAR_OP_CODE, NRF_SUCCESS, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -234,6 +249,11 @@ uint32_t dfu_init(void)
|
||||
|
||||
m_init_packet_length = 0;
|
||||
m_image_crc = 0;
|
||||
|
||||
// Reset lazy erase state
|
||||
dfu_page_erased = NULL;
|
||||
dfu_image_page_count = 0;
|
||||
dfu_base_address = 0;
|
||||
|
||||
err_code = pstorage_register(&storage_module_param, &m_storage_handle_app);
|
||||
if (err_code != NRF_SUCCESS)
|
||||
@@ -363,8 +383,8 @@ uint32_t dfu_data_pkt_handle(dfu_update_packet_t * p_packet)
|
||||
}
|
||||
else
|
||||
{
|
||||
flash_nrf5x_write(DFU_BANK_0_REGION_START + m_data_received, p_data, data_length, false);
|
||||
pstorage_callback_handler(mp_storage_handle_active, PSTORAGE_STORE_OP_CODE, NRF_SUCCESS, (uint8_t *) p_data, data_length);
|
||||
flash_nrf5x_write(DFU_BANK_0_REGION_START + m_data_received, p_data, data_length, false);
|
||||
pstorage_callback_handler(mp_storage_handle_active, PSTORAGE_STORE_OP_CODE, NRF_SUCCESS, (uint8_t *) p_data, data_length);
|
||||
}
|
||||
|
||||
m_data_received += data_length;
|
||||
@@ -378,8 +398,8 @@ uint32_t dfu_data_pkt_handle(dfu_update_packet_t * p_packet)
|
||||
{
|
||||
if ( !is_ota() ) flash_nrf5x_flush(false);
|
||||
|
||||
// The entire image has been received. Return NRF_SUCCESS.
|
||||
err_code = NRF_SUCCESS;
|
||||
// The entire image has been received. Return NRF_SUCCESS.
|
||||
err_code = NRF_SUCCESS;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
|
||||
#define BLEGAP_EVENT_LENGTH 6
|
||||
#define BLEGATT_ATT_MTU_MAX 23
|
||||
#define BLEGATT_ATT_MTU_MAX 247
|
||||
enum { BLE_CONN_CFG_HIGH_BANDWIDTH = 1 };
|
||||
|
||||
#define DFU_REV_MAJOR 0x00 /** DFU Major revision number to be exposed. */
|
||||
@@ -82,6 +82,16 @@ enum { BLE_CONN_CFG_HIGH_BANDWIDTH = 1 };
|
||||
#define BL_IMAGE_SIZE_OFFSET 4 /**< Offset in start packet for the size information for bootloader. */
|
||||
#define APP_IMAGE_SIZE_OFFSET 8 /**< Offset in start packet for the size information for application. */
|
||||
|
||||
// Accumulator buffer for small packets, to reduce flash write overhead
|
||||
#define ACCUMULATE_THRESHOLD 64 /**< Enable accumulator only if the first packet is <= this size */
|
||||
#define ACCUMULATE_TARGET_SIZE 240 /**< Flush when accumulator reaches this size (word-aligned) */
|
||||
|
||||
static uint8_t m_accum_buf[ACCUMULATE_TARGET_SIZE] __attribute__((aligned(4)));
|
||||
static uint16_t m_accum_len = 0;
|
||||
static bool m_accum_active = false;
|
||||
static uint32_t m_accum_bytes_pending = 0; /**< Bytes accumulated but not yet counted in m_num_of_firmware_bytes_rcvd */
|
||||
static uint32_t m_reported_image_size = 0; /**< Size of the incoming image being received over BLE DFU as reported by the host. */
|
||||
|
||||
/**@brief Packet type enumeration.
|
||||
*/
|
||||
typedef enum
|
||||
@@ -113,6 +123,13 @@ static uint8_t * mp_final_packet;
|
||||
static ble_gap_addr_t const * m_whitelist[1]; /**< List of peers in whitelist (only one) */
|
||||
static ble_gap_id_key_t const * m_gap_ids[1];
|
||||
|
||||
static ble_gap_data_length_params_t m_dl_params = {
|
||||
.max_tx_octets = 251,
|
||||
.max_rx_octets = 251,
|
||||
.max_tx_time_us = BLE_GAP_DATA_LENGTH_AUTO,
|
||||
.max_rx_time_us = BLE_GAP_DATA_LENGTH_AUTO
|
||||
};
|
||||
|
||||
// Adafruit
|
||||
static uint8_t _adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET;
|
||||
|
||||
@@ -326,6 +343,11 @@ static void start_data_process(ble_dfu_t * p_dfu, ble_dfu_evt_t * p_evt)
|
||||
start_packet.bl_image_size = uint32_decode(p_length_data + BL_IMAGE_SIZE_OFFSET);
|
||||
start_packet.app_image_size = uint32_decode(p_length_data + APP_IMAGE_SIZE_OFFSET);
|
||||
|
||||
// Store the reported image size for use by the accumulator logic
|
||||
m_reported_image_size = start_packet.sd_image_size +
|
||||
start_packet.bl_image_size +
|
||||
start_packet.app_image_size;
|
||||
|
||||
err_code = dfu_start_pkt_handle(&update_packet);
|
||||
if (err_code != NRF_SUCCESS)
|
||||
{
|
||||
@@ -390,6 +412,94 @@ static void init_data_process(ble_dfu_t * p_dfu, ble_dfu_evt_t * p_evt)
|
||||
}
|
||||
}
|
||||
|
||||
/**@brief Flush the accumulator buffer to HCI pool for processing
|
||||
*/
|
||||
static void accum_flush(ble_dfu_t * p_dfu)
|
||||
{
|
||||
if (m_accum_len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t err_code;
|
||||
uint32_t length = m_accum_len;
|
||||
uint32_t original_len = m_accum_len; // Before padding
|
||||
|
||||
PRINTF("OTA: Flushing accumulator buffer (%ld bytes)\r\n", length);
|
||||
|
||||
// Word-align the length (pad with 0xFF for flash compatibility)
|
||||
uint32_t aligned_len = (length + 3) & ~3;
|
||||
while (m_accum_len < aligned_len)
|
||||
{
|
||||
m_accum_buf[m_accum_len++] = 0xFF;
|
||||
}
|
||||
length = m_accum_len;
|
||||
|
||||
err_code = hci_mem_pool_rx_produce(length, (void **) &mp_rx_buffer);
|
||||
if (err_code != NRF_SUCCESS)
|
||||
{
|
||||
PRINTF("OTA: hci_mem_pool_rx_produce failed: 0x%08lX\r\n", err_code);
|
||||
dfu_error_notify(p_dfu, err_code);
|
||||
m_accum_len = 0;
|
||||
m_accum_bytes_pending = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(mp_rx_buffer, m_accum_buf, length);
|
||||
|
||||
err_code = hci_mem_pool_rx_data_size_set(length);
|
||||
if (err_code != NRF_SUCCESS)
|
||||
{
|
||||
PRINTF("OTA: hci_mem_pool_rx_data_size_set failed: 0x%08lX\r\n", err_code);
|
||||
dfu_error_notify(p_dfu, err_code);
|
||||
m_accum_len = 0;
|
||||
m_accum_bytes_pending = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
err_code = hci_mem_pool_rx_extract(&mp_rx_buffer, &length);
|
||||
if (err_code != NRF_SUCCESS)
|
||||
{
|
||||
PRINTF("OTA: hci_mem_pool_rx_extract failed: 0x%08lX\r\n", err_code);
|
||||
dfu_error_notify(p_dfu, err_code);
|
||||
m_accum_len = 0;
|
||||
m_accum_bytes_pending = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
dfu_update_packet_t dfu_pkt;
|
||||
dfu_pkt.packet_type = DATA_PACKET;
|
||||
dfu_pkt.params.data_packet.packet_length = length / sizeof(uint32_t);
|
||||
dfu_pkt.params.data_packet.p_data_packet = (uint32_t *)mp_rx_buffer;
|
||||
|
||||
err_code = dfu_data_pkt_handle(&dfu_pkt);
|
||||
|
||||
if (err_code == NRF_SUCCESS)
|
||||
{
|
||||
// All firmware data received - this was the final packet
|
||||
m_num_of_firmware_bytes_rcvd += original_len;
|
||||
mp_final_packet = mp_rx_buffer;
|
||||
PRINTF("OTA: Final packet flushed, total bytes: %ld\r\n", m_num_of_firmware_bytes_rcvd);
|
||||
}
|
||||
else if (err_code == NRF_ERROR_INVALID_LENGTH)
|
||||
{
|
||||
// More data expected - this is the normal case
|
||||
m_num_of_firmware_bytes_rcvd += original_len;
|
||||
}
|
||||
else
|
||||
{
|
||||
PRINTF("OTA: dfu_data_pkt_handle failed: 0x%08lX\r\n", err_code);
|
||||
uint32_t hci_error = hci_mem_pool_rx_consume(mp_rx_buffer);
|
||||
if (hci_error != NRF_SUCCESS)
|
||||
{
|
||||
dfu_error_notify(p_dfu, hci_error);
|
||||
}
|
||||
dfu_error_notify(p_dfu, err_code);
|
||||
}
|
||||
|
||||
m_accum_len = 0;
|
||||
m_accum_bytes_pending = 0;
|
||||
}
|
||||
|
||||
/**@brief Function for processing application data written by the peer to the DFU Packet
|
||||
* Characteristic.
|
||||
@@ -400,8 +510,61 @@ static void init_data_process(ble_dfu_t * p_dfu, ble_dfu_evt_t * p_evt)
|
||||
static void app_data_process(ble_dfu_t * p_dfu, ble_dfu_evt_t * p_evt)
|
||||
{
|
||||
uint32_t err_code;
|
||||
uint16_t pkt_len = p_evt->evt.ble_dfu_pkt_write.len;
|
||||
uint8_t * p_data = p_evt->evt.ble_dfu_pkt_write.p_data;
|
||||
|
||||
if ((p_evt->evt.ble_dfu_pkt_write.len & (sizeof(uint32_t) - 1)) != 0)
|
||||
// Check if this is the first data packet and decide whether to accumulate packets
|
||||
if (m_num_of_firmware_bytes_rcvd == 0 && m_accum_len == 0)
|
||||
{
|
||||
m_accum_active = (pkt_len <= ACCUMULATE_THRESHOLD);
|
||||
PRINTF("OTA: Packet accumulator %s (first packet size %d)\r\n", m_accum_active ? "enabled" : "disabled", pkt_len);
|
||||
}
|
||||
|
||||
if (m_accum_active)
|
||||
{
|
||||
// Flush the accumulator if adding this packet would overflow the buffer
|
||||
if (m_accum_len + pkt_len > ACCUMULATE_TARGET_SIZE)
|
||||
{
|
||||
PRINTF("OTA: Pre-overflow flush of accumulator\r\n");
|
||||
accum_flush(p_dfu);
|
||||
}
|
||||
|
||||
// Add packet to accumulator buffer
|
||||
memcpy(m_accum_buf + m_accum_len, p_data, pkt_len);
|
||||
m_accum_len += pkt_len;
|
||||
m_accum_bytes_pending += pkt_len;
|
||||
|
||||
// Handle PRN - must be based on RECEIVED packets not flushed packets
|
||||
// otherwise host might timeout waiting for notification
|
||||
if (m_pkt_rcpt_notif_enabled)
|
||||
{
|
||||
m_pkt_notif_target_cnt--;
|
||||
|
||||
if (m_pkt_notif_target_cnt == 0)
|
||||
{
|
||||
// Need to flush before sending PRN so byte count is accurate
|
||||
PRINTF("OTA: PRN flush of accumulator\r\n");
|
||||
accum_flush(p_dfu);
|
||||
|
||||
err_code = ble_dfu_pkts_rcpt_notify(p_dfu, m_num_of_firmware_bytes_rcvd);
|
||||
APP_ERROR_CHECK(err_code);
|
||||
|
||||
m_pkt_notif_target_cnt = m_pkt_notif_target;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush the accumulator if we've received the full image in order to trigger completion
|
||||
if ((m_num_of_firmware_bytes_rcvd + m_accum_len) == m_reported_image_size)
|
||||
{
|
||||
accum_flush(p_dfu);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Original logic for large packets (no accumulation)
|
||||
if ((pkt_len & (sizeof(uint32_t) - 1)) != 0)
|
||||
{
|
||||
// Data length is not a multiple of 4 (word size).
|
||||
err_code = ble_dfu_response_send(p_dfu,
|
||||
@@ -411,7 +574,7 @@ static void app_data_process(ble_dfu_t * p_dfu, ble_dfu_evt_t * p_evt)
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t length = p_evt->evt.ble_dfu_pkt_write.len;
|
||||
uint32_t length = pkt_len;
|
||||
|
||||
err_code = hci_mem_pool_rx_produce(length, (void **) &mp_rx_buffer);
|
||||
if (err_code != NRF_SUCCESS)
|
||||
@@ -420,9 +583,7 @@ static void app_data_process(ble_dfu_t * p_dfu, ble_dfu_evt_t * p_evt)
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t * p_data_packet = p_evt->evt.ble_dfu_pkt_write.p_data;
|
||||
|
||||
memcpy(mp_rx_buffer, p_data_packet, length);
|
||||
memcpy(mp_rx_buffer, p_data, length);
|
||||
|
||||
err_code = hci_mem_pool_rx_data_size_set(length);
|
||||
if (err_code != NRF_SUCCESS)
|
||||
@@ -747,6 +908,11 @@ static void on_ble_evt(ble_evt_t * p_ble_evt)
|
||||
case BLE_GAP_EVT_CONNECTED:
|
||||
m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
|
||||
m_is_advertising = false;
|
||||
|
||||
// APP_ERROR_CHECK( sd_ble_gattc_exchange_mtu_request(m_conn_handle, BLEGATT_ATT_MTU_MAX) );
|
||||
|
||||
sd_ble_gap_data_length_update(m_conn_handle, &m_dl_params, NULL);
|
||||
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVT_DISCONNECTED:
|
||||
@@ -888,7 +1054,7 @@ static void on_ble_evt(ble_evt_t * p_ble_evt)
|
||||
case BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST:
|
||||
// Let Softdevice decide the data length
|
||||
// ble_gap_data_length_params_t* param = &evt->evt.gap_evt.params.data_length_update_request.peer_params
|
||||
APP_ERROR_CHECK( sd_ble_gap_data_length_update(m_conn_handle, NULL, NULL) );
|
||||
APP_ERROR_CHECK( sd_ble_gap_data_length_update(m_conn_handle, &m_dl_params, NULL) );
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
|
||||
@@ -1025,6 +1191,9 @@ uint32_t dfu_transport_ble_update_start(void)
|
||||
{
|
||||
uint32_t err_code;
|
||||
|
||||
m_accum_len = 0;
|
||||
m_accum_active = false;
|
||||
|
||||
m_tear_down_in_progress = false;
|
||||
m_pkt_type = PKT_TYPE_INVALID;
|
||||
|
||||
@@ -1180,7 +1349,7 @@ lookup_table_t const _strevt_table =
|
||||
|
||||
void print_ble_event(uint16_t evt_id)
|
||||
{
|
||||
// const char * str = (const char *) lookup_find(&_strevt_table, evt_id);
|
||||
// const char * str = (const char *) lookup_find(&_strevt_table, evt_id);
|
||||
const char * str = NULL;
|
||||
PRINTF("BLE event: ");
|
||||
if ( str == NULL )
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
MCU_SUB_VARIANT = nrf52840
|
||||
CFLAGS += -DDEVICE_NAME='"HT-NRF-OTA"'
|
||||
CFLAGS += -DDEVICE_NAME='"T114_DFU"'
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
MCU_SUB_VARIANT = nrf52840
|
||||
CFLAGS += -DDEVICE_NAME='"PROM_DFU"'
|
||||
@@ -1,3 +1,3 @@
|
||||
MCU_SUB_VARIANT = nrf52840
|
||||
SD_VERSION = 7.3.0
|
||||
|
||||
CFLAGS += -DDEVICE_NAME='"T1KE_DFU"'
|
||||
@@ -1,2 +1,3 @@
|
||||
MCU_SUB_VARIANT = nrf52840
|
||||
SD_VERSION = 7.3.0
|
||||
SD_VERSION = 7.3.0
|
||||
CFLAGS += -DDEVICE_NAME='"WTL1_DFU"'
|
||||
@@ -1 +1,2 @@
|
||||
MCU_SUB_VARIANT = nrf52840
|
||||
CFLAGS += -DDEVICE_NAME='"4631_DFU"'
|
||||
@@ -1 +1,2 @@
|
||||
MCU_SUB_VARIANT = nrf52840
|
||||
CFLAGS += -DDEVICE_NAME='"RTAG_DFU"'
|
||||
@@ -1,2 +1,3 @@
|
||||
MCU_SUB_VARIANT = nrf52840
|
||||
SD_VERSION = 7.3.0
|
||||
SD_VERSION = 7.3.0
|
||||
CFLAGS += -DDEVICE_NAME='"XIAO_DFU"'
|
||||
@@ -1,2 +1,3 @@
|
||||
MCU_SUB_VARIANT = nrf52840
|
||||
SD_VERSION = 7.3.0
|
||||
SD_VERSION = 7.3.0
|
||||
CFLAGS += -DDEVICE_NAME='"XIAO_DFU"'
|
||||
+11
-5
@@ -125,9 +125,9 @@ extern void tusb_hal_nrf_power_event(uint32_t event);
|
||||
// DFU_DBL_RESET magic is used to determined which mode is entered
|
||||
#define APP_ASKS_FOR_SINGLE_TAP_RESET() (*((uint32_t*)(DFU_BANK_0_REGION_START + 0x200)) == 0x87eeb07c)
|
||||
|
||||
// These value must be the same with one in dfu_transport_ble.c
|
||||
// These values must match the values in dfu_transport_ble.c and MAX_DFU_PKT_LEN should match BLEGATT_ATT_MTU_MAX - 3
|
||||
#define BLEGAP_EVENT_LENGTH 6
|
||||
#define BLEGATT_ATT_MTU_MAX 23
|
||||
#define BLEGATT_ATT_MTU_MAX 247
|
||||
enum {
|
||||
BLE_CONN_CFG_HIGH_BANDWIDTH = 1
|
||||
};
|
||||
@@ -235,6 +235,7 @@ static void check_dfu_mode(void) {
|
||||
bool const dfu_skip = (gpregret == DFU_MAGIC_SKIP);
|
||||
|
||||
bool const reason_reset_pin = (NRF_POWER->RESETREAS & POWER_RESETREAS_RESETPIN_Msk) ? true : false;
|
||||
bool const double_reset = ((*dbl_reset_mem) == DFU_DBL_RESET_MAGIC) && reason_reset_pin;
|
||||
|
||||
// start either serial, uf2 or ble
|
||||
bool dfu_start = _ota_dfu || serial_only_dfu || uf2_dfu ||
|
||||
@@ -259,7 +260,7 @@ static void check_dfu_mode(void) {
|
||||
if (!just_start_app && APP_ASKS_FOR_SINGLE_TAP_RESET()) dfu_start = 1;
|
||||
|
||||
// App mode: Double Reset detection or DFU startup for nrf52832
|
||||
if (!(just_start_app || dfu_start || !valid_app)) {
|
||||
if (!(just_start_app || dfu_start)) { // removed !valid_app check to allow double reset even when no app
|
||||
#ifdef NRF52832_XXAA
|
||||
/* Even DFU is not active, we still force an 1000 ms dfu serial mode when startup
|
||||
* to support auto programming from Arduino IDE
|
||||
@@ -285,6 +286,10 @@ static void check_dfu_mode(void) {
|
||||
(*dbl_reset_mem) = 0;
|
||||
}
|
||||
|
||||
if ((dfu_start || !valid_app) && !serial_only_dfu && !uf2_dfu && !double_reset) {
|
||||
_ota_dfu = true; // set default to OTA only when no explicit UF2/serial/dbl-reset
|
||||
}
|
||||
|
||||
// Enter DFU mode accordingly to input
|
||||
if (dfu_start || !valid_app) {
|
||||
if (_ota_dfu) {
|
||||
@@ -312,6 +317,7 @@ static void check_dfu_mode(void) {
|
||||
|
||||
if (_ota_dfu) {
|
||||
sd_softdevice_disable();
|
||||
usb_teardown(); // allow booting to app after ota even if usb is connected
|
||||
} else {
|
||||
usb_teardown();
|
||||
}
|
||||
@@ -370,12 +376,12 @@ static uint32_t ble_stack_init(void) {
|
||||
// Note: Interrupt state (enabled, forwarding) is not work properly if not enable ble
|
||||
sd_ble_enable(&ram_start);
|
||||
|
||||
#if 0
|
||||
// #if 0
|
||||
ble_opt_t opt;
|
||||
varclr(&opt);
|
||||
opt.common_opt.conn_evt_ext.enable = 1; // enable Data Length Extension
|
||||
sd_ble_opt_set(BLE_COMMON_OPT_CONN_EVT_EXT, &opt);
|
||||
#endif
|
||||
// #endif
|
||||
|
||||
return NRF_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user