mirror of
https://github.com/google/pebble.git
synced 2025-05-18 01:14:55 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
93
python_libs/pblprog/.gitignore
vendored
Normal file
93
python_libs/pblprog/.gitignore
vendored
Normal 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
|
16
python_libs/pblprog/README.md
Normal file
16
python_libs/pblprog/README.md
Normal 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
|
116
python_libs/pblprog/loader/src/irq_stm32f2_f4.def
Normal file
116
python_libs/pblprog/loader/src/irq_stm32f2_f4.def
Normal 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)
|
112
python_libs/pblprog/loader/src/main.c
Normal file
112
python_libs/pblprog/loader/src/main.c
Normal 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
|
||||
};
|
136
python_libs/pblprog/loader/src/stm32f4_loader.ld
Normal file
136
python_libs/pblprog/loader/src/stm32f4_loader.ld
Normal 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) }
|
||||
}
|
29
python_libs/pblprog/loader/waftools/ldscript.py
Normal file
29
python_libs/pblprog/loader/waftools/ldscript.py
Normal 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)
|
84
python_libs/pblprog/loader/waftools/objcopy.py
Normal file
84
python_libs/pblprog/loader/waftools/objcopy.py
Normal 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')
|
63
python_libs/pblprog/loader/wscript
Normal file
63
python_libs/pblprog/loader/wscript
Normal 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
|
15
python_libs/pblprog/pebble/__init__.py
Normal file
15
python_libs/pblprog/pebble/__init__.py
Normal 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__)
|
44
python_libs/pblprog/pebble/programmer/__init__.py
Normal file
44
python_libs/pblprog/pebble/programmer/__init__.py
Normal 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()
|
||||
|
38
python_libs/pblprog/pebble/programmer/__main__.py
Normal file
38
python_libs/pblprog/pebble/programmer/__main__.py
Normal 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()
|
||||
|
137
python_libs/pblprog/pebble/programmer/ftdi_swd.py
Normal file
137
python_libs/pblprog/pebble/programmer/ftdi_swd.py
Normal 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
|
||||
|
287
python_libs/pblprog/pebble/programmer/swd_port.py
Normal file
287
python_libs/pblprog/pebble/programmer/swd_port.py
Normal 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
|
||||
|
16
python_libs/pblprog/pebble/programmer/targets/__init__.py
Normal file
16
python_libs/pblprog/pebble/programmer/targets/__init__.py
Normal 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
|
352
python_libs/pblprog/pebble/programmer/targets/stm32.py
Normal file
352
python_libs/pblprog/pebble/programmer/targets/stm32.py
Normal 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
|
||||
|
21
python_libs/pblprog/pebble/programmer/targets/stm32f4.py
Normal file
21
python_libs/pblprog/pebble/programmer/targets/stm32f4.py
Normal 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]
|
21
python_libs/pblprog/pebble/programmer/targets/stm32f7.py
Normal file
21
python_libs/pblprog/pebble/programmer/targets/stm32f7.py
Normal 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]
|
44
python_libs/pblprog/setup.py
Normal file
44
python_libs/pblprog/setup.py
Normal 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
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue