Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,15 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
__import__('pkg_resources').declare_namespace(__name__)

View file

@ -0,0 +1,44 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from targets import STM32F4FlashProgrammer, STM32F7FlashProgrammer
from swd_port import SerialWireDebugPort
from ftdi_swd import FTDISerialWireDebug
def get_device(board, reset=True, frequency=None):
boards = {
'silk_bb': (0x7893, 10E6, STM32F4FlashProgrammer),
'robert_bb2': (0x7894, 3E6, STM32F7FlashProgrammer)
}
if board not in boards:
raise Exception('Invalid board: {}'.format(board))
usb_pid, default_frequency, board_ctor = boards[board]
if not frequency:
frequency = default_frequency
ftdi = FTDISerialWireDebug(vid=0x0403, pid=usb_pid, interface=0, direction=0x1b,
output_mask=0x02, reset_mask=0x40, frequency=frequency)
swd_port = SerialWireDebugPort(ftdi, reset)
return board_ctor(swd_port)
def flash(board, hex_files):
with get_device(board) as programmer:
programmer.execute_loader()
for hex_file in hex_files:
programmer.load_hex(hex_file)
programmer.reset_core()

View file

@ -0,0 +1,38 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import logging
from . import flash
def main(args=None):
if args is None:
parser = argparse.ArgumentParser(description='A tool for flashing a bigboard via FTDI+SWD')
parser.add_argument('hex_files', type=str, nargs='+',
help='Path to one or more hex files to flash')
parser.add_argument('--board', action='store', choices=['robert_bb2', 'silk_bb'], required=True,
help='Which board is being programmed')
parser.add_argument('--verbose', action='store_true',
help='Output lots of debugging info to the console.')
args = parser.parse_args()
logging.basicConfig(level=(logging.DEBUG if args.verbose else logging.INFO))
flash(args.board, args.hex_files)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,137 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from array import array
from pyftdi.pyftdi.ftdi import Ftdi
import usb.util
class FTDISerialWireDebug(object):
def __init__(self, vid, pid, interface, direction, output_mask, reset_mask, frequency):
self._direction = direction
self._output_mask = output_mask
self._reset_mask = reset_mask
self._ftdi = Ftdi()
try:
self._ftdi.open_mpsse(vid, pid, interface, direction=direction, frequency=frequency,
latency=1)
except:
self._ftdi = None
raise
# get the FTDI FIFO size and increase the chuncksize to match
self._ftdi_fifo_size = min(self._ftdi.fifo_sizes)
self._ftdi.write_data_set_chunksize(self._ftdi_fifo_size)
self._cmd_buffer = array('B')
self._output_enabled = False
self._pending_acks = 0
self._sequence_cmd_buffer = None
def close(self):
if not self._ftdi:
return
self.send_cmds()
self._ftdi.close()
# PyFTDI doesn't do a good job of cleaning up - make sure we release the usb device
usb.util.dispose_resources(self._ftdi.usb_dev)
self._ftdi = None
def _fatal(self, message):
raise Exception('FATAL ERROR: {}'.format(message))
def _queue_cmd(self, write_data):
if len(write_data) > self._ftdi_fifo_size:
raise Exception('Data too big!')
if self._sequence_cmd_buffer is not None:
self._sequence_cmd_buffer.extend(write_data)
else:
if len(self._cmd_buffer) + len(write_data) > self._ftdi_fifo_size:
self.send_cmds()
self._cmd_buffer.extend(write_data)
def _set_output_enabled(self, enabled):
if enabled == self._output_enabled:
return
self._output_enabled = enabled
direction = self._direction & ~(0x00 if enabled else self._output_mask)
self._queue_cmd([Ftdi.SET_BITS_LOW, 0, direction])
def reset(self):
# toggle the reset line
self.reset_lo()
self.reset_hi()
def reset_lo(self):
direction = self._direction & ~(0x00 if self._output_enabled else self._output_mask)
self._queue_cmd([Ftdi.SET_BITS_LOW, 0, direction | self._reset_mask])
self.send_cmds()
def reset_hi(self):
direction = self._direction & ~(0x00 if self._output_enabled else self._output_mask)
self._queue_cmd([Ftdi.SET_BITS_LOW, 0, direction & ~self._reset_mask])
self.send_cmds()
def send_cmds(self):
if self._sequence_cmd_buffer is not None:
self._ftdi.write_data(self._sequence_cmd_buffer)
elif len(self._cmd_buffer) > 0:
self._ftdi.write_data(self._cmd_buffer)
self._cmd_buffer = array('B')
def write_bits_cmd(self, data, num_bits):
if num_bits < 0 or num_bits > 8:
self._fatal('Invalid num_bits')
elif (data & ((1 << num_bits) - 1)) != data:
self._fatal('Invalid data!')
self._set_output_enabled(True)
self._queue_cmd([Ftdi.WRITE_BITS_NVE_LSB, num_bits - 1, data])
def write_bytes_cmd(self, data):
length = len(data) - 1
if length < 0 or length > 0xffff:
self._fatal('Invalid length')
self._set_output_enabled(True)
self._queue_cmd([Ftdi.WRITE_BYTES_NVE_LSB, length & 0xff, length >> 8] + data)
def read_bits_cmd(self, num_bits):
if num_bits < 0 or num_bits > 8:
self._fatal('Invalid num_bits')
self._set_output_enabled(False)
self._queue_cmd([Ftdi.READ_BITS_PVE_LSB, num_bits - 1])
def read_bytes_cmd(self, length):
length -= 1
if length < 0 or length > 0xffff:
self._fatal('Invalid length')
self._set_output_enabled(False)
self._queue_cmd([Ftdi.READ_BYTES_PVE_LSB, length & 0xff, length >> 8])
def get_read_bytes(self, length):
return self._ftdi.read_data_bytes(length)
def get_read_fifo_size(self):
return self._ftdi_fifo_size
def start_sequence(self):
if self._sequence_cmd_buffer is not None:
self._fatal('Attempted to start a sequence while one is in progress')
self.send_cmds()
self._sequence_cmd_buffer = array('B')
def end_sequence(self):
if self._sequence_cmd_buffer is None:
self._fatal('No sequence started')
self._sequence_cmd_buffer = None

View file

@ -0,0 +1,287 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import struct
import time
class SerialWireDebugPort(object):
# debug port registers
DP_IDCODE_ADDR = 0x00
DP_ABORT_ADDR = 0x00
DP_CTRLSTAT_ADDR = 0x04
DP_SELECT_ADDR = 0x08
DP_RDBUFF_ADDR = 0x0c
# MEM-AP register
MEM_AP_CSW_ADDR = 0x0
MEM_AP_CSW_MASTER_DEBUG = (1 << 29)
MEM_AP_CSW_PRIVILEGED_MODE = (1 << 25)
MEM_AP_CSW_ADDRINCWORD = (1 << 4)
MEM_AP_CSW_SIZE8BITS = (0 << 1)
MEM_AP_CSW_SIZE32BITS = (1 << 1)
MEM_AP_TAR_ADDR = 0x4
MEM_AP_DRW_ADDR = 0xc
MEM_AP_IDR_VALUES = [0x24770011, 0x74770001]
def __init__(self, driver, reset=True):
self._driver = driver
self._swd_connected = False
self._reset = reset
self._pending_acks = 0
def close(self):
if self._swd_connected:
# power down the system and debug domains
self._write(self.DP_CTRLSTAT_ADDR, 0x00000000, is_access_port=False)
# send 1 byte worth of trailing bits since we're done communicating
self._driver.write_bits_cmd(0x00, 8)
# this makes the Saleae's SWD analyzer happy, and it's otherwise harmless
self._driver.write_bits_cmd(0x3, 2)
self._swd_connected = False
self._driver.close()
@staticmethod
def _fatal(message):
raise Exception('FATAL ERROR: {}'.format(message))
def _get_request_header(self, addr, is_read, is_access_port):
# the header consists of the following fields
# bit 0: start (always 1)
# bit 1: DebugPort (0) or AccessPort (1)
# bit 2: write (0) or read (1)
# bits 3-4: bits 2 and 3 of the address
# bit 5: parity bit such that bits 1-5 contain an even number of 1's
# bit 6: stop (always 0)
# bit 7: park (always 1)
header = 0x1
header |= (1 << 1) if is_access_port else 0
header |= (1 << 2) if is_read else 0
header |= ((addr & 0xf) >> 2) << 3
parity = 0
for i in range(1, 5):
parity += (header >> i) & 0x1
header |= (parity & 0x1) << 5
header |= 1 << 7
return header
def _send_request_header(self, addr, is_read, is_access_port):
self._driver.write_bytes_cmd([self._get_request_header(addr, is_read, is_access_port)])
def _check_write_acks(self):
if not self._pending_acks:
return
self._driver.send_cmds()
# the ACK is in the top 3 bits that we get from the FTDI read, so shift right by 5
for ack in [x >> 5 for x in self._driver.get_read_bytes(self._pending_acks)]:
if ack != 0x1:
self._fatal('ACK=0x{:02x}'.format(ack))
self._pending_acks = 0
def _read(self, addr, is_access_port):
# check any pending write ACKs before doing a read
self._check_write_acks()
# send the read request
self._send_request_header(addr, is_read=True, is_access_port=is_access_port)
# do all the reads at the same time as an optimization (and hope we get an ACK)
self._driver.read_bits_cmd(4) # 4 bits for ACK + turnaround
self._driver.read_bytes_cmd(4) # 4 data bytes
self._driver.read_bits_cmd(2) # 2 bits for parity + turnaround
self._driver.send_cmds()
result = self._driver.get_read_bytes(6)
# check the ACK
ack = result[0] >> 5
if ack != 0x1:
self._fatal('ACK=0x{:02x}'.format(ack))
# grab the response
response = struct.unpack('<I', result[1:5])[0]
# read two more bits: the parity and another for some reason I don't understand
# check that the parity is correct
parity = (result[5] >> 6) & 0x1
if parity != sum((response >> i) & 0x1 for i in range(32)) & 0x1:
self._fatal('Bad parity')
return response
def _write(self, addr, data, is_access_port, no_ack=False):
if data > 0xffffffff:
self._fatal('Bad data')
# send the write request
self._send_request_header(addr, is_read=False, is_access_port=is_access_port)
# OPTIMIZATION: queue the ACK read now and keep going (hope we get an ACK)
self._driver.read_bits_cmd(4)
# calculate the parity and send the data
parity = sum((data >> i) & 0x1 for i in range(32)) & 0x1
# OPTIMIZATION: We need to send 1 turnaround bit, 4 data bytes, and 1 parity bit.
# We can combine this into a single FTDI write by sending it as 5 bytes, so
# let's shift everything such that the extra 6 bits are at the end where they
# will be properly ignored as trailing bits.
temp = ((data << 1) & 0xfffffffe)
data_bytes = [(temp >> (i * 8)) & 0xff for i in range(4)]
data_bytes += [(data >> 31) | (parity << 1)]
self._driver.write_bytes_cmd(data_bytes)
# check the ACK(s) if necessary
self._pending_acks += 1
if not no_ack or self._pending_acks >= self._driver.get_read_fifo_size():
self._check_write_acks()
def connect(self):
if self._reset:
# reset the target
self._driver.reset_lo()
# switch from JTAG to SWD mode (based on what openocd does)
# - line reset
# - magic number of 0xE79E
# - line reset
# - 2 low bits for unknown reasons (maybe padding to nibbles?)
def line_reset():
# a line reset is 50 high bits (6 bytes + 2 bits)
self._driver.write_bytes_cmd([0xff] * 6)
self._driver.write_bits_cmd(0x3, 2)
line_reset()
self._driver.write_bytes_cmd([0x9e, 0xe7])
line_reset()
self._driver.write_bits_cmd(0x0, 2)
idcode = self._read(self.DP_IDCODE_ADDR, is_access_port=False)
# clear the error flags
self._write(self.DP_ABORT_ADDR, 0x0000001E, is_access_port=False)
# power up the system and debug domains
self._write(self.DP_CTRLSTAT_ADDR, 0xF0000001, is_access_port=False)
# check the MEM-AP IDR
# the IDR register is has the same address as the DRW register but on the 0xf bank
self._write(self.DP_SELECT_ADDR, 0xf0, is_access_port=False) # select the 0xf bank
self._read(self.MEM_AP_DRW_ADDR, is_access_port=True) # read the value register (twice)
if self._read(self.DP_RDBUFF_ADDR, is_access_port=False) not in self.MEM_AP_IDR_VALUES:
self._fatal('Invalid MEM-AP IDR')
self._write(self.DP_SELECT_ADDR, 0x0, is_access_port=False) # return to the 0x0 bank
# enable privileged access to the MEM-AP with 32 bit data accesses and auto-incrementing
csw_value = self.MEM_AP_CSW_PRIVILEGED_MODE
csw_value |= self.MEM_AP_CSW_MASTER_DEBUG
csw_value |= self.MEM_AP_CSW_ADDRINCWORD
csw_value |= self.MEM_AP_CSW_SIZE32BITS
self._write(self.MEM_AP_CSW_ADDR, csw_value, is_access_port=True)
self._swd_connected = True
if self._reset:
self._driver.reset_hi()
return idcode
def read_memory_address(self, addr):
self._write(self.MEM_AP_TAR_ADDR, addr, is_access_port=True)
self._read(self.MEM_AP_DRW_ADDR, is_access_port=True)
return self._read(self.DP_RDBUFF_ADDR, is_access_port=False)
def write_memory_address(self, addr, value):
self._write(self.MEM_AP_TAR_ADDR, addr, is_access_port=True)
self._write(self.MEM_AP_DRW_ADDR, value, is_access_port=True)
def write_memory_bulk(self, base_addr, data):
# TAR is configured as auto-incrementing, but it wraps every 4096 bytes, so that's how much
# we can write before we need to explicitly set it again.
WORD_SIZE = 4
BURST_LENGTH = 4096 / WORD_SIZE
assert(base_addr % BURST_LENGTH == 0 and len(data) % WORD_SIZE == 0)
for i in range(0, len(data), WORD_SIZE):
if i % BURST_LENGTH == 0:
# set the target address
self._write(self.MEM_AP_TAR_ADDR, base_addr + i, is_access_port=True, no_ack=True)
# write the word
word = sum(data[i+j] << (j * 8) for j in range(WORD_SIZE))
self._write(self.MEM_AP_DRW_ADDR, word, is_access_port=True, no_ack=True)
def continuous_read(self, addr, duration):
# This is a highly-optimized function which is samples the specified memory address for the
# specified duration. This is generally used for profiling by reading the PC sampling
# register.
NUM_READS = 510 # a magic number which gives us the best sample rate on Silk/Robert
# don't auto-increment the address
csw_value = self.MEM_AP_CSW_PRIVILEGED_MODE
csw_value |= self.MEM_AP_CSW_SIZE32BITS
self._write(self.MEM_AP_CSW_ADDR, csw_value, is_access_port=True)
# set the address
self._write(self.MEM_AP_TAR_ADDR, addr, is_access_port=True)
# discard the previous value
self._read(self.MEM_AP_DRW_ADDR, is_access_port=True)
# flush everything
self._check_write_acks()
header = self._get_request_header(self.MEM_AP_DRW_ADDR, is_read=True, is_access_port=True)
self._driver.start_sequence()
for i in range(NUM_READS):
self._driver.write_bits_cmd(header, 8)
self._driver.read_bits_cmd(6)
self._driver.read_bytes_cmd(4)
raw_data = []
end_time = time.time() + duration
while time.time() < end_time:
# send the read requests
self._driver.send_cmds()
# do all the reads at the same time as an optimization (and hope we get an ACK)
raw_data.extend(self._driver.get_read_bytes(5 * NUM_READS))
self._driver.end_sequence()
def get_value_from_bits(b):
result = 0
for o in range(len(b)):
result |= b[o] << o
return result
values = []
for raw_result in [raw_data[i:i+5] for i in range(0, len(raw_data), 5)]:
result = raw_result
# The result is read as 5 bytes, with the first one containing 6 bits (shifted in from
# the left as they are read). Let's convert this into an array of bits, and then
# reconstruct the values we care about.
bits = []
bits.extend((result[0] >> (2 + j)) & 1 for j in range(6))
for i in range(4):
bits.extend((result[i + 1] >> j) & 1 for j in range(8))
ack = get_value_from_bits(bits[1:4])
response = get_value_from_bits(bits[4:36])
parity = bits[36]
# check the ACK
if ack != 0x1:
self._fatal('ACK=0x{:02x}'.format(ack))
# read two more bits: the parity and another for some reason I don't understand
# check that the parity is correct
if parity != sum((response >> i) & 0x1 for i in range(32)) & 0x1:
self._fatal('Bad parity')
# store the response
values.append(response)
return values

View file

@ -0,0 +1,16 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .stm32f4 import STM32F4FlashProgrammer
from .stm32f7 import STM32F7FlashProgrammer

View file

@ -0,0 +1,352 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import array
import logging
import os
import struct
import time
from intelhex import IntelHex
LOG = logging.getLogger(__name__)
class STM32FlashProgrammer(object):
# CPUID register
CPUID_ADDR = 0xE000ED00
# Flash constants
FLASH_BASE_ADDR = 0x08000000
# Flash key register (FLASH_KEYR)
FLASH_KEYR_ADDR = 0x40023C04
FLASH_KEYR_VAL1 = 0x45670123
FLASH_KEYR_VAL2 = 0xCDEF89AB
# Flash status register (FLASH_SR)
FLASH_SR_ADDR = 0x40023C0C
FLASH_SR_BSY = (1 << 16)
# Flash control register (FLASH_CR)
FLASH_CR_ADDR = 0x40023C10
FLASH_CR_PG = (1 << 0)
FLASH_CR_SER = (1 << 1)
FLASH_CR_SNB_OFFSET = 3
FLASH_CR_PSIZE_8BIT = (0x0 << 8)
FLASH_CR_PSIZE_16BIT = (0x1 << 8)
FLASH_CR_PSIZE_32BIT = (0x2 << 8)
FLASH_CR_STRT = (1 << 16)
# Debug halting control and status register (DHCSR)
DHCSR_ADDR = 0xE000EDF0
DHCSR_DBGKEY_VALUE = 0xA05F0000
DHCSR_HALT = (1 << 0)
DHCSR_DEBUGEN = (1 << 1)
DHCSR_S_REGRDY = (1 << 16)
DHCSR_S_LOCKUP = (1 << 19)
# Application interrupt and reset control register (AIRCR)
AIRCR_ADDR = 0xE000ED0C
AIRCR_VECTKEY_VALUE = 0x05FA0000
AIRCR_SYSRESETREQ = (1 << 2)
# Debug Core Register Selector Register (DCRSR)
DCRSR_ADDR = 0xE000EDF4
DCRSR_WRITE = (1 << 16)
# Debug Core Register Data register (DCRDR)
DCRDR_ADDR = 0xE000EDF8
# Debug Exception and Monitor Control register (DEMCR)
DEMCR_ADDR = 0xE000EDFC
DEMCR_RESET_CATCH = (1 << 0)
DEMCR_TRCENA = (1 << 24)
# Program Counter Sample Register (PCSR)
PCSR_ADDR = 0xE000101C
# Loader addresses
PBLLDR_HEADER_ADDR = 0x20000400
PBLLDR_HEADER_OFFSET = PBLLDR_HEADER_ADDR + 0x4
PBLLDR_HEADER_LENGTH = PBLLDR_HEADER_ADDR + 0x8
PBLLDR_DATA_ADDR = 0x20000800
PBLLDR_DATA_MAX_LENGTH = 0x20000
PBLLDR_STATE_WAIT = 0
PBLLDR_STATE_WRITE = 1
PBLLDR_STATE_CRC = 2
# SRAM base addr
SRAM_BASE_ADDR = 0x20000000
def __init__(self, driver):
self._driver = driver
self._step_start_time = 0
self.FLASH_SECTOR_SIZES = [x*1024 for x in self.FLASH_SECTOR_SIZES]
def __enter__(self):
try:
self.connect()
return self
except:
self.close()
raise
def __exit__(self, exc, value, trace):
self.close()
def _fatal(self, message):
raise Exception('FATAL ERROR: {}'.format(message))
def _start_step(self, msg):
LOG.info(msg)
self._step_start_time = time.time()
def _end_step(self, msg, no_time=False, num_bytes=None):
total_time = round(time.time() - self._step_start_time, 2)
if not no_time:
msg += ' in {}s'.format(total_time)
if num_bytes:
kibps = round(num_bytes / 1024.0 / total_time, 2)
msg += ' ({} KiB/s)'.format(kibps)
LOG.info(msg)
def connect(self):
self._start_step('Connecting...')
# connect and check the IDCODE
if self._driver.connect() != self.IDCODE:
self._fatal('Invalid IDCODE')
# check the CPUID register
if self._driver.read_memory_address(self.CPUID_ADDR) != self.CPUID_VALUE:
self._fatal('Invalid CPU ID')
self._end_step('Connected', no_time=True)
def halt_core(self):
# halt the core immediately
dhcsr_value = self.DHCSR_DBGKEY_VALUE | self.DHCSR_DEBUGEN | self.DHCSR_HALT
self._driver.write_memory_address(self.DHCSR_ADDR, dhcsr_value)
def resume_core(self):
# resume the core
dhcsr_value = self.DHCSR_DBGKEY_VALUE
self._driver.write_memory_address(self.DHCSR_ADDR, dhcsr_value)
def reset_core(self, halt=False):
if self._driver.read_memory_address(self.DHCSR_ADDR) & self.DHCSR_S_LOCKUP:
# halt the core first to clear the lockup
LOG.info('Clearing lockup condition')
self.halt_core()
# enable reset vector catch
demcr_value = 0
if halt:
demcr_value |= self.DEMCR_RESET_CATCH
self._driver.write_memory_address(self.DEMCR_ADDR, demcr_value)
self._driver.read_memory_address(self.DHCSR_ADDR)
# reset the core
aircr_value = self.AIRCR_VECTKEY_VALUE | self.AIRCR_SYSRESETREQ
self._driver.write_memory_address(self.AIRCR_ADDR, aircr_value)
if halt:
self.halt_core()
def unlock_flash(self):
# unlock the flash
self._driver.write_memory_address(self.FLASH_KEYR_ADDR, self.FLASH_KEYR_VAL1)
self._driver.write_memory_address(self.FLASH_KEYR_ADDR, self.FLASH_KEYR_VAL2)
def _poll_register(self, timeout=0.5):
end_time = time.time() + timeout
while end_time > time.time():
val = self._driver.read_memory_address(self.DHCSR_ADDR)
if val & self.DHCSR_S_REGRDY:
break
else:
raise Exception('Register operation was not confirmed')
def write_register(self, reg, val):
self._driver.write_memory_address(self.DCRDR_ADDR, val)
reg |= self.DCRSR_WRITE
self._driver.write_memory_address(self.DCRSR_ADDR, reg)
self._poll_register()
def read_register(self, reg):
self._driver.write_memory_address(self.DCRSR_ADDR, reg)
self._poll_register()
return self._driver.read_memory_address(self.DCRDR_ADDR)
def erase_flash(self, flash_offset, length):
self._start_step('Erasing...')
def overlap(a1, a2, b1, b2):
return max(a1, b1) < min(a2, b2)
# find all the sectors which we need to erase
erase_sectors = []
for i, size in enumerate(self.FLASH_SECTOR_SIZES):
addr = self.FLASH_BASE_ADDR + sum(self.FLASH_SECTOR_SIZES[:i])
if overlap(flash_offset, flash_offset+length, addr, addr+size):
erase_sectors += [i]
if not erase_sectors:
self._fatal('Could not find sectors to erase!')
# erase the sectors
for sector in erase_sectors:
# start the erase
reg_value = (sector << self.FLASH_CR_SNB_OFFSET)
reg_value |= self.FLASH_CR_PSIZE_8BIT
reg_value |= self.FLASH_CR_STRT
reg_value |= self.FLASH_CR_SER
self._driver.write_memory_address(self.FLASH_CR_ADDR, reg_value)
# wait for the erase to finish
while self._driver.read_memory_address(self.FLASH_SR_ADDR) & self.FLASH_SR_BSY:
time.sleep(0)
self._end_step('Erased')
def close(self):
self._driver.close()
def _write_loader_state(self, state):
self._driver.write_memory_address(self.PBLLDR_HEADER_ADDR, state)
def _wait_loader_state(self, wanted_state, timeout=3):
end_time = time.time() + timeout
state = -1
while time.time() < end_time:
time.sleep(0)
state = self._driver.read_memory_address(self.PBLLDR_HEADER_ADDR)
if state == wanted_state:
break
else:
raise Exception("Timed out waiting for loader state %d, got %d" % (wanted_state, state))
@staticmethod
def _chunks(l, n):
for i in xrange(0, len(l), n):
yield l[i:i+n], len(l[i:i+n]), i
def execute_loader(self):
# reset and halt the core
self.reset_core(halt=True)
with open(os.path.join(os.path.dirname(__file__), "loader.bin")) as f:
loader_bin = f.read()
# load loader binary into SRAM
self._driver.write_memory_bulk(self.SRAM_BASE_ADDR, array.array('B', loader_bin))
# set PC based on value in loader
reg_sp, = struct.unpack("<I", loader_bin[:4])
self.write_register(13, reg_sp)
# set PC to new reset handler
pc, = struct.unpack('<I', loader_bin[4:8])
self.write_register(15, pc)
# unlock flash
self.unlock_flash()
self.resume_core()
@staticmethod
def generate_crc(data):
length = len(data)
lookup_table = [0, 47, 94, 113, 188, 147, 226, 205, 87, 120, 9, 38, 235, 196, 181, 154]
crc = 0
for i in xrange(length*2):
nibble = data[i / 2]
if i % 2 == 0:
nibble >>= 4
index = nibble ^ (crc >> 4)
crc = lookup_table[index & 0xf] ^ ((crc << 4) & 0xf0)
return crc
def read_crc(self, addr, length):
self._driver.write_memory_address(self.PBLLDR_HEADER_OFFSET, addr)
self._driver.write_memory_address(self.PBLLDR_HEADER_LENGTH, length)
self._write_loader_state(self.PBLLDR_STATE_CRC)
self._wait_loader_state(self.PBLLDR_STATE_WAIT)
return self._driver.read_memory_address(self.PBLLDR_DATA_ADDR) & 0xFF
def load_hex(self, hex_path):
self._start_step("Loading binary: %s" % hex_path)
ih = IntelHex(hex_path)
offset = ih.minaddr()
data = ih.tobinarray()
self.load_bin(offset, data)
self._end_step("Loaded binary", num_bytes=len(data))
def load_bin(self, offset, data):
while len(data) % 4 != 0:
data.append(0xFF)
length = len(data)
# prepare the flash for programming
self.erase_flash(offset, length)
cr_value = self.FLASH_CR_PSIZE_8BIT | self.FLASH_CR_PG
self._driver.write_memory_address(self.FLASH_CR_ADDR, cr_value)
# set the base address
self._wait_loader_state(self.PBLLDR_STATE_WAIT)
self._driver.write_memory_address(self.PBLLDR_HEADER_OFFSET, offset)
for chunk, chunk_length, pos in self._chunks(data, self.PBLLDR_DATA_MAX_LENGTH):
LOG.info("Written %d/%d", pos, length)
self._driver.write_memory_address(self.PBLLDR_HEADER_LENGTH, chunk_length)
self._driver.write_memory_bulk(self.PBLLDR_DATA_ADDR, chunk)
self._write_loader_state(self.PBLLDR_STATE_WRITE)
self._wait_loader_state(self.PBLLDR_STATE_WAIT)
expected_crc = self.generate_crc(data)
actual_crc = self.read_crc(offset, length)
if actual_crc != expected_crc:
raise Exception("Bad CRC, expected %d, found %d" % (expected_crc, actual_crc))
LOG.info("CRC-8 matched: %d", actual_crc)
def profile(self, duration):
LOG.info('Collecting %f second(s) worth of samples...', duration)
# ensure DWT is enabled so we can get PC samples from PCSR
demcr_value = self._driver.read_memory_address(self.DEMCR_ADDR)
self._driver.write_memory_address(self.DEMCR_ADDR, demcr_value | self.DEMCR_TRCENA)
# take the samples
samples = self._driver.continuous_read(self.PCSR_ADDR, duration)
# restore the original DEMCR value
self._driver.write_memory_address(self.DEMCR_ADDR, demcr_value)
# process the samples
pcs = dict()
for sample in samples:
sample = '0x%08x' % sample
pcs[sample] = pcs.get(sample, 0) + 1
LOG.info('Collected %d samples!', len(samples))
return pcs

View file

@ -0,0 +1,21 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from stm32 import STM32FlashProgrammer
class STM32F4FlashProgrammer(STM32FlashProgrammer):
IDCODE = 0x2BA01477
CPUID_VALUE = 0x410FC241
FLASH_SECTOR_SIZES = [16, 16, 16, 16, 64, 128, 128, 128, 128, 128, 128, 128]

View file

@ -0,0 +1,21 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from stm32 import STM32FlashProgrammer
class STM32F7FlashProgrammer(STM32FlashProgrammer):
IDCODE = 0x5BA02477
CPUID_VALUE = 0x411FC270
FLASH_SECTOR_SIZES = [32, 32, 32, 32, 128, 256, 256, 256, 256, 256, 256, 256]