From 222ec1a4a86cdcfa9546c30243e4e244bec9697f Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 29 Sep 2020 18:37:45 +1000 Subject: stm32/boards/NUCLEO_WB55: Add standalone WB55 FUS/WS firmware updater. This commit adds a script that can be run on-device to install FUS and WS binaries from the filesystem. Instructions for use are provided in the rfcore_firmware.py file. The commit also removes unneeded functionality from the existing rfcore.py debug script (and renames it rfcore_debug.py). --- ports/stm32/boards/NUCLEO_WB55/rfcore.py | 348 ------------- ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py | 237 +++++++++ ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py | 547 +++++++++++++++++++++ .../boards/NUCLEO_WB55/rfcore_makefirmware.py | 79 +++ 4 files changed, 863 insertions(+), 348 deletions(-) delete mode 100644 ports/stm32/boards/NUCLEO_WB55/rfcore.py create mode 100644 ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py create mode 100644 ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py create mode 100755 ports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore.py b/ports/stm32/boards/NUCLEO_WB55/rfcore.py deleted file mode 100644 index cfe315605..000000000 --- a/ports/stm32/boards/NUCLEO_WB55/rfcore.py +++ /dev/null @@ -1,348 +0,0 @@ -# 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 some helpers to allow Python code to access the IPCC -# 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. - -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 -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) - -TABLE_DEVICE_INFO = const(0) -TABLE_BLE = const(1) -TABLE_SYS = const(3) -TABLE_MEM_MANAGER = const(4) - -CHANNEL_BLE = const(1) -CHANNEL_SYS = const(2) -CHANNEL_TRACES = const(4) -CHANNEL_ACL = const(6) - -INDICATOR_HCI_COMMAND = const(0x01) -INDICATOR_HCI_EVENT = const(0x04) -INDICATOR_FUS_COMMAND = const(0x10) -INDICATOR_FUS_RESPONSE = const(0x11) -INDICATOR_FUS_EVENT = const(0x12) - -MAGIC_FUS_ACTIVE = const(0xA94656B9) - - -def get_ipccdba(): - return mem32[stm.FLASH + stm.FLASH_IPCCBR] & 0x3FFF - - -def get_ipcc_table(table): - return mem32[SRAM2A_BASE + get_ipccdba() + table * 4] - - -def get_ipcc_table_word(table, offset): - return mem32[get_ipcc_table(table) + offset * 4] & 0xFFFFFFFF - - -def get_ipcc_table_byte(table, offset): - return mem8[get_ipcc_table(table) + offset] & 0xFF - - -def sram2a_dump(num_words=64, width=8): - print("SRAM2A @%08x" % SRAM2A_BASE) - for i in range((num_words + width - 1) // width): - print(" %04x " % (i * 4 * width), end="") - for j in range(width): - print(" %08x" % (mem32[SRAM2A_BASE + (i * width + j) * 4] & 0xFFFFFFFF), end="") - print() - - -SYS_CMD_BUF = 0 # next*,prev*,type8,...; 272 bytes -SYS_SYS_QUEUE = 0 # next*,prev* - -MM_BLE_SPARE_EVT_BUF = 0 # next*,prev*; 272 bytes -MM_SYS_SPARE_EVT_BUF = 0 # next*,prev*; 272 bytes -MM_BLE_POOL = 0 # ? -MM_BLE_POOL_SIZE = 0 # ? -MM_FREE_BUF_QUEUE = 0 # next*,prev* -MM_EV_POOL = 0 # ? -MM_EV_POOL_SIZE = 0 # ? - -BLE_CMD_BUF = 0 -BLE_CS_BUF = 0 -BLE_EVT_QUEUE = 0 -BLE_HCI_ACL_DATA_BUF = 0 - - -def ipcc_init(): - global SYS_CMD_BUF, SYS_SYS_QUEUE - SYS_CMD_BUF = get_ipcc_table_word(TABLE_SYS, 0) - SYS_SYS_QUEUE = get_ipcc_table_word(TABLE_SYS, 1) - - global MM_BLE_SPARE_EVT_BUF, MM_SYS_SPARE_EVT_BUF, MM_BLE_POOL, MM_BLE_POOL_SIZE, MM_FREE_BUF_QUEUE, MM_EV_POOL, MM_EV_POOL_SIZE - MM_BLE_SPARE_EVT_BUF = get_ipcc_table_word(TABLE_MEM_MANAGER, 0) - MM_SYS_SPARE_EVT_BUF = get_ipcc_table_word(TABLE_MEM_MANAGER, 1) - MM_BLE_POOL = get_ipcc_table_word(TABLE_MEM_MANAGER, 2) - MM_BLE_POOL_SIZE = get_ipcc_table_word(TABLE_MEM_MANAGER, 3) - MM_FREE_BUF_QUEUE = get_ipcc_table_word(TABLE_MEM_MANAGER, 4) - MM_EV_POOL = get_ipcc_table_word(TABLE_MEM_MANAGER, 5) - MM_EV_POOL_SIZE = get_ipcc_table_word(TABLE_MEM_MANAGER, 6) - - global BLE_CMD_BUF, BLE_CS_BUF, BLE_EVT_QUEUE, BLE_HCI_ACL_DATA_BUF - BLE_CMD_BUF = get_ipcc_table_word(TABLE_BLE, 0) - BLE_CS_BUF = get_ipcc_table_word(TABLE_BLE, 1) - BLE_EVT_QUEUE = get_ipcc_table_word(TABLE_BLE, 2) - BLE_HCI_ACL_DATA_BUF = get_ipcc_table_word(TABLE_BLE, 3) - - # Disable interrupts, the code here uses polling - mem32[stm.IPCC + stm.IPCC_C1CR] = 0 - - print("IPCC initialised") - print("SYS: 0x%08x 0x%08x" % (SYS_CMD_BUF, SYS_SYS_QUEUE)) - 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 - - -def info(): - sfr = mem32[stm.FLASH + stm.FLASH_SFR] - srrvr = mem32[stm.FLASH + stm.FLASH_SRRVR] - - print("IPCCDBA : 0x%08x" % (get_ipccdba() & 0x3FFF)) - print("DDS : %r" % bool(sfr & (1 << 12))) - print("FSD : %r" % bool(sfr & (1 << 8))) - print("SFSA : 0x%08x" % (sfr & 0xFF)) - print("C2OPT : %r" % bool(srrvr & (1 << 31))) - print("NBRSD : %r" % bool(srrvr & (1 << 30))) - print("SNBRSA : 0x%08x" % ((srrvr >> 25) & 0x1F)) - print("BRSD : %r" % bool(srrvr & (1 << 23))) - print("SBRSA : 0x%08x" % ((srrvr >> 18) & 0x1F)) - print("SBRV : 0x%08x" % (srrvr & 0x3FFFF)) - - -def dev_info(): - def dump_version(offset): - x = get_ipcc_table_word(TABLE_DEVICE_INFO, offset) - print( - "0x%08x (%u.%u.%u.%u.%u)" - % (x, x >> 24, x >> 16 & 0xFF, x >> 8 & 0xFF, x >> 4 & 0xF, x & 0xF) - ) - - def dump_memory_size(offset): - x = get_ipcc_table_word(TABLE_DEVICE_INFO, offset) - print( - "0x%08x (SRAM2b=%uk SRAM2a=%uk flash=%uk)" - % (x, x >> 24, x >> 16 & 0xFF, (x & 0xFF) * 4) - ) - - print("Device information table @%08x:" % get_ipcc_table(TABLE_DEVICE_INFO)) - if fus_active(): - # layout when running FUS - print("FUS is active") - print("state : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 0)) - print("last FUS active state : 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 5)) - print("last wireless stack state: 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 6)) - print("cur wireless stack type : 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 7)) - print("safe boot version : ", end="") - dump_version(2) - print("FUS version : ", end="") - dump_version(3) - print("FUS memory size : ", end="") - dump_memory_size(4) - print("wireless stack version : ", end="") - dump_version(5) - print("wireless stack mem size : ", end="") - dump_memory_size(6) - print("wireless FW-BLE info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) - print("wireless FW-thread info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 8)) - print( - "UID64 : 0x%08x 0x%08x" - % ( - get_ipcc_table_word(TABLE_DEVICE_INFO, 9), - get_ipcc_table_word(TABLE_DEVICE_INFO, 10), - ) - ) - print("device ID : 0x%04x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 11)) - else: - # layout when running WS - print("WS is active") - print("safe boot version : ", end="") - dump_version(0) - print("FUS version : ", end="") - dump_version(1) - print("FUS memory size : ", end="") - dump_memory_size(2) - print("FUS info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 3)) - print("wireless stack version : ", end="") - dump_version(4) - print("wireless stack mem size : ", end="") - dump_memory_size(5) - print("wireless stack info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) - print("wireless reserved : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) - - -def ipcc_state(): - print("IPCC:") - print(" C1CR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1CR] & 0xFFFFFFFF), end="") - print(" C2CR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2CR] & 0xFFFFFFFF)) - print(" C1MR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1MR] & 0xFFFFFFFF), end="") - print(" C2MR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2MR] & 0xFFFFFFFF)) - # these always read 0 - # print(' C1SCR: 0x%08x' % (mem32[stm.IPCC + stm.IPCC_C1SCR] & 0xffffffff), end='') - # print(' C2SCR: 0x%08x' % (mem32[stm.IPCC + stm.IPCC_C2SCR] & 0xffffffff)) - print(" C1TOC2SR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1TOC2SR] & 0xFFFFFFFF), end="") - print(" C2TOC1SR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2TOC1SR] & 0xFFFFFFFF)) - - -sram2a_dump(264) -ipcc_init() -info() -dev_info() diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py new file mode 100644 index 000000000..4dbead0ac --- /dev/null +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py @@ -0,0 +1,237 @@ +# 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 some helpers to allow Python code to access the IPCC +# 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. + +# 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 stm + +SRAM2A_BASE = const(0x2003_0000) + +# for vendor OGF +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) + +TABLE_DEVICE_INFO = const(0) +TABLE_BLE = const(1) +TABLE_SYS = const(3) +TABLE_MEM_MANAGER = const(4) + +CHANNEL_BLE = const(1) +CHANNEL_SYS = const(2) +CHANNEL_TRACES = const(4) +CHANNEL_ACL = const(6) + +INDICATOR_HCI_COMMAND = const(0x01) +INDICATOR_HCI_EVENT = const(0x04) +INDICATOR_FUS_COMMAND = const(0x10) +INDICATOR_FUS_RESPONSE = const(0x11) +INDICATOR_FUS_EVENT = const(0x12) + +MAGIC_FUS_ACTIVE = const(0xA94656B9) + + +def get_ipccdba(): + return mem32[stm.FLASH + stm.FLASH_IPCCBR] & 0x3FFF + + +def get_ipcc_table(table): + return mem32[SRAM2A_BASE + get_ipccdba() + table * 4] + + +def get_ipcc_table_word(table, offset): + return mem32[get_ipcc_table(table) + offset * 4] & 0xFFFFFFFF + + +def get_ipcc_table_byte(table, offset): + return mem8[get_ipcc_table(table) + offset] & 0xFF + + +def sram2a_dump(num_words=64, width=8): + print("SRAM2A @%08x" % SRAM2A_BASE) + for i in range((num_words + width - 1) // width): + print(" %04x " % (i * 4 * width), end="") + for j in range(width): + print(" %08x" % (mem32[SRAM2A_BASE + (i * width + j) * 4] & 0xFFFFFFFF), end="") + print() + + +SYS_CMD_BUF = 0 # next*,prev*,type8,...; 272 bytes +SYS_SYS_QUEUE = 0 # next*,prev* + +MM_BLE_SPARE_EVT_BUF = 0 # next*,prev*; 272 bytes +MM_SYS_SPARE_EVT_BUF = 0 # next*,prev*; 272 bytes +MM_BLE_POOL = 0 # ? +MM_BLE_POOL_SIZE = 0 # ? +MM_FREE_BUF_QUEUE = 0 # next*,prev* +MM_EV_POOL = 0 # ? +MM_EV_POOL_SIZE = 0 # ? + +BLE_CMD_BUF = 0 +BLE_CS_BUF = 0 +BLE_EVT_QUEUE = 0 +BLE_HCI_ACL_DATA_BUF = 0 + + +def ipcc_init(): + global SYS_CMD_BUF, SYS_SYS_QUEUE + SYS_CMD_BUF = get_ipcc_table_word(TABLE_SYS, 0) + SYS_SYS_QUEUE = get_ipcc_table_word(TABLE_SYS, 1) + + global MM_BLE_SPARE_EVT_BUF, MM_SYS_SPARE_EVT_BUF, MM_BLE_POOL, MM_BLE_POOL_SIZE, MM_FREE_BUF_QUEUE, MM_EV_POOL, MM_EV_POOL_SIZE + MM_BLE_SPARE_EVT_BUF = get_ipcc_table_word(TABLE_MEM_MANAGER, 0) + MM_SYS_SPARE_EVT_BUF = get_ipcc_table_word(TABLE_MEM_MANAGER, 1) + MM_BLE_POOL = get_ipcc_table_word(TABLE_MEM_MANAGER, 2) + MM_BLE_POOL_SIZE = get_ipcc_table_word(TABLE_MEM_MANAGER, 3) + MM_FREE_BUF_QUEUE = get_ipcc_table_word(TABLE_MEM_MANAGER, 4) + MM_EV_POOL = get_ipcc_table_word(TABLE_MEM_MANAGER, 5) + MM_EV_POOL_SIZE = get_ipcc_table_word(TABLE_MEM_MANAGER, 6) + + global BLE_CMD_BUF, BLE_CS_BUF, BLE_EVT_QUEUE, BLE_HCI_ACL_DATA_BUF + BLE_CMD_BUF = get_ipcc_table_word(TABLE_BLE, 0) + BLE_CS_BUF = get_ipcc_table_word(TABLE_BLE, 1) + BLE_EVT_QUEUE = get_ipcc_table_word(TABLE_BLE, 2) + BLE_HCI_ACL_DATA_BUF = get_ipcc_table_word(TABLE_BLE, 3) + + # Disable interrupts, the code here uses polling + mem32[stm.IPCC + stm.IPCC_C1CR] = 0 + + print("IPCC initialised") + print("SYS: 0x%08x 0x%08x" % (SYS_CMD_BUF, SYS_SYS_QUEUE)) + print("BLE: 0x%08x 0x%08x 0x%08x" % (BLE_CMD_BUF, BLE_CS_BUF, BLE_EVT_QUEUE)) + + +def fus_active(): + return get_ipcc_table_word(TABLE_DEVICE_INFO, 0) == MAGIC_FUS_ACTIVE + + +def info(): + sfr = mem32[stm.FLASH + stm.FLASH_SFR] + srrvr = mem32[stm.FLASH + stm.FLASH_SRRVR] + + print("IPCCDBA : 0x%08x" % (get_ipccdba() & 0x3FFF)) + print("DDS : %r" % bool(sfr & (1 << 12))) + print("FSD : %r" % bool(sfr & (1 << 8))) + print("SFSA : 0x%08x" % (sfr & 0xFF)) + print("C2OPT : %r" % bool(srrvr & (1 << 31))) + print("NBRSD : %r" % bool(srrvr & (1 << 30))) + print("SNBRSA : 0x%08x" % ((srrvr >> 25) & 0x1F)) + print("BRSD : %r" % bool(srrvr & (1 << 23))) + print("SBRSA : 0x%08x" % ((srrvr >> 18) & 0x1F)) + print("SBRV : 0x%08x" % (srrvr & 0x3FFFF)) + + +def dev_info(): + def dump_version(offset): + x = get_ipcc_table_word(TABLE_DEVICE_INFO, offset) + print( + "0x%08x (%u.%u.%u.%u.%u)" + % (x, x >> 24, x >> 16 & 0xFF, x >> 8 & 0xFF, x >> 4 & 0xF, x & 0xF) + ) + + def dump_memory_size(offset): + x = get_ipcc_table_word(TABLE_DEVICE_INFO, offset) + print( + "0x%08x (SRAM2b=%uk SRAM2a=%uk flash=%uk)" + % (x, x >> 24, x >> 16 & 0xFF, (x & 0xFF) * 4) + ) + + print("Device information table @%08x:" % get_ipcc_table(TABLE_DEVICE_INFO)) + if fus_active(): + # layout when running FUS + print("FUS is active") + print("state : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 0)) + print("last FUS active state : 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 5)) + print("last wireless stack state: 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 6)) + print("cur wireless stack type : 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 7)) + print("safe boot version : ", end="") + dump_version(2) + print("FUS version : ", end="") + dump_version(3) + print("FUS memory size : ", end="") + dump_memory_size(4) + print("wireless stack version : ", end="") + dump_version(5) + print("wireless stack mem size : ", end="") + dump_memory_size(6) + print("wireless FW-BLE info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + print("wireless FW-thread info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 8)) + print( + "UID64 : 0x%08x 0x%08x" + % ( + get_ipcc_table_word(TABLE_DEVICE_INFO, 9), + get_ipcc_table_word(TABLE_DEVICE_INFO, 10), + ) + ) + print("device ID : 0x%04x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 11)) + else: + # layout when running WS + print("WS is active") + print("safe boot version : ", end="") + dump_version(0) + print("FUS version : ", end="") + dump_version(1) + print("FUS memory size : ", end="") + dump_memory_size(2) + print("FUS info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 3)) + print("wireless stack version : ", end="") + dump_version(4) + print("wireless stack mem size : ", end="") + dump_memory_size(5) + print("wireless stack info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + print("wireless reserved : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + + +def ipcc_state(): + print("IPCC:") + print(" C1CR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1CR] & 0xFFFFFFFF), end="") + print(" C2CR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2CR] & 0xFFFFFFFF)) + print(" C1MR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1MR] & 0xFFFFFFFF), end="") + print(" C2MR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2MR] & 0xFFFFFFFF)) + # these always read 0 + # print(' C1SCR: 0x%08x' % (mem32[stm.IPCC + stm.IPCC_C1SCR] & 0xffffffff), end='') + # print(' C2SCR: 0x%08x' % (mem32[stm.IPCC + stm.IPCC_C2SCR] & 0xffffffff)) + print(" C1TOC2SR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1TOC2SR] & 0xFFFFFFFF), end="") + print(" C2TOC1SR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2TOC1SR] & 0xFFFFFFFF)) + + +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(" 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("