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

93
python_libs/pblprog/.gitignore vendored Normal file
View file

@ -0,0 +1,93 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# IPython Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
.waf*
.lock*
*.swp

View file

@ -0,0 +1,16 @@
# pblprog
Utility for flashing Pebble BigBoards over SWD
Installation
------------
Install from PyPi (https://pebbletechnology.atlassian.net/wiki/display/DEV/pypi) under the package name `pebble.programmer`.
Supported devices
-----------------
It is relatively easy to add support for any STM32/SWD based Pebble BigBoard. Currently supported are:
- silk_bb
- robert_bb

View file

@ -0,0 +1,116 @@
IRQ_DEF(0, WWDG) // Window WatchDog
IRQ_DEF(1, PVD) // PVD through EXTI Line detection
IRQ_DEF(2, TAMP_STAMP) // Tamper and TimeStamps through the EXTI line
IRQ_DEF(3, RTC_WKUP) // RTC Wakeup through the EXTI line
IRQ_DEF(4, FLASH) // FLASH
IRQ_DEF(5, RCC) // RCC
IRQ_DEF(6, EXTI0) // EXTI Line0
IRQ_DEF(7, EXTI1) // EXTI Line1
IRQ_DEF(8, EXTI2) // EXTI Line2
IRQ_DEF(9, EXTI3) // EXTI Line3
IRQ_DEF(10, EXTI4) // EXTI Line4
IRQ_DEF(11, DMA1_Stream0) // DMA1 Stream 0
IRQ_DEF(12, DMA1_Stream1) // DMA1 Stream 1
IRQ_DEF(13, DMA1_Stream2) // DMA1 Stream 2
IRQ_DEF(14, DMA1_Stream3) // DMA1 Stream 3
IRQ_DEF(15, DMA1_Stream4) // DMA1 Stream 4
IRQ_DEF(16, DMA1_Stream5) // DMA1 Stream 5
IRQ_DEF(17, DMA1_Stream6) // DMA1 Stream 6
IRQ_DEF(18, ADC) // ADC1, ADC2 and ADC3s
IRQ_DEF(19, CAN1_TX) // CAN1 TX
IRQ_DEF(20, CAN1_RX0) // CAN1 RX0
IRQ_DEF(21, CAN1_RX1) // CAN1 RX1
IRQ_DEF(22, CAN1_SCE) // CAN1 SCE
IRQ_DEF(23, EXTI9_5) // External Line[9:5]s
IRQ_DEF(24, TIM1_BRK_TIM9) // TIM1 Break and TIM9
IRQ_DEF(25, TIM1_UP_TIM10) // TIM1 Update and TIM10
IRQ_DEF(26, TIM1_TRG_COM_TIM11) // TIM1 Trigger and Commutation and TIM11
IRQ_DEF(27, TIM1_CC) // TIM1 Capture Compare
IRQ_DEF(28, TIM2) // TIM2
IRQ_DEF(29, TIM3) // TIM3
IRQ_DEF(30, TIM4) // TIM4
IRQ_DEF(31, I2C1_EV) // I2C1 Event
IRQ_DEF(32, I2C1_ER) // I2C1 Error
IRQ_DEF(33, I2C2_EV) // I2C2 Event
IRQ_DEF(34, I2C2_ER) // I2C2 Error
IRQ_DEF(35, SPI1) // SPI1
IRQ_DEF(36, SPI2) // SPI2
IRQ_DEF(37, USART1) // USART1
IRQ_DEF(38, USART2) // USART2
IRQ_DEF(39, USART3) // USART3
IRQ_DEF(40, EXTI15_10) // External Line[15:10]s
IRQ_DEF(41, RTC_Alarm) // RTC Alarm (A and B) through EXTI Line
IRQ_DEF(42, OTG_FS_WKUP) // USB OTG FS Wakeup through EXTI line
IRQ_DEF(43, TIM8_BRK_TIM12) // TIM8 Break and TIM12
IRQ_DEF(44, TIM8_UP_TIM13) // TIM8 Update and TIM13
IRQ_DEF(45, TIM8_TRG_COM_TIM14) // TIM8 Trigger and Commutation and TIM14
IRQ_DEF(46, TIM8_CC) // TIM8 Capture Compare
IRQ_DEF(47, DMA1_Stream7) // DMA1 Stream7
IRQ_DEF(48, FSMC) // FSMC
IRQ_DEF(49, SDIO) // SDIO
IRQ_DEF(50, TIM5) // TIM5
IRQ_DEF(51, SPI3) // SPI3
#if !defined(STM32F412xG)
IRQ_DEF(52, UART4) // UART4
IRQ_DEF(53, UART5) // UART5
IRQ_DEF(54, TIM6_DAC) // TIM6 and DAC1&2 underrun errors
#else
IRQ_DEF(54, TIM6) // TIM6
#endif
IRQ_DEF(55, TIM7) // TIM7
IRQ_DEF(56, DMA2_Stream0) // DMA2 Stream 0
IRQ_DEF(57, DMA2_Stream1) // DMA2 Stream 1
IRQ_DEF(58, DMA2_Stream2) // DMA2 Stream 2
IRQ_DEF(59, DMA2_Stream3) // DMA2 Stream 3
IRQ_DEF(60, DMA2_Stream4) // DMA2 Stream 4
#if !defined(STM32F412xG)
IRQ_DEF(61, ETH) // Ethernet
IRQ_DEF(62, ETH_WKUP) // Ethernet Wakeup through EXTI line
#else
IRQ_DEF(61, DFSDM1) // DFSDM1
IRQ_DEF(62, DFSDM2) // DFSDM2
#endif
IRQ_DEF(63, CAN2_TX) // CAN2 TX
IRQ_DEF(64, CAN2_RX0) // CAN2 RX0
IRQ_DEF(65, CAN2_RX1) // CAN2 RX1
IRQ_DEF(66, CAN2_SCE) // CAN2 SCE
IRQ_DEF(67, OTG_FS) // USB OTG FS
IRQ_DEF(68, DMA2_Stream5) // DMA2 Stream 5
IRQ_DEF(69, DMA2_Stream6) // DMA2 Stream 6
IRQ_DEF(70, DMA2_Stream7) // DMA2 Stream 7
IRQ_DEF(71, USART6) // USART6
IRQ_DEF(72, I2C3_EV) // I2C3 event
IRQ_DEF(73, I2C3_ER) // I2C3 error
#if !defined(STM32F412xG)
IRQ_DEF(74, OTG_HS_EP1_OUT) // USB OTG HS End Point 1 Out
IRQ_DEF(75, OTG_HS_EP1_IN) // USB OTG HS End Point 1 In
IRQ_DEF(76, OTG_HS_WKUP) // USB OTG HS Wakeup through EXTI
IRQ_DEF(77, OTG_HS) // USB OTG HS
IRQ_DEF(78, DCMI) // DCMI
IRQ_DEF(79, CRYP) // CRYP crypto
#endif
#if !defined(STM32F412xG)
IRQ_DEF(80, HASH_RNG) // Hash and Rng
#else
IRQ_DEF(80, RNG) // Rng
#endif
#if !defined(STM32F2XX) // STM32F2 IRQs end here
IRQ_DEF(81, FPU) // FPU
#if !defined(STM32F412xG)
IRQ_DEF(82, UART7) // UART7
IRQ_DEF(83, UART8) // UART8
#endif
IRQ_DEF(84, SPI4) // SPI4
IRQ_DEF(85, SPI5) // SPI5
#if !defined(STM32F412xG)
IRQ_DEF(86, SPI6) // SPI6
IRQ_DEF(87, SAI1) // SAI1
IRQ_DEF(88, LTDC) // LTDC
IRQ_DEF(89, LTDC_ER) // LTDC_ER
IRQ_DEF(90, DMA2D) // DMA2D
#else
IRQ_DEF(92, QUADSPI) // QUADSPI
IRQ_DEF(95, FMPI2C1_EV) // FMPI2C1 Event
IRQ_DEF(96, FMPI2C1_ER) // FMPI2C1 Error
#endif
#endif // !defined(STM32F2XX)

View file

@ -0,0 +1,112 @@
/*
* 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.
*/
#include <stdint.h>
#define HEADER_ADDR (0x20000400)
#define DATA_ADDR (0x20000800)
#define FLASH_SR_ADDR (0x40023C0C)
#define STATE_WAITING (0)
#define STATE_WRITE (1)
#define STATE_CRC (2)
// typedef to make it easy to change the program size
typedef uint8_t p_size_t;
typedef struct __attribute__((__packed__)) {
uint32_t state;
volatile p_size_t *addr;
uint32_t length;
} Header;
static uint8_t prv_crc8(const uint8_t *data, uint32_t data_len) {
uint8_t crc = 0;
// nibble lookup table for (x^8 + x^5 + x^3 + x^2 + x + 1)
static const uint8_t lookup_table[] =
{ 0, 47, 94, 113, 188, 147, 226, 205, 87, 120, 9, 38, 235, 196, 181, 154 };
for (uint32_t i = 0; i < data_len * 2; i++) {
uint8_t nibble = data[i / 2];
if (i % 2 == 0) {
nibble >>= 4;
}
uint8_t index = nibble ^ (crc >> 4);
crc = lookup_table[index & 0xf] ^ ((crc << 4) & 0xf0);
}
return crc;
}
static void prv_wait_for_flash_not_busy(void) {
while ((*(volatile uint32_t *)FLASH_SR_ADDR) & (1 << 16)); // BSY flag in FLASH_SR
}
__attribute__((__noreturn__)) void Reset_Handler(void) {
// Disable all interrupts
__asm__("cpsid i" : : : "memory");
volatile uint32_t *flash_sr = (volatile uint32_t *)FLASH_SR_ADDR;
volatile p_size_t *data = (volatile p_size_t *)DATA_ADDR;
volatile Header *header = (volatile Header *)HEADER_ADDR;
header->state = STATE_WAITING;
while(1) {
switch (header->state) {
case STATE_WRITE:
prv_wait_for_flash_not_busy();
for (uint32_t i = 0; i < header->length / sizeof(p_size_t); i++) {
header->addr[i] = data[i];
__asm__("isb 0xF":::"memory");
__asm__("dsb 0xF":::"memory");
/// Wait until flash isn't busy
prv_wait_for_flash_not_busy();
if (*flash_sr & (0x1f << 4)) {
// error raised, set bad state
header->state = *flash_sr;
}
if (header->addr[i] != data[i]) {
header->state = 0xbd;
}
}
header->addr += header->length / sizeof(p_size_t);
header->state = STATE_WAITING;
break;
case STATE_CRC:
*data = prv_crc8((uint8_t *)header->addr, header->length);
header->state = STATE_WAITING;
break;
default:
break;
}
}
__builtin_unreachable();
}
//! These symbols are defined in the linker script for use in initializing
//! the data sections. uint8_t since we do arithmetic with section lengths.
//! These are arrays to avoid the need for an & when dealing with linker symbols.
extern uint8_t _estack[];
__attribute__((__section__(".isr_vector"))) const void * const vector_table[] = {
_estack,
Reset_Handler
};

View file

@ -0,0 +1,136 @@
__Stack_Size = 128;
PROVIDE ( _Stack_Size = __Stack_Size ) ;
__Stack_Init = _estack - __Stack_Size ;
PROVIDE ( _Stack_Init = __Stack_Init ) ;
MEMORY
{
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 1K
}
SECTIONS
{
/* for Cortex devices, the beginning of the startup code is stored in the .isr_vector section, which goes to FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >RAM
/* for some STRx devices, the beginning of the startup code is stored in the .flashtext section, which goes to FLASH */
.flashtext :
{
. = ALIGN(4);
*(.flashtext) /* Startup code */
. = ALIGN(4);
} >RAM
/* Exception handling sections. "contains index entries for section unwinding" */
.ARM.exidx :
{
. = ALIGN(4);
*(.ARM.exidx)
. = ALIGN(4);
} >RAM
/* the program code is stored in the .text section, which goes to Flash */
.text :
{
. = ALIGN(4);
*(.text) /* remaining code */
*(.text.*) /* remaining code */
*(.rodata) /* read-only data (constants) */
*(.rodata*)
*(.constdata) /* read-only data (constants) */
*(.constdata*)
*(.glue_7)
*(.glue_7t)
*(i.*)
. = ALIGN(4);
} >RAM
/* This is the initialized data section
The program executes knowing that the data is in the RAM
but the loader puts the initial values in the FLASH (inidata).
It is one task of the startup to copy the initial values from FLASH to RAM. */
.data : {
. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */
__data_start = .;
*(.data)
*(.data.*)
. = ALIGN(4);
__data_end = .; /* This is used by the startup in order to initialize the .data secion */
} >RAM
__data_load_start = LOADADDR(.data);
/* This is the uninitialized data section */
.bss (NOLOAD) : {
. = ALIGN(4);
__bss_start = .; /* This is used by the startup in order to initialize the .bss secion */
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
__bss_end = .; /* This is used by the startup in order to initialize the .bss secion */
} >RAM
.stack (NOLOAD) : {
. = ALIGN(8);
_sstack = .;
. = . + __Stack_Size;
. = ALIGN(8);
_estack = .;
} >RAM
/* after that it's only debugging information. */
/* remove the debugging information from the standard libraries */
DISCARD : {
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
}

View file

@ -0,0 +1,29 @@
# 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 waflib import Utils, Errors
from waflib.TaskGen import after, feature
@after('apply_link')
@feature('cprogram', 'cshlib')
def process_ldscript(self):
if not getattr(self, 'ldscript', None) or self.env.CC_NAME != 'gcc':
return
node = self.path.find_resource(self.ldscript)
if not node:
raise Errors.WafError('could not find %r' % self.ldscript)
self.link_task.env.append_value('LINKFLAGS', '-T%s' % node.abspath())
self.link_task.dep_nodes.append(node)

View file

@ -0,0 +1,84 @@
#!/usr/bin/python
# 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.
# Grygoriy Fuchedzhy 2010
"""
Support for converting linked targets to ihex, srec or binary files using
objcopy. Use the 'objcopy' feature in conjuction with the 'cc' or 'cxx'
feature. The 'objcopy' feature uses the following attributes:
objcopy_bfdname Target object format name (eg. ihex, srec, binary).
Defaults to ihex.
objcopy_target File name used for objcopy output. This defaults to the
target name with objcopy_bfdname as extension.
objcopy_install_path Install path for objcopy_target file. Defaults to ${PREFIX}/fw.
objcopy_flags Additional flags passed to objcopy.
"""
from waflib.Utils import def_attrs
from waflib import Task
from waflib.TaskGen import feature, after_method
class objcopy(Task.Task):
run_str = '${OBJCOPY} -O ${TARGET_BFDNAME} ${OBJCOPYFLAGS} ${SRC} ${TGT}'
color = 'CYAN'
@feature('objcopy')
@after_method('apply_link')
def objcopy(self):
def_attrs(self,
objcopy_bfdname='ihex',
objcopy_target=None,
objcopy_install_path="${PREFIX}/firmware",
objcopy_flags='')
link_output = self.link_task.outputs[0]
if not self.objcopy_target:
self.objcopy_target = link_output.change_ext('.' + self.objcopy_bfdname).name
elif isinstance(self.objcopy_target, str):
self.objcopy_target = self.path.find_or_declare(self.objcopy_target)
task = self.create_task('objcopy',
src=link_output,
tgt=self.objcopy_target)
task.env.append_unique('TARGET_BFDNAME', self.objcopy_bfdname)
try:
task.env.append_unique('OBJCOPYFLAGS', getattr(self, 'objcopy_flags'))
except AttributeError:
pass
if self.objcopy_install_path:
self.bld.install_files(self.objcopy_install_path,
task.outputs[0],
env=task.env.derive())
def configure(ctx):
objcopy = ctx.find_program('objcopy', var='OBJCOPY', mandatory=True)
def objcopy_simple(task, mode):
return task.exec_command('arm-none-eabi-objcopy -S -R .stack -R .priv_bss'
' -R .bss -O %s "%s" "%s"' %
(mode, task.inputs[0].abspath(), task.outputs[0].abspath()))
def objcopy_simple_bin(task):
return objcopy_simple(task, 'binary')

View file

@ -0,0 +1,63 @@
# Build script for the silk loader
import sys
import os
from waflib import Logs
def options(opt):
opt.load('gcc')
def configure(conf):
# Find our binary tools
conf.find_program('arm-none-eabi-gcc', var='CC', mandatory=True)
conf.env.AS = conf.env.CC
conf.find_program('arm-none-eabi-gcc-ar', var='AR', mandatory=True)
conf.load('gcc')
for tool in 'ar objcopy'.split():
conf.find_program('arm-none-eabi-' + tool, var=tool.upper(), mandatory=True)
# Set up our compiler configuration
CPU_FLAGS = ['-mcpu=cortex-m3', '-mthumb']
OPT_FLAGS = ['-Os', '-g']
C_FLAGS = [
'-std=c11', '-ffunction-sections',
'-Wall', '-Wextra', '-Werror', '-Wpointer-arith',
'-Wno-unused-parameter', '-Wno-missing-field-initializers',
'-Wno-error=unused-function', '-Wno-error=unused-variable',
'-Wno-error=unused-parameter', '-Wno-error=unused-but-set-variable',
'-Wno-packed-bitfield-compat'
]
LINK_FLAGS = ['-Wl,--gc-sections', '-specs=nano.specs']
conf.env.append_unique('CFLAGS', CPU_FLAGS + OPT_FLAGS + C_FLAGS)
conf.env.append_unique('LINKFLAGS', LINK_FLAGS + CPU_FLAGS + OPT_FLAGS)
conf.env.append_unique('DEFINES', ['_REENT_SMALL=1'])
# Load up other waftools that we need
conf.load('objcopy ldscript', tooldir='waftools')
def build(bld):
elf_node = bld.path.get_bld().make_node('loader.elf')
linkflags = ['-Wl,-Map,loader.map']
sources = ['src/**/*.c']
includes = ['src']
bld.program(features="objcopy",
source=bld.path.ant_glob(sources),
includes=includes,
target=elf_node,
ldscript='src/stm32f4_loader.ld',
linkflags=linkflags,
objcopy_bfdname='ihex',
objcopy_target=elf_node.change_ext('.hex'))
import objcopy
bld(rule=objcopy.objcopy_simple_bin, source='loader.elf', target='loader.bin')
# vim:filetype=python

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]

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.
# Always prefer setuptools over distutils
from setuptools import setup, find_packages
setup(
name='pebble.programmer',
version='0.0.3',
description='Pebble Programmer',
url='https://github.com/pebble/pblprog',
author='Pebble Technology Corporation',
author_email='liam@pebble.com',
packages=find_packages(exclude=['contrib', 'docs', 'tests']),
namespace_packages=['pebble'],
install_requires=[
'intelhex>=2.1,<3',
'pyftdi==0.10.5'
],
package_data={
'pebble.programmer.targets': ['loader.bin']
},
entry_points={
'console_scripts': [
'pblprog = pebble.programmer.__main__:main',
],
},
zip_safe=False
)