summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py (renamed from ports/stm32/boards/NUCLEO_WB55/rfcore.py)119
-rw-r--r--ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py547
-rwxr-xr-xports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py79
3 files changed, 630 insertions, 115 deletions
diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py
index cfe315605..4dbead0ac 100644
--- a/ports/stm32/boards/NUCLEO_WB55/rfcore.py
+++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py
@@ -27,85 +27,16 @@
# mechanism in the WB55, and works with the memory layout configured in
# ports/stm32/rfcore.c -- i.e. it expects that rfcore_init() has been run.
-# At this stage this is useful for debugging, but can be extended to support
-# FUS/WS firmware updates.
# e.g.
# ../../tools/pyboard.py --device /dev/ttyACM0 boards/NUCLEO_WB55/rfcore.py
# to print out SRAM2A, register state and FUS/WS info.
+#
+# The `stm` module provides some helper functions to access rfcore functionality.
+# See rfcore_firmware.py for more information.
from machine import mem8, mem16, mem32
-import time, struct
import stm
-
-def addressof(buf):
- assert type(buf) is bytearray
- return mem32[id(buf) + 12]
-
-
-class Flash:
- FLASH_KEY1 = 0x45670123
- FLASH_KEY2 = 0xCDEF89AB
-
- def wait_not_busy(self):
- while mem32[stm.FLASH + stm.FLASH_SR] & 1 << 16:
- machine.idle()
-
- def unlock(self):
- mem32[stm.FLASH + stm.FLASH_KEYR] = Flash.FLASH_KEY1
- mem32[stm.FLASH + stm.FLASH_KEYR] = Flash.FLASH_KEY2
-
- def lock(self):
- mem32[stm.FLASH + stm.FLASH_CR] = 1 << 31 # LOCK
-
- def erase_page(self, page):
- print("erase", page)
- assert 0 <= page <= 255 # 1MiB range (4k page)
- self.wait_not_busy()
- cr = page << 3 | 1 << 1 # PNB # PER
- mem32[stm.FLASH + stm.FLASH_CR] = cr
- mem32[stm.FLASH + stm.FLASH_CR] = cr | 1 << 16 # STRT
- self.wait_not_busy()
- mem32[stm.FLASH + stm.FLASH_CR] = 0
-
- def write(self, addr, buf):
- assert len(buf) % 4 == 0
- self.wait_not_busy()
- cr = 1 << 0 # PG
- mem32[stm.FLASH + stm.FLASH_CR] = cr
- buf_addr = addressof(buf)
- off = 0
- while off < len(buf):
- mem32[addr + off] = mem32[buf_addr + off]
- off += 4
- if off % 8 == 0:
- self.wait_not_busy()
- if off % 8:
- mem32[addr + off] = 0
- self.wait_not_busy()
- mem32[stm.FLASH + stm.FLASH_CR] = 0
-
-
-def copy_file_to_flash(filename, addr):
- flash = Flash()
- flash.unlock()
- try:
- with open(filename, "rb") as f:
- buf = bytearray(4096)
- while 1:
- sz = f.readinto(buf)
- if sz == 0:
- break
- print("write", hex(addr), sz)
- flash.erase_page((addr - 0x08000000) // 4096)
- print("done e")
- flash.write(addr, buf)
- print("done")
- addr += 4096
- finally:
- flash.lock()
-
-
SRAM2A_BASE = const(0x2003_0000)
# for vendor OGF
@@ -205,49 +136,6 @@ def ipcc_init():
print("BLE: 0x%08x 0x%08x 0x%08x" % (BLE_CMD_BUF, BLE_CS_BUF, BLE_EVT_QUEUE))
-def tl_list_init(addr):
- mem32[addr] = addr # next
- mem32[addr + 4] = addr # prev
-
-
-def tl_list_append(head, n):
- sram2a_dump(1024)
- print("Appending 0x%08x to 0x%08x" % (head, n))
- # item->next = head
- mem32[n] = head
- # item->prev = head->prev
- mem32[n + 4] = mem32[head + 4]
- # head->prev->next = item
- mem32[mem32[head + 4]] = n
- # head->prev = item
- mem32[head + 4] = n
-
-
-def tl_list_unlink(n):
- # next = item->next
- next = mem32[n]
- # prev = item->prev
- prev = mem32[n + 4]
- # prev->next = item->next
- mem32[prev] = next
- # item->next->prev = prev
- mem32[next + 4] = prev
-
- return next
-
-
-def tl_list_dump(head):
- print(
- "list(%08x, %08x, %08x):" % (head, mem32[head] & 0xFFFFFFFF, mem32[head + 4] & 0xFFFFFFFF),
- end="",
- )
- cur = mem32[head]
- while cur != head:
- print(" %08x" % (cur & 0xFFFFFFFF), end="")
- cur = mem32[cur]
- print()
-
-
def fus_active():
return get_ipcc_table_word(TABLE_DEVICE_INFO, 0) == MAGIC_FUS_ACTIVE
@@ -346,3 +234,4 @@ sram2a_dump(264)
ipcc_init()
info()
dev_info()
+ipcc_state()
diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py
new file mode 100644
index 000000000..53c96a552
--- /dev/null
+++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py
@@ -0,0 +1,547 @@
+# This file is part of the MicroPython project, http://micropython.org/
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2020 Damien P. George
+# Copyright (c) 2020 Jim Mussared
+#
+# 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.
+
+# This script provides helpers for working with the FUS/WS firmware on the WB55.
+# It can be frozen into the MicroPython firmware (via manifest.py)
+#
+# The current FUS and WS firmware version and state can be queried via the
+# `stm` module, e.g.
+# stm.rfcore_status() (returns the first word of the device info table)
+# stm.rfcore_fw_version(id) (returns a 5-tuple indicating fw version; id is: 0=FUS, 1=WS)
+# stm.rfcore_sys_hci(ogf, ocf, cmd_buf) (synchronously execute HCI command on SYS channel)
+#
+# To perform a firmware update:
+#
+# 1. Generate "obfuscated" binary images using rfcore_makefirmware.py
+# ./boards/NUCLEO_WB55/rfcore_makefirmware.py ~/src/github.com/STMicroelectronics/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/ /tmp
+# This will generate /tmp/{fus_102,fus_110,ws_ble_hci}.bin
+#
+# 2. Copy required files to the device filesystem.
+# In general, it's always safe to copy all three files and the updater will
+# figure out what needs to be done. This is the recommended option.
+# However, if you already have the latest FUS (1.1.0) installed, then just the
+# WS firmware is required.
+# If a FUS binary is present, then the existing WS will be removed so it's a good
+# idea to always include the WS binary if updating FUS.
+# Note that a WS binary will not be installed unless FUS 1.1.0 is installed.
+#
+# 3. Ensure boot.py calls `rfcore_firmware.resume()`.
+# The WB55 will reset several times during the firmware update process, so this
+# script manages the update state using RTC backup registers.
+# `rfcore_firmware.resume()` will continue the update operation on startup to
+# resume any in-progress update operation, and either trigger another reset, or
+# return 0 to indicate that the operation completed successfully, or a reason
+# code (see REASON_* below) to indicate failure.
+#
+# 4. Call rfcore_firmware.check_for_updates() to start the update process.
+# The device will then immediately reboot and when the firmware update completes,
+# the status will be returned from rfcore_firmware.resume(). See the REASON_ codes below.
+# You can use the built-in stm.rfcore_fw_version() to query the installed version
+# from your application code.
+
+import struct, os
+import machine, stm
+from micropython import const
+
+_OGF_VENDOR = const(0x3F)
+
+_OCF_FUS_GET_STATE = const(0x52)
+_OCF_FUS_FW_UPGRADE = const(0x54)
+_OCF_FUS_FW_DELETE = const(0x55)
+_OCF_FUS_START_WS = const(0x5A)
+_OCF_BLE_INIT = const(0x66)
+
+_HCI_KIND_VENDOR_RESPONSE = const(0x11)
+
+
+# The firmware updater will search all of flash for the image to install, so
+# it's important that the file doesn't exist anywhere on the filesystem and
+# that the updater only finds the version that we copy into the reserved area.
+# Otherwise it will find matching headers/footers in the flash filesystem and
+# get confused leading to either "FUS_STATE_IMG_NOT_AUTHENTIC" or (worse)
+# corrupting the FUS.
+# See footnote [1] referenced by Table 9 in AN5185 - Rev 4 -- the address
+# passed to FUS_FW_UPGRADE is ignored (implying that it must be searching the
+# flash). This requires that the firmware files have been pre-processed by
+# rfcore_makefirmware.py and this key must match the one there.
+_OBFUSCATION_KEY = const(0x0573B55AA)
+
+# On boards using the internal flash filesystem, this must match the
+# `_flash_fs_end` symbol defined by the linker script (boards/stm32wb55xg.ld).
+# We erase everything from here until the start of the secure area (defined by
+# SFSA) just to ensure that no other fragments of firmware files are left
+# behind. On boards with external flash, this just needs to ensure that it
+# includes any regions that may contain partial firmware data.
+# This is non-const so it can be override.
+STAGING_AREA_START = 0x80C0000
+
+# First word of device info table indicating FUS state (returned by `stm.rfcore_status()`).
+_MAGIC_FUS_ACTIVE = const(0xA94656B9) # AN5185
+_MAGIC_IPCC_MEM_INCORRECT = const(0x3DE96F61) # # AN5185
+
+# Argument to `stm.rfcore_fw_version()`.
+_FW_VERSION_FUS = const(0)
+_FW_VERSION_WS = const(1)
+
+# No firmware update in progress. Boot normally.
+_STATE_IDLE = const(0)
+
+# A previous firmware update failed. Will return reason code from resume().
+_STATE_FAILED = const(1)
+
+# Trying to get into the FUS. Keep issuing GET_STATE until the FUS is active.
+_STATE_WAITING_FOR_FUS = const(2)
+
+# Trying to get into the WS. Keep issuing START_WS until the WS is active (or fails).
+_STATE_WAITING_FOR_WS = const(3)
+
+# FW_DELETE has been issued. Waiting for the WS version to report zero.
+_STATE_DELETING_WS = const(4)
+
+# Flash copy has started for FUS/WS. If a reboot occurs, then fail.
+_STATE_COPYING_FUS = const(5)
+_STATE_COPYING_WS = const(6)
+
+# Flash write fully completed, ready for install.
+_STATE_COPIED_FUS = const(7)
+_STATE_COPIED_WS = const(8)
+
+# Check for next update to perform.
+# Either we've just gotten into the FUS, or the first update in a sequence
+# has completed. (e.g. FUS done, now do WS).
+_STATE_CHECK_UPDATES = const(9)
+
+# Installation has started, keep polling GET_STATE.
+_STATE_INSTALLING_WS = const(10)
+_STATE_INSTALLING_FUS = const(11)
+
+# Update completed successfully.
+REASON_OK = const(0)
+# The device reset during flash copy. Possibly WS still installed.
+REASON_FLASH_COPY_FAILED = const(1)
+# Unable to start the WS after firmware update.
+REASON_NO_WS = const(2)
+# Copying FUS image to staging area caused FUS to fail.
+REASON_FLASH_FUS_BAD_STATE = const(3)
+# Copying WS image to staging area caused FUS to fail.
+REASON_FLASH_WS_BAD_STATE = const(4)
+# Cannot get into the FUS. Perhaps rfcore misconfigured.
+REASON_FUS_NOT_RESPONDING = const(5)
+# After a FUS install, unable to get back to the FUS.
+REASON_FUS_NOT_RESPONDING_AFTER_FUS = const(6)
+# After a WS install, unable to get back to the FUS.
+REASON_FUS_NOT_RESPONDING_AFTER_WS = const(7)
+# Unable to query rfcore version/active.
+REASON_RFCORE_NOT_CONFIGURED = const(8)
+# The WS deletion didn't have any effect.
+REASON_WS_STILL_PRESENT = const(9)
+# FUS refused to delete the WS.
+REASON_WS_DELETION_FAILED = const(10)
+# FUS returned a specific code for a FUS update.
+# See AN5185 Rev 4, Table 12. Reason between 0x00-0x11 will be added.
+REASON_FUS_VENDOR = const(0x10)
+# FUS returned a specific code for a WS update. Values as for the FUS update.
+REASON_WS_VENDOR = const(0x30)
+
+# FUS 1.0.2 must be installed before FUS 1.1.0 can be installed.
+# A factory Nucleo board has FUS (0, 5, 3, 0, 0) and WS (0, 5, 1, 0, 0).
+_FUS_VERSION_102 = (1, 0, 2, 0, 0)
+_FUS_VERSION_110 = (1, 1, 0, 0, 0)
+_PATH_FUS_102 = "fus_102.bin"
+_PATH_FUS_110 = "fus_110.bin"
+_PATH_WS_BLE_HCI = "ws_ble_hci.bin"
+
+# This address is correct for versions up to v1.8 (assuming existing firmware deleted).
+# Note any address from the end of the filesystem to the SFSA would be fine, but if
+# the FUS is fixed in the future to use the specified address then these are the "correct"
+# ones.
+_ADDR_FUS = 0x080EC000
+_ADDR_WS_BLE_HCI = 0x080DC000
+
+
+def log(msg, *args, **kwargs):
+ print("[rfcore update]", msg.format(*args, **kwargs))
+
+
+class _Flash:
+ _FLASH_KEY1 = 0x45670123
+ _FLASH_KEY2 = 0xCDEF89AB
+
+ def wait_not_busy(self):
+ while machine.mem32[stm.FLASH + stm.FLASH_SR] & 1 << 16:
+ machine.idle()
+
+ def unlock(self):
+ machine.mem32[stm.FLASH + stm.FLASH_KEYR] = _Flash._FLASH_KEY1
+ machine.mem32[stm.FLASH + stm.FLASH_KEYR] = _Flash._FLASH_KEY2
+
+ def lock(self):
+ machine.mem32[stm.FLASH + stm.FLASH_CR] = 1 << 31 # LOCK
+
+ def erase_page(self, page):
+ assert 0 <= page <= 255 # 1MiB range (4k page)
+ self.wait_not_busy()
+ cr = page << 3 | 1 << 1 # PNB # PER
+ machine.mem32[stm.FLASH + stm.FLASH_CR] = cr
+ machine.mem32[stm.FLASH + stm.FLASH_CR] = cr | 1 << 16 # STRT
+ self.wait_not_busy()
+ machine.mem32[stm.FLASH + stm.FLASH_CR] = 0
+
+ def write(self, addr, buf, sz, key=0):
+ assert sz % 4 == 0
+ self.wait_not_busy()
+ cr = 1 << 0 # PG
+ machine.mem32[stm.FLASH + stm.FLASH_CR] = cr
+ off = 0
+ while off < sz:
+ v = (buf[off]) | (buf[off + 1] << 8) | (buf[off + 2] << 16) | (buf[off + 3] << 24)
+ machine.mem32[addr + off] = v ^ key
+ off += 4
+ if off % 8 == 0:
+ self.wait_not_busy()
+ if off % 8:
+ machine.mem32[addr + off] = 0
+ self.wait_not_busy()
+ machine.mem32[stm.FLASH + stm.FLASH_CR] = 0
+
+
+def _copy_file_to_flash(filename, addr):
+ flash = _Flash()
+ flash.unlock()
+ try:
+ # Erase the entire staging area in flash.
+ erase_addr = STAGING_AREA_START
+ sfr_sfsa = machine.mem32[stm.FLASH + stm.FLASH_SFR] & 0xFF
+ erase_limit = 0x08000000 + sfr_sfsa * 4096
+ while erase_addr < erase_limit:
+ flash.erase_page((erase_addr - 0x08000000) // 4096)
+ erase_addr += 4096
+
+ # Write the contents of the firmware (note flash.write will apply the
+ # XOR de-obfuscation).
+ with open(filename, "rb") as f:
+ buf = bytearray(4096)
+
+ while 1:
+ sz = f.readinto(buf)
+ if sz == 0:
+ break
+ flash.write(addr, buf, sz, _OBFUSCATION_KEY)
+ addr += 4096
+
+ finally:
+ flash.lock()
+
+
+def _parse_vendor_response(data):
+ assert len(data) >= 7
+ assert data[0] == _HCI_KIND_VENDOR_RESPONSE
+ assert data[1] == 0x0E
+ # assert data[3] == 0xff # "Num HCI" -- docs say 0xff, but we see 0x01
+ op = (data[5] << 8) | data[4]
+ return (op >> 10, op & 0x3FF, data[6], data[7] if len(data) > 7 else 0)
+
+
+def _run_sys_hci_cmd(ogf, ocf, buf=b""):
+ try:
+ ogf_out, ocf_out, status, result = _parse_vendor_response(
+ stm.rfcore_sys_hci(ogf, ocf, buf)
+ )
+ except OSError:
+ # Timeout or FUS not active.
+ return (0xFF, 0xFF)
+ assert ogf_out == ogf
+ assert ocf_out == ocf
+ return (status, result)
+
+
+def fus_get_state():
+ return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_GET_STATE)
+
+
+def fus_is_idle():
+ return fus_get_state() == (0, 0)
+
+
+def fus_start_ws():
+ return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_START_WS)
+
+
+def _fus_fwdelete():
+ return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_FW_DELETE)
+
+
+def _fus_run_fwupgrade(addr):
+ # Note: Address is ignored by the FUS (see comments above).
+ return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_FW_UPGRADE, struct.pack("<I", addr))
+
+
+# Get/set current state/reason to RTC Backup Domain.
+# Using the second- and third-last registers (17, 18) as the final one (19)
+# is reserved by powerctrl.c for restoring the frequency.
+# Can be overridden if necessary.
+REG_RTC_STATE = stm.RTC + stm.RTC_BKP18R
+REG_RTC_REASON = stm.RTC + stm.RTC_BKP17R
+
+
+def _read_state():
+ return machine.mem32[REG_RTC_STATE]
+
+
+def _write_state(state):
+ machine.mem32[REG_RTC_STATE] = state
+
+
+def _read_failure_reason():
+ return machine.mem32[REG_RTC_REASON]
+
+
+def _write_failure_state(reason):
+ machine.mem32[REG_RTC_REASON] = reason
+ _write_state(_STATE_FAILED)
+ return reason
+
+
+# Check for the presence of a given file and attempt to start installing it.
+def _stat_and_start_copy(path, addr, copying_state, copied_state):
+ try:
+ os.stat(path)
+ except OSError:
+ log("{} not found", path)
+ return False
+
+ log("{} update is available", path)
+ if sum(stm.rfcore_fw_version(_FW_VERSION_WS)):
+ # There was some WS firmware already installed. Need to remove that
+ # before copying to flash (both FUS or WS copy require this).
+ log("Removing existing WS firmware")
+ _write_state(_STATE_DELETING_WS)
+ _fus_fwdelete()
+ else:
+ log("Copying {} to flash", path)
+ # Mark that the flash write has started. Any failure should result in an overall failure.
+ _write_state(copying_state) # Either _STATE_COPYING_FUS or _STATE_COPYING_WS
+ _copy_file_to_flash(path, addr)
+ log("Copying complete")
+ # The entire write has completed successfully, start the install.
+ _write_state(copied_state) # Either _STATE_COPIED_FUS or _STATE_COPIED_WS
+
+ return True
+
+
+# This should be called in boot.py to resume any in-progress update.
+# If there's nothing to do, it will return 0 and the app can continue as normal.
+# If a previous update has failed, then it will return the failure reason.
+# Otherwise it will attempt to continue the update from where it left off.
+def resume():
+ log("Checking firmware update progress...")
+
+ if stm.rfcore_status() == _MAGIC_IPCC_MEM_INCORRECT:
+ return _write_failure_state(REASON_RFCORE_NOT_CONFIGURED)
+
+ while True:
+ _STATE_id = _read_state()
+
+ if _STATE_id == _STATE_IDLE:
+ log("Firmware update complete")
+ return 0
+
+ elif _STATE_id == _STATE_FAILED:
+ log("Firmware update failed")
+ return _read_failure_reason()
+
+ # Keep calling GET_STATE until error or FUS.
+ elif _STATE_id == _STATE_WAITING_FOR_FUS:
+ log("Querying FUS state")
+ status, result = fus_get_state()
+ log("FUS state: {} {}", status, result)
+
+ if status == 0xFF and result == 0xFF:
+ _write_failure_state(REASON_FUS_NOT_RESPONDING)
+ elif status != 0:
+ log("Operation in progress. Re-querying FUS state")
+ elif stm.rfcore_status() == _MAGIC_FUS_ACTIVE:
+ log("FUS active")
+ _write_state(_STATE_CHECK_UPDATES)
+
+ # Keep trying to start the WS until !fus_active() (or error).
+ elif _STATE_id == _STATE_WAITING_FOR_WS:
+ if stm.rfcore_status() != _MAGIC_FUS_ACTIVE:
+ log("WS active")
+ _write_state(_STATE_IDLE)
+ # Need to force a reset otherwise BLE will fail if FUS has changed.
+ machine.reset()
+ else:
+ log("Starting WS")
+ status, result = fus_start_ws()
+ if status != 0:
+ log("Can't start WS")
+ log("WS version: {}", stm.rfcore_fw_version(_FW_VERSION_WS))
+ _write_failure_state(REASON_NO_WS)
+
+ # Sequence the FUS 1.0.2 -> FUS 1.1.0 -> WS (depending on what's available).
+ elif _STATE_id == _STATE_CHECK_UPDATES:
+ log("Checking for updates")
+ fus_version = stm.rfcore_fw_version(_FW_VERSION_FUS)
+ log("FUS version {}", fus_version)
+ if fus_version < _FUS_VERSION_102:
+ log("Factory FUS detected")
+ if _stat_and_start_copy(
+ _PATH_FUS_102, _ADDR_FUS, _STATE_COPYING_FUS, _STATE_COPIED_FUS
+ ):
+ continue
+ elif fus_version >= _FUS_VERSION_102 and fus_version < _FUS_VERSION_110:
+ log("FUS 1.0.2 detected")
+ if _stat_and_start_copy(
+ _PATH_FUS_110, _ADDR_FUS, _STATE_COPYING_FUS, _STATE_COPIED_FUS
+ ):
+ continue
+ else:
+ log("FUS is up-to-date")
+
+ if fus_version >= _FUS_VERSION_110:
+ if _stat_and_start_copy(
+ _PATH_WS_BLE_HCI, _ADDR_WS_BLE_HCI, _STATE_COPYING_WS, _STATE_COPIED_WS
+ ):
+ continue
+ else:
+ log("No WS updates available")
+ else:
+ # Don't attempt to install WS if we're running an old FUS.
+ log("Need latest FUS to install WS")
+
+ # Attempt to go back to WS.
+ # Either this will fail (because WS was removed due to FUS install), or
+ # this whole thing was a no-op and we should be fine to restart WS.
+ _write_state(_STATE_WAITING_FOR_WS)
+
+ # This shouldn't happen - the flash write should always complete and
+ # move straight onto the COPIED state. Failure here indicates that
+ # the rfcore is misconfigured or the WS firmware was not deleted first.
+ elif _STATE_id == _STATE_COPYING_FUS or _STATE_id == _STATE_COPYING_WS:
+ log("Flash copy failed mid-write")
+ _write_failure_state(REASON_FLASH_COPY_FAILED)
+
+ # Flash write completed, we should immediately see GET_STATE return 0,0
+ # so we can start the FUS install.
+ elif _STATE_id == _STATE_COPIED_FUS:
+ if fus_is_idle():
+ log("FUS copy complete, installing")
+ _write_state(_STATE_INSTALLING_FUS)
+ _fus_run_fwupgrade(_ADDR_FUS)
+ else:
+ log("FUS copy bad state")
+ _write_failure_state(REASON_FLASH_FUS_BAD_STATE)
+
+ # Keep polling the state until we see a 0,0 (success) or non-transient
+ # error. In general we should expect to see (16,0) several times,
+ # followed by a (255,0), followed by (0, 0).
+ elif _STATE_id == _STATE_INSTALLING_FUS:
+ log("Installing FUS...")
+ status, result = fus_get_state()
+ log("FUS state: {} {}", status, result)
+ if 0x20 <= status <= 0x2F and result == 0:
+ # FUS_STATE_FUS_UPGRD_ONGOING
+ log("FUS still in progress...")
+ elif 0x10 <= status <= 0x1F and result == 0x11:
+ # FUS_STATE_FW_UPGRD_ONGOING and FUS_FW_ROLLBACK_ERROR
+ # Confusingly this is a "FW_UPGRD" (0x10) not "FUS_UPRD" (0x20).
+ log("Attempted to install same FUS version... re-querying FUS state to resume.")
+ elif status == 0:
+ log("FUS update successful")
+ _write_state(_STATE_CHECK_UPDATES)
+ # Need to force a reset after FUS install otherwise a subsequent flash copy will fail.
+ machine.reset()
+ elif result == 0:
+ # See below (for equivalent path for WS install -- we
+ # sometimes see (255,0) right at the end).
+ log("Re-querying FUS state...")
+ elif result == 0xFF:
+ _write_failure_state(REASON_FUS_NOT_RESPONDING_AFTER_FUS)
+ else:
+ _write_failure_state(REASON_FUS_VENDOR + result)
+
+ # Keep polling the state until we see 0,0 or failure (1,0). Any other
+ # result means retry (but the docs say that 0 and 1 are the only
+ # status values).
+ elif _STATE_id == _STATE_DELETING_WS:
+ log("Deleting WS...")
+ status, result = fus_get_state()
+ log("FUS state: {} {}", status, result)
+ if status == 0:
+ if sum(stm.rfcore_fw_version(_FW_VERSION_WS)) == 0:
+ log("WS deletion complete")
+ _write_state(_STATE_CHECK_UPDATES)
+ else:
+ log("WS deletion no effect")
+ _write_failure_state(REASON_WS_STILL_PRESENT)
+ elif status == 1:
+ log("WS deletion failed")
+ _write_failure_state(REASON_WS_DELETION_FAILED)
+
+ # As for _STATE_COPIED_FUS above. We should immediately see 0,0.
+ elif _STATE_id == _STATE_COPIED_WS:
+ if fus_is_idle():
+ log("WS copy complete, installing")
+ _write_state(_STATE_INSTALLING_WS)
+ _fus_run_fwupgrade(_ADDR_WS_BLE_HCI)
+ else:
+ log("WS copy bad state")
+ _write_failure_state(REASON_FLASH_WS_BAD_STATE)
+
+ # As for _STATE_INSTALLING_FUS above.
+ elif _STATE_id == _STATE_INSTALLING_WS:
+ log("Installing WS...")
+ status, result = fus_get_state()
+ log("FUS state: {} {}", status, result)
+ if 0x10 <= status <= 0x1F and result == 0:
+ # FUS_STATE_FW_UPGRD_ONGOING
+ log("WS still in progress...")
+ elif 0x10 <= status <= 0x1F and result == 0x11:
+ # FUS_FW_ROLLBACK_ERROR
+ log("Attempted to install same WS version... re-querying FUS state to resume.")
+ elif status == 0:
+ log("WS update successful")
+ _write_state(_STATE_WAITING_FOR_WS)
+ elif result == 0:
+ # We get a error response with no payload sometimes at the end
+ # of the update (this is not in AN5185). Re-try the GET_STATE.
+ # The same thing happens transitioning from WS to FUS mode.
+ # The actual HCI response has no payload, the result=0 comes from
+ # _parse_vendor_response above when len=7.
+ log("Re-querying FUS state...")
+ elif result == 0xFF:
+ # This is specifically a failure sending the HCI command.
+ _write_failure_state(REASON_FUS_NOT_RESPONDING_AFTER_WS)
+ else:
+ _write_failure_state(REASON_WS_VENDOR + result)
+
+
+# Start a firmware update.
+# This will immediately trigger a reset and start the update process on boot.
+def check_for_updates():
+ log("Starting firmware update")
+ _write_state(_STATE_WAITING_FOR_FUS)
+ machine.reset()
diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py
new file mode 100755
index 000000000..23f3d20f0
--- /dev/null
+++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+#
+# This file is part of the MicroPython project, http://micropython.org/
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2020 Jim Mussared
+#
+# 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.
+
+# This script obfuscates the ST wireless binaries so they can be safely copied
+# to the flash filesystem and not be accidentally discovered by the FUS during
+# an update. See more information (and the corresponding de-obfuscation) in
+# rfcore_firmware.py as well as instructions on how to use.
+
+import os
+import struct
+import sys
+
+# Must match rfcore_firmware.py.
+_OBFUSCATION_KEY = 0x0573B55AA
+
+_FIRMWARE_FILES = {
+ "stm32wb5x_FUS_fw_1_0_2.bin": "fus_102.bin",
+ "stm32wb5x_FUS_fw.bin": "fus_110.bin",
+ "stm32wb5x_BLE_HCILayer_fw.bin": "ws_ble_hci.bin",
+}
+
+
+def main(src_path, dest_path):
+ for src_file, dest_file in _FIRMWARE_FILES.items():
+ src_file = os.path.join(src_path, src_file)
+ dest_file = os.path.join(dest_path, dest_file)
+ if not os.path.exists(src_file):
+ print("Unable to find: {}".format(src_file))
+ continue
+ sz = 0
+ with open(src_file, "rb") as src:
+ with open(dest_file, "wb") as dest:
+ while True:
+ b = src.read(4)
+ if not b:
+ break
+ (v,) = struct.unpack("<I", b)
+ v ^= _OBFUSCATION_KEY
+ dest.write(struct.pack("<I", v))
+ sz += 4
+ print("Written {} ({} bytes)".format(dest_file, sz))
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print("Usage: {} src_path dest_path".format(sys.argv[0]))
+ print()
+ print(
+ '"src_path" should be the location of the ST binaries from https://github.com/STMicroelectronics/STM32CubeWB/tree/master/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x'
+ )
+ print(
+ '"dest_path" will be where fus_102.bin, fus_110.bin, and ws_ble_hci.bin will be written to.'
+ )
+ sys.exit(1)
+
+ main(sys.argv[1], sys.argv[2])