|
|
@@ -0,0 +1,352 @@
|
|
|
+import logging
|
|
|
+from enum import Enum
|
|
|
+
|
|
|
+from flipper.utils.openocd import OpenOCD
|
|
|
+from flipper.utils.register import Register32, RegisterBitDefinition
|
|
|
+
|
|
|
+
|
|
|
+class STM32WB55:
|
|
|
+ # Address of OTP memory in flash
|
|
|
+ OTP_BASE = 0x1FFF7000
|
|
|
+
|
|
|
+ # Address of Option byte in flash
|
|
|
+ OPTION_BYTE_BASE = 0x1FFF8000
|
|
|
+
|
|
|
+ # Flash base address
|
|
|
+ FLASH_BASE = 0x58004000
|
|
|
+
|
|
|
+ # Flash unlock register
|
|
|
+ FLASH_KEYR = FLASH_BASE + 0x08
|
|
|
+
|
|
|
+ # Option byte unlock register
|
|
|
+ FLASH_OPTKEYR = FLASH_BASE + 0x0C
|
|
|
+
|
|
|
+ # Flash unlock keys
|
|
|
+ FLASH_UNLOCK_KEY1 = 0x45670123
|
|
|
+ FLASH_UNLOCK_KEY2 = 0xCDEF89AB
|
|
|
+
|
|
|
+ # Option byte unlock keys
|
|
|
+ FLASH_UNLOCK_OPTKEY1 = 0x08192A3B
|
|
|
+ FLASH_UNLOCK_OPTKEY2 = 0x4C5D6E7F
|
|
|
+
|
|
|
+ # Flash control register
|
|
|
+ FLASH_CR = Register32(
|
|
|
+ FLASH_BASE + 0x14,
|
|
|
+ [
|
|
|
+ RegisterBitDefinition("PG", 0, 1),
|
|
|
+ RegisterBitDefinition("PER", 1, 1),
|
|
|
+ RegisterBitDefinition("MER", 2, 1),
|
|
|
+ RegisterBitDefinition("PNB", 3, 8),
|
|
|
+ RegisterBitDefinition("_", 11, 5),
|
|
|
+ RegisterBitDefinition("STRT", 16, 1),
|
|
|
+ RegisterBitDefinition("OPT_STRT", 17, 1),
|
|
|
+ RegisterBitDefinition("FSTPG", 18, 1),
|
|
|
+ RegisterBitDefinition("_", 19, 5),
|
|
|
+ RegisterBitDefinition("EOPIE", 24, 1),
|
|
|
+ RegisterBitDefinition("ERRIE", 25, 1),
|
|
|
+ RegisterBitDefinition("RD_ERRIE", 26, 1),
|
|
|
+ RegisterBitDefinition("OBL_LAUNCH", 27, 1),
|
|
|
+ RegisterBitDefinition("_", 28, 2),
|
|
|
+ RegisterBitDefinition("OPT_LOCK", 30, 1),
|
|
|
+ RegisterBitDefinition("LOCK", 31, 1),
|
|
|
+ ],
|
|
|
+ )
|
|
|
+
|
|
|
+ # Flash status register
|
|
|
+ FLASH_SR = Register32(
|
|
|
+ FLASH_BASE + 0x10,
|
|
|
+ [
|
|
|
+ RegisterBitDefinition("EOP", 0, 1),
|
|
|
+ RegisterBitDefinition("OP_ERR", 1, 1),
|
|
|
+ RegisterBitDefinition("_", 2, 1),
|
|
|
+ RegisterBitDefinition("PROG_ERR", 3, 1),
|
|
|
+ RegisterBitDefinition("WRP_ERR", 4, 1),
|
|
|
+ RegisterBitDefinition("PGA_ERR", 5, 1),
|
|
|
+ RegisterBitDefinition("SIZE_ERR", 6, 1),
|
|
|
+ RegisterBitDefinition("PGS_ERR", 7, 1),
|
|
|
+ RegisterBitDefinition("MISS_ERR", 8, 1),
|
|
|
+ RegisterBitDefinition("FAST_ERR", 9, 1),
|
|
|
+ RegisterBitDefinition("_", 10, 3),
|
|
|
+ RegisterBitDefinition("OPTNV", 13, 1),
|
|
|
+ RegisterBitDefinition("RD_ERR", 14, 1),
|
|
|
+ RegisterBitDefinition("OPTV_ERR", 15, 1),
|
|
|
+ RegisterBitDefinition("BSY", 16, 1),
|
|
|
+ RegisterBitDefinition("_", 17, 1),
|
|
|
+ RegisterBitDefinition("CFGBSY", 18, 1),
|
|
|
+ RegisterBitDefinition("PESD", 19, 1),
|
|
|
+ RegisterBitDefinition("_", 20, 12),
|
|
|
+ ],
|
|
|
+ )
|
|
|
+
|
|
|
+ # Option byte registers
|
|
|
+ FLASH_OPTR = FLASH_BASE + 0x20
|
|
|
+ FLASH_PCROP1ASR = FLASH_BASE + 0x24
|
|
|
+ FLASH_PCROP1AER = FLASH_BASE + 0x28
|
|
|
+ FLASH_WRP1AR = FLASH_BASE + 0x2C
|
|
|
+ FLASH_WRP1BR = FLASH_BASE + 0x30
|
|
|
+ FLASH_PCROP1BSR = FLASH_BASE + 0x34
|
|
|
+ FLASH_PCROP1BER = FLASH_BASE + 0x38
|
|
|
+ FLASH_IPCCBR = FLASH_BASE + 0x3C
|
|
|
+
|
|
|
+ # Map option byte dword index to register address
|
|
|
+ OPTION_BYTE_MAP_TO_REGS = {
|
|
|
+ 0: FLASH_OPTR,
|
|
|
+ 1: FLASH_PCROP1ASR,
|
|
|
+ 2: FLASH_PCROP1AER,
|
|
|
+ 3: FLASH_WRP1AR,
|
|
|
+ 4: FLASH_WRP1BR,
|
|
|
+ 5: FLASH_PCROP1BSR,
|
|
|
+ 6: FLASH_PCROP1BER,
|
|
|
+ 7: None, # Invalid Options
|
|
|
+ 8: None, # Invalid Options
|
|
|
+ 9: None, # Invalid Options
|
|
|
+ 10: None, # Invalid Options
|
|
|
+ 11: None, # Invalid Options
|
|
|
+ 12: None, # Invalid Options
|
|
|
+ 13: FLASH_IPCCBR,
|
|
|
+ 14: None, # Secure Flash
|
|
|
+ 15: None, # Core 2 Options
|
|
|
+ }
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ self.logger = logging.getLogger("STM32WB55")
|
|
|
+
|
|
|
+ class RunMode(Enum):
|
|
|
+ Init = "init"
|
|
|
+ Run = "run"
|
|
|
+ Halt = "halt"
|
|
|
+
|
|
|
+ def reset(self, oocd: OpenOCD, mode: RunMode):
|
|
|
+ self.logger.debug("Resetting device")
|
|
|
+ oocd.send_tcl(f"reset {mode.value}")
|
|
|
+
|
|
|
+ def clear_flash_errors(self, oocd: OpenOCD):
|
|
|
+ # Errata 2.2.9: Flash OPTVERR flag is always set after system reset
|
|
|
+ # And also clear all other flash error flags
|
|
|
+ self.logger.debug(f"Resetting flash errors")
|
|
|
+ self.FLASH_SR.load(oocd)
|
|
|
+ self.FLASH_SR.OP_ERR = 1
|
|
|
+ self.FLASH_SR.PROG_ERR = 1
|
|
|
+ self.FLASH_SR.WRP_ERR = 1
|
|
|
+ self.FLASH_SR.PGA_ERR = 1
|
|
|
+ self.FLASH_SR.SIZE_ERR = 1
|
|
|
+ self.FLASH_SR.PGS_ERR = 1
|
|
|
+ self.FLASH_SR.MISS_ERR = 1
|
|
|
+ self.FLASH_SR.FAST_ERR = 1
|
|
|
+ self.FLASH_SR.RD_ERR = 1
|
|
|
+ self.FLASH_SR.OPTV_ERR = 1
|
|
|
+ self.FLASH_SR.store(oocd)
|
|
|
+
|
|
|
+ def flash_unlock(self, oocd: OpenOCD):
|
|
|
+ # Check if flash is already unlocked
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ if self.FLASH_CR.LOCK == 0:
|
|
|
+ self.logger.debug("Flash is already unlocked")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Unlock flash
|
|
|
+ self.logger.debug("Unlocking Flash")
|
|
|
+ oocd.write_32(self.FLASH_KEYR, self.FLASH_UNLOCK_KEY1)
|
|
|
+ oocd.write_32(self.FLASH_KEYR, self.FLASH_UNLOCK_KEY2)
|
|
|
+
|
|
|
+ # Check if flash is unlocked
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ if self.FLASH_CR.LOCK == 0:
|
|
|
+ self.logger.debug("Flash unlocked")
|
|
|
+ else:
|
|
|
+ self.logger.error("Flash unlock failed")
|
|
|
+ raise Exception("Flash unlock failed")
|
|
|
+
|
|
|
+ def option_bytes_unlock(self, oocd: OpenOCD):
|
|
|
+ # Check if options is already unlocked
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ if self.FLASH_CR.OPT_LOCK == 0:
|
|
|
+ self.logger.debug("Options is already unlocked")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Unlock options
|
|
|
+ self.logger.debug("Unlocking Options")
|
|
|
+ oocd.write_32(self.FLASH_OPTKEYR, self.FLASH_UNLOCK_OPTKEY1)
|
|
|
+ oocd.write_32(self.FLASH_OPTKEYR, self.FLASH_UNLOCK_OPTKEY2)
|
|
|
+
|
|
|
+ # Check if options is unlocked
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ if self.FLASH_CR.OPT_LOCK == 0:
|
|
|
+ self.logger.debug("Options unlocked")
|
|
|
+ else:
|
|
|
+ self.logger.error("Options unlock failed")
|
|
|
+ raise Exception("Options unlock failed")
|
|
|
+
|
|
|
+ def option_bytes_lock(self, oocd: OpenOCD):
|
|
|
+ # Check if options is already locked
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ if self.FLASH_CR.OPT_LOCK == 1:
|
|
|
+ self.logger.debug("Options is already locked")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Lock options
|
|
|
+ self.logger.debug("Locking Options")
|
|
|
+ self.FLASH_CR.OPT_LOCK = 1
|
|
|
+ self.FLASH_CR.store(oocd)
|
|
|
+
|
|
|
+ # Check if options is locked
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ if self.FLASH_CR.OPT_LOCK == 1:
|
|
|
+ self.logger.debug("Options locked")
|
|
|
+ else:
|
|
|
+ self.logger.error("Options lock failed")
|
|
|
+ raise Exception("Options lock failed")
|
|
|
+
|
|
|
+ def flash_lock(self, oocd: OpenOCD):
|
|
|
+ # Check if flash is already locked
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ if self.FLASH_CR.LOCK == 1:
|
|
|
+ self.logger.debug("Flash is already locked")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Lock flash
|
|
|
+ self.logger.debug("Locking Flash")
|
|
|
+ self.FLASH_CR.LOCK = 1
|
|
|
+ self.FLASH_CR.store(oocd)
|
|
|
+
|
|
|
+ # Check if flash is locked
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ if self.FLASH_CR.LOCK == 1:
|
|
|
+ self.logger.debug("Flash locked")
|
|
|
+ else:
|
|
|
+ self.logger.error("Flash lock failed")
|
|
|
+ raise Exception("Flash lock failed")
|
|
|
+
|
|
|
+ def option_bytes_apply(self, oocd: OpenOCD):
|
|
|
+ self.logger.debug(f"Applying Option Bytes")
|
|
|
+
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ self.FLASH_CR.OPT_STRT = 1
|
|
|
+ self.FLASH_CR.store(oocd)
|
|
|
+
|
|
|
+ # Wait for Option Bytes to be applied
|
|
|
+ self.flash_wait_for_operation(oocd)
|
|
|
+
|
|
|
+ def option_bytes_load(self, oocd: OpenOCD):
|
|
|
+ self.logger.debug(f"Loading Option Bytes")
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ self.FLASH_CR.OBL_LAUNCH = 1
|
|
|
+ self.FLASH_CR.store(oocd)
|
|
|
+
|
|
|
+ def option_bytes_id_to_address(self, id: int) -> int:
|
|
|
+ # Check if this option byte (dword) is mapped to a register
|
|
|
+ device_reg_addr = self.OPTION_BYTE_MAP_TO_REGS.get(id, None)
|
|
|
+ if device_reg_addr is None:
|
|
|
+ raise Exception(f"Option Byte {id} is not mapped to a register")
|
|
|
+
|
|
|
+ return device_reg_addr
|
|
|
+
|
|
|
+ def flash_wait_for_operation(self, oocd: OpenOCD):
|
|
|
+ # Wait for flash operation to complete
|
|
|
+ # TODO: timeout
|
|
|
+ while True:
|
|
|
+ self.FLASH_SR.load(oocd)
|
|
|
+ if self.FLASH_SR.BSY == 0:
|
|
|
+ break
|
|
|
+
|
|
|
+ def flash_dump_status_register(self, oocd: OpenOCD):
|
|
|
+ self.FLASH_SR.load(oocd)
|
|
|
+ self.logger.info(f"FLASH_SR: {self.FLASH_SR.get():08x}")
|
|
|
+ if self.FLASH_SR.EOP:
|
|
|
+ self.logger.info(" End of operation")
|
|
|
+ if self.FLASH_SR.OP_ERR:
|
|
|
+ self.logger.error(" Operation error")
|
|
|
+ if self.FLASH_SR.PROG_ERR:
|
|
|
+ self.logger.error(" Programming error")
|
|
|
+ if self.FLASH_SR.WRP_ERR:
|
|
|
+ self.logger.error(" Write protection error")
|
|
|
+ if self.FLASH_SR.PGA_ERR:
|
|
|
+ self.logger.error(" Programming alignment error")
|
|
|
+ if self.FLASH_SR.SIZE_ERR:
|
|
|
+ self.logger.error(" Size error")
|
|
|
+ if self.FLASH_SR.PGS_ERR:
|
|
|
+ self.logger.error(" Programming sequence error")
|
|
|
+ if self.FLASH_SR.MISS_ERR:
|
|
|
+ self.logger.error(" Fast programming data miss error")
|
|
|
+ if self.FLASH_SR.FAST_ERR:
|
|
|
+ self.logger.error(" Fast programming error")
|
|
|
+ if self.FLASH_SR.OPTNV:
|
|
|
+ self.logger.info(" User option OPTVAL indication")
|
|
|
+ if self.FLASH_SR.RD_ERR:
|
|
|
+ self.logger.info(" PCROP read error")
|
|
|
+ if self.FLASH_SR.OPTV_ERR:
|
|
|
+ self.logger.info(" Option and Engineering bits loading validity error")
|
|
|
+ if self.FLASH_SR.BSY:
|
|
|
+ self.logger.info(" Busy")
|
|
|
+ if self.FLASH_SR.CFGBSY:
|
|
|
+ self.logger.info(" Programming or erase configuration busy")
|
|
|
+ if self.FLASH_SR.PESD:
|
|
|
+ self.logger.info(" Programming / erase operation suspended.")
|
|
|
+
|
|
|
+ def write_flash_64(self, oocd: OpenOCD, address: int, word_1: int, word_2: int):
|
|
|
+ self.logger.debug(f"Writing flash at address {address:08x}")
|
|
|
+
|
|
|
+ if address % 8 != 0:
|
|
|
+ self.logger.error("Address must be aligned to 8 bytes")
|
|
|
+ raise Exception("Address must be aligned to 8 bytes")
|
|
|
+
|
|
|
+ if word_1 == oocd.read_32(address) and word_2 == oocd.read_32(address + 4):
|
|
|
+ self.logger.debug("Data is already programmed")
|
|
|
+ return
|
|
|
+
|
|
|
+ self.flash_unlock(oocd)
|
|
|
+
|
|
|
+ # Check that no flash main memory operation is ongoing by checking the BSY bit
|
|
|
+ self.FLASH_SR.load(oocd)
|
|
|
+ if self.FLASH_SR.BSY:
|
|
|
+ self.logger.error("Flash is busy")
|
|
|
+ self.flash_dump_status_register(oocd)
|
|
|
+ raise Exception("Flash is busy")
|
|
|
+
|
|
|
+ # Enable end of operation interrupts and error interrupts
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ self.FLASH_CR.EOPIE = 1
|
|
|
+ self.FLASH_CR.ERRIE = 1
|
|
|
+ self.FLASH_CR.store(oocd)
|
|
|
+
|
|
|
+ # Check that flash memory program and erase operations are allowed
|
|
|
+ if self.FLASH_SR.PESD:
|
|
|
+ self.logger.error("Flash operations are not allowed")
|
|
|
+ self.flash_dump_status_register(oocd)
|
|
|
+ raise Exception("Flash operations are not allowed")
|
|
|
+
|
|
|
+ # Check and clear all error programming flags due to a previous programming.
|
|
|
+ self.clear_flash_errors(oocd)
|
|
|
+
|
|
|
+ # Set the PG bit in the Flash memory control register (FLASH_CR)
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ self.FLASH_CR.PG = 1
|
|
|
+ self.FLASH_CR.store(oocd)
|
|
|
+
|
|
|
+ # Perform the data write operation at the desired memory address, only double word (64 bits) can be programmed.
|
|
|
+ # Write the first word
|
|
|
+ oocd.send_tcl(f"mww 0x{address:08x} 0x{word_1:08x}")
|
|
|
+ # Write the second word
|
|
|
+ oocd.send_tcl(f"mww 0x{(address + 4):08x} 0x{word_2:08x}")
|
|
|
+
|
|
|
+ # Wait for the BSY bit to be cleared
|
|
|
+ self.flash_wait_for_operation(oocd)
|
|
|
+
|
|
|
+ # Check that EOP flag is set in the FLASH_SR register
|
|
|
+ self.FLASH_SR.load(oocd)
|
|
|
+ if not self.FLASH_SR.EOP:
|
|
|
+ self.logger.error("Flash operation failed")
|
|
|
+ self.flash_dump_status_register(oocd)
|
|
|
+ raise Exception("Flash operation failed")
|
|
|
+
|
|
|
+ # Clear the EOP flag
|
|
|
+ self.FLASH_SR.load(oocd)
|
|
|
+ self.FLASH_SR.EOP = 1
|
|
|
+ self.FLASH_SR.store(oocd)
|
|
|
+
|
|
|
+ # Clear the PG bit in the FLASH_CR register
|
|
|
+ self.FLASH_CR.load(oocd)
|
|
|
+ self.FLASH_CR.PG = 0
|
|
|
+ self.FLASH_CR.store(oocd)
|
|
|
+
|
|
|
+ self.flash_lock(oocd)
|