diff --git a/zephcore/Kconfig b/zephcore/Kconfig index 2477f63..3be2266 100644 --- a/zephcore/Kconfig +++ b/zephcore/Kconfig @@ -280,6 +280,17 @@ config ZEPHCORE_BLE_ADV_SLOW_INTERVAL Advertising interval in 0.625ms units. 338 = 211.25ms (Apple-compliant, used after 60s fast window). +config ZEPHCORE_BLE_DFU + bool "Buttonless BLE DFU (legacy Nordic/Adafruit OTA jump)" + default y if SOC_SERIES_NRF52X || SOC_SERIES_NRF52 + help + Register the legacy Nordic DFU service (00001530/00001531) in the + running companion app so a paired phone can trigger the jump into the + Adafruit bootloader's BLE OTA mode without pressing a button. On a write + of 0x01 to the control point the device sets the bootloader GPREGRET + magic (0xA8) and resets; the bootloader then advertises for DFU and the + phone's DFU tool reconnects to flash. nRF52 only. + endmenu # BLE Configuration menu "Linux Companion Transport (native_sim)" diff --git a/zephcore/adapters/ble/ZephyrBLE.cpp b/zephcore/adapters/ble/ZephyrBLE.cpp index bc7ae69..a972e57 100644 --- a/zephcore/adapters/ble/ZephyrBLE.cpp +++ b/zephcore/adapters/ble/ZephyrBLE.cpp @@ -237,6 +237,67 @@ BT_GATT_SERVICE_DEFINE(secure_nus_svc, NULL, secure_nus_rx_write, NULL), ); +#if IS_ENABLED(CONFIG_ZEPHCORE_BLE_DFU) +/* ========== Legacy Nordic/Adafruit buttonless DFU service ========== + * + * Mirrors Adafruit BLEDfu (Arduino MeshCore >=1.15.0) so the same DFU tools + * interoperate: a paired phone writes 0x01 to the control point and the device + * resets into the bootloader's BLE OTA mode. We use the *unbonded* OTA reset + * (GPREGRET 0xA8, same as `start ota`): the bootloader comes up as a fresh DFU + * target and the tool re-scans for it (the legacy buttonless flow). Adafruit's + * 0xB1 bonded-resume path needs SoftDevice peer-data enrollment we can't + * replicate on Zephyr, so the phone makes a fresh connection to the bootloader. + */ +static struct bt_uuid_128 dfu_svc_uuid = BT_UUID_INIT_128( + BT_UUID_128_ENCODE(0x00001530, 0x1212, 0xefde, 0x1523, 0x785feabcd123)); +static struct bt_uuid_128 dfu_ctrl_uuid = BT_UUID_INIT_128( + BT_UUID_128_ENCODE(0x00001531, 0x1212, 0xefde, 0x1523, 0x785feabcd123)); + +static void dfu_jump_work_fn(struct k_work *work); +static K_WORK_DELAYABLE_DEFINE(dfu_jump_work, dfu_jump_work_fn); + +static void dfu_jump_work_fn(struct k_work *work) +{ + ARG_UNUSED(work); + if (ble_cbs && ble_cbs->on_dfu_request) { + ble_cbs->on_dfu_request(); /* sets GPREGRET + resets; never returns */ + } +} + +static ssize_t dfu_ctrl_write(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, uint8_t flags) +{ + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(offset); + ARG_UNUSED(flags); + /* Adafruit BLEDfu jump command: first byte 0x01 (1-2 byte write). */ + if (len >= 1 && ((const uint8_t *)buf)[0] == 0x01) { + LOG_INF("buttonless DFU requested - rebooting to BLE OTA"); + /* Defer so the ATT write response flushes and the DFU tool can + * arm its disconnect/rescan before we reset. */ + k_work_schedule(&dfu_jump_work, K_MSEC(250)); + } + return len; +} + +static void dfu_ctrl_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + ARG_UNUSED(attr); + ARG_UNUSED(value); +} + +BT_GATT_SERVICE_DEFINE(dfu_svc, + BT_GATT_PRIMARY_SERVICE(&dfu_svc_uuid), + BT_GATT_CHARACTERISTIC(&dfu_ctrl_uuid.uuid, + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_WRITE_AUTHEN, + NULL, dfu_ctrl_write, NULL), + BT_GATT_CCC(dfu_ctrl_ccc_changed, + BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN), +); +#endif /* CONFIG_ZEPHCORE_BLE_DFU */ + /* ========== Work items ========== */ static void tx_drain_work_fn(struct k_work *work); diff --git a/zephcore/adapters/ble/ZephyrBLE.h b/zephcore/adapters/ble/ZephyrBLE.h index fdc5507..5774baf 100644 --- a/zephcore/adapters/ble/ZephyrBLE.h +++ b/zephcore/adapters/ble/ZephyrBLE.h @@ -27,6 +27,9 @@ struct ble_callbacks { void (*on_connected)(void); /* BLE disconnected */ void (*on_disconnected)(void); + /* Buttonless DFU control-point write — defer reboot into the + * bootloader's BLE OTA mode. May be NULL (feature disabled). */ + void (*on_dfu_request)(void); }; enum zephcore_iface { diff --git a/zephcore/src/main_companion.cpp b/zephcore/src/main_companion.cpp index 5259b41..ffda59d 100644 --- a/zephcore/src/main_companion.cpp +++ b/zephcore/src/main_companion.cpp @@ -241,11 +241,21 @@ static void ble_on_disconnected(void) ui_notify(UI_EVENT_BLE_DISCONNECTED); } +#if IS_ENABLED(CONFIG_ZEPHCORE_BLE_DFU) +static void ble_on_dfu_request(void) +{ + mesh_reboot_to_ota_dfu(); /* GPREGRET 0xA8 + reset; never returns */ +} +#endif + static const struct ble_callbacks ble_cbs = { .on_rx_frame = ble_on_rx_frame, .on_tx_idle = ble_on_tx_idle, .on_connected = ble_on_connected, .on_disconnected = ble_on_disconnected, +#if IS_ENABLED(CONFIG_ZEPHCORE_BLE_DFU) + .on_dfu_request = ble_on_dfu_request, +#endif }; /* ========== Frame send/receive (mesh ↔ BLE/USB) ========== */