diff --git a/flashstub/README b/flashstub/README index 05172a4..90d164c 100644 --- a/flashstub/README +++ b/flashstub/README @@ -1,5 +1,19 @@ -These are the assembler routines for executing a flash write -on the supported targets. They are kept here for reference, but -are not used, as the compiled binary code is included in the -target drivers. +Flash Stubs +=========== +For most of the targets, these are assembler routines for executing +a flash write on the supported targets. They are kept here for +reference, but are not used, as the compiled binary code is included +in the target drivers. + +For the STM32l0x, the stubs are written in C++ and emitted as arrays +of half-words for inclusion in the target driver. The use of a higher +level language allows more detailed code and for easy revisions. +These stubs communicate with the driver through a structure defined in +the src/include/stm32l0-nvm.h header. + +The dump-to-array.sh helper script uses sed to transform the output of +'objdump -d' into a half-word array of the instructions that may be +included in C code to declare the stub. FWIW, objcopy doesn't produce +the same output as objdump. It omits some of the instructions, +probably because the object file isn't linked. diff --git a/flashstub/dump-to-array.sh b/flashstub/dump-to-array.sh new file mode 100755 index 0000000..78584d0 --- /dev/null +++ b/flashstub/dump-to-array.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# +# Convert the output of objdump to an array of half-words that can be +# included into C code to represent the stub. +# +# Invoke with something like this: +# +# objdump -z -d FILE.o | dump-to-array.sh > FILE.stub +# + +sed -E "/^[ ][ ]*[0-9a-fA-F]+:/!d; s/([0-9a-fA-F]+):[ \t]+([0-9a-fA-F]+).*/[0x\1\/2] = 0x\2,/ ; s/0x(....)(....),/0x\2, 0x\1,/" diff --git a/flashstub/stm32l05x-nvm-prog-erase.cc b/flashstub/stm32l05x-nvm-prog-erase.cc new file mode 100644 index 0000000..58030e3 --- /dev/null +++ b/flashstub/stm32l05x-nvm-prog-erase.cc @@ -0,0 +1,93 @@ +/* @file stm32l05x-nvm-prog-erase.cc + * + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2014 Woollysoft + * Written by Marc Singer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* ----------- + DESCRIPTION + ----------- + + NVM program flash erase stub for STM32L05x, a Cortex-M0+ core. The + stub uses SRAM to host the code fragment to perform the erase. + + This stub works with the STM32L1xx given a few options. + + If you plan to modify this routine and emit a new stub, make sure + to audit the code. We don't have a stack so we cannot make calls + that save the link pointer. IOW, the inline functions should be be + inlined. + +*/ + +#include +#include +#include "../src/include/stm32lx-nvm.h" + +/* Erase a region of flash. In the event that the erase is misaligned + with respect to pages, it will erase the pages that contain the + requested range of bytes. */ +extern "C" void __attribute((naked)) stm32l05x_nvm_prog_erase() { + // Leave room for INFO at second word of routine + __asm volatile ("b 0f\n\t" + ".align 2\n\t" + ".word 0\n\t" + ".word 0\n\t" + ".word 0\n\t" + ".word 0\n\t" + ".word 0\n\t" + "0:"); + + auto& nvm = Nvm (Info.nvm); + + // Align to the start of the first page so that we make sure to erase + // all of the target pages. + auto remainder = reinterpret_cast (Info.destination) + & (Info.page_size - 1); + Info.size += remainder; + Info.destination -= remainder/sizeof (*Info.destination); + + if (!unlock(nvm)) + goto quit; + + nvm.sr = STM32Lx_NVM_SR_ERR_M; // Clear errors + + // Enable erasing + nvm.pecr = STM32Lx_NVM_PECR_PROG | STM32Lx_NVM_PECR_ERASE; + if ((nvm.pecr & (STM32Lx_NVM_PECR_PROG | STM32Lx_NVM_PECR_ERASE)) + != (STM32Lx_NVM_PECR_PROG | STM32Lx_NVM_PECR_ERASE)) + goto quit; + + while (Info.size > 0) { + *Info.destination = 0; // Initiate erase + + Info.destination += Info.page_size/sizeof (*Info.destination); + Info.size -= Info.page_size; + } + +quit: + lock(nvm); + __asm volatile ("bkpt"); +} + +/* + Local Variables: + compile-command: "/opt/arm/arm-none-eabi-g++ -mcpu=cortex-m0plus -g -c -std=c++11 -mthumb -o stm32l05x-nvm-prog-erase.o -Os -Wa,-ahndl=stm32l05x-nvm-prog-erase.lst stm32l05x-nvm-prog-erase.cc ; /opt/arm/arm-none-eabi-objdump -d -z stm32l05x-nvm-prog-erase.o | ./dump-to-array.sh > stm32l05x-nvm-prog-erase.stub" + End: + +*/ diff --git a/flashstub/stm32l05x-nvm-prog-erase.stub b/flashstub/stm32l05x-nvm-prog-erase.stub new file mode 100644 index 0000000..e0fea34 --- /dev/null +++ b/flashstub/stm32l05x-nvm-prog-erase.stub @@ -0,0 +1,67 @@ + [0x0/2] = 0xe00a, + [0x2/2] = 0x46c0, + [0x4/2] = 0x0000, 0x0000, + [0x8/2] = 0x0000, 0x0000, + [0xc/2] = 0x0000, 0x0000, + [0x10/2] = 0x0000, 0x0000, + [0x14/2] = 0x0000, 0x0000, + [0x18/2] = 0x491a, + [0x1a/2] = 0x8a08, + [0x1c/2] = 0x680c, + [0x1e/2] = 0x684d, + [0x20/2] = 0x1e42, + [0x22/2] = 0x4022, + [0x24/2] = 0x1955, + [0x26/2] = 0x0892, + [0x28/2] = 0x0092, + [0x2a/2] = 0x1aa2, + [0x2c/2] = 0x600a, + [0x2e/2] = 0x2201, + [0x30/2] = 0x68cb, + [0x32/2] = 0x604d, + [0x34/2] = 0x605a, + [0x36/2] = 0x4a14, + [0x38/2] = 0x60da, + [0x3a/2] = 0x4a14, + [0x3c/2] = 0x60da, + [0x3e/2] = 0x4a14, + [0x40/2] = 0x611a, + [0x42/2] = 0x4a14, + [0x44/2] = 0x611a, + [0x46/2] = 0x685a, + [0x48/2] = 0x0792, + [0x4a/2] = 0xd502, + [0x4c/2] = 0x2201, + [0x4e/2] = 0x605a, + [0x50/2] = 0xbe00, + [0x52/2] = 0x4a11, + [0x54/2] = 0x619a, + [0x56/2] = 0x2282, + [0x58/2] = 0x0092, + [0x5a/2] = 0x605a, + [0x5c/2] = 0x685c, + [0x5e/2] = 0x4014, + [0x60/2] = 0x4294, + [0x62/2] = 0xd1f3, + [0x64/2] = 0x0884, + [0x66/2] = 0x00a4, + [0x68/2] = 0x684d, + [0x6a/2] = 0x4a06, + [0x6c/2] = 0x2d00, + [0x6e/2] = 0xdded, + [0x70/2] = 0x2600, + [0x72/2] = 0x6815, + [0x74/2] = 0x602e, + [0x76/2] = 0x6815, + [0x78/2] = 0x192d, + [0x7a/2] = 0x6015, + [0x7c/2] = 0x6855, + [0x7e/2] = 0x1a2d, + [0x80/2] = 0x6055, + [0x82/2] = 0xe7f1, + [0x84/2] = 0x0004, 0x2000, + [0x88/2] = 0xcdef, 0x89ab, + [0x8c/2] = 0x0405, 0x0203, + [0x90/2] = 0xaebf, 0x8c9d, + [0x94/2] = 0x1516, 0x1314, + [0x98/2] = 0x0700, 0x0001, diff --git a/flashstub/stm32l05x-nvm-prog-write.cc b/flashstub/stm32l05x-nvm-prog-write.cc new file mode 100644 index 0000000..e90401e --- /dev/null +++ b/flashstub/stm32l05x-nvm-prog-write.cc @@ -0,0 +1,113 @@ +/* @file stm32l05x-nvm-prog-write.cc + * + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2014 Woollysoft + * Written by Marc Singer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* ----------- + DESCRIPTION + ----------- + + NVM program flash writing stub for STM32L05x, a Cortex-M0+ core. + The stub uses SRAM to host the code fragment and source data to + perform a write to flash. + + This stub works with the STM32L1xx given a few options. + + If you plan to modify this routine and emit a new stub, make sure + to audit the code. We don't have a stack so we cannot make calls + that save the link pointer. IOW, the inline functions should be be + inlined. + +*/ + +#include +#include +#include "../src/include/stm32lx-nvm.h" + +/* Write a block of bytes to flash. The called is responsible for + making sure that the address are aligned and that the count is an + even multiple of words. */ +extern "C" void __attribute((naked)) stm32l05x_nvm_prog_write() { + // Leave room for INFO at second word of routine + __asm volatile ("b 0f\n\t" + ".align 2\n\t" + ".word 0\n\t" + ".word 0\n\t" + ".word 0\n\t" + ".word 0\n\t" + ".word 0\n\t" + "0:"); + + auto& nvm = Nvm (Info.nvm); + + if (!unlock(nvm)) + goto quit; + + nvm.sr = STM32Lx_NVM_SR_ERR_M; // Clear errors + + while (Info.size > 0) { + + // Either we're not half-page aligned or we have less + // than a half page to write + if (Info.size < Info.page_size/2 + || (reinterpret_cast (Info.destination) + & (Info.page_size/2 - 1))) { + nvm.pecr = (Info.options & OPT_STM32L1) ? 0 + : STM32Lx_NVM_PECR_PROG; // Word programming + size_t c = Info.page_size/2 + - (reinterpret_cast (Info.destination) + & (Info.page_size/2 - 1)); + if (c > Info.size) + c = Info.size; + Info.size -= c; + c /= 4; + while (c--) { + uint32_t v = *Info.source++; + *Info.destination++ = v; + if (nvm.sr & STM32Lx_NVM_SR_ERR_M) + goto quit; + } + } + // Or we are writing a half-page(s) + else { + nvm.pecr = STM32Lx_NVM_PECR_PROG + | STM32Lx_NVM_PECR_FPRG; // Half-page prg + size_t c = Info.size & ~(Info.page_size/2 - 1); + Info.size -= c; + c /= 4; + while (c--) { + uint32_t v = *Info.source++; + *Info.destination++ = v; + } + if (nvm.sr & STM32Lx_NVM_SR_ERR_M) + goto quit; + } + } + +quit: + lock(nvm); + __asm volatile ("bkpt"); +} + +/* + Local Variables: + compile-command: "/opt/arm/arm-none-eabi-g++ -mcpu=cortex-m0plus -g -c -std=c++11 -mthumb -o stm32l05x-nvm-prog-write.o -Os -Wa,-ahndl=stm32l05x-nvm-prog-write.lst stm32l05x-nvm-prog-write.cc ; /opt/arm/arm-none-eabi-objdump -d -z stm32l05x-nvm-prog-write.o | ./dump-to-array.sh > stm32l05x-nvm-prog-write.stub" + End: + +*/ diff --git a/flashstub/stm32l05x-nvm-prog-write.stub b/flashstub/stm32l05x-nvm-prog-write.stub new file mode 100644 index 0000000..7ddbc81 --- /dev/null +++ b/flashstub/stm32l05x-nvm-prog-write.stub @@ -0,0 +1,99 @@ + [0x0/2] = 0xe00a, + [0x2/2] = 0x46c0, + [0x4/2] = 0x0000, 0x0000, + [0x8/2] = 0x0000, 0x0000, + [0xc/2] = 0x0000, 0x0000, + [0x10/2] = 0x0000, 0x0000, + [0x14/2] = 0x0000, 0x0000, + [0x18/2] = 0x2201, + [0x1a/2] = 0x4b2a, + [0x1c/2] = 0x68d9, + [0x1e/2] = 0x604a, + [0x20/2] = 0x4a29, + [0x22/2] = 0x60ca, + [0x24/2] = 0x4a29, + [0x26/2] = 0x60ca, + [0x28/2] = 0x4a29, + [0x2a/2] = 0x610a, + [0x2c/2] = 0x4a29, + [0x2e/2] = 0x610a, + [0x30/2] = 0x684a, + [0x32/2] = 0x0792, + [0x34/2] = 0xd502, + [0x36/2] = 0x2301, + [0x38/2] = 0x604b, + [0x3a/2] = 0xbe00, + [0x3c/2] = 0x4826, + [0x3e/2] = 0x6188, + [0x40/2] = 0x685d, + [0x42/2] = 0x4e20, + [0x44/2] = 0x2d00, + [0x46/2] = 0xddf6, + [0x48/2] = 0x8a32, + [0x4a/2] = 0x0852, + [0x4c/2] = 0x1e54, + [0x4e/2] = 0x4295, + [0x50/2] = 0xdb02, + [0x52/2] = 0x6837, + [0x54/2] = 0x4227, + [0x56/2] = 0xd01d, + [0x58/2] = 0x2602, + [0x5a/2] = 0x8a5f, + [0x5c/2] = 0x4037, + [0x5e/2] = 0x427e, + [0x60/2] = 0x417e, + [0x62/2] = 0x00f6, + [0x64/2] = 0x604e, + [0x66/2] = 0x681e, + [0x68/2] = 0x4034, + [0x6a/2] = 0x1b12, + [0x6c/2] = 0x42aa, + [0x6e/2] = 0xd900, + [0x70/2] = 0x1c2a, + [0x72/2] = 0x1aad, + [0x74/2] = 0x605d, + [0x76/2] = 0x0892, + [0x78/2] = 0x3a01, + [0x7a/2] = 0xd3e1, + [0x7c/2] = 0x689c, + [0x7e/2] = 0x1d25, + [0x80/2] = 0x609d, + [0x82/2] = 0x6825, + [0x84/2] = 0x681c, + [0x86/2] = 0x1d26, + [0x88/2] = 0x601e, + [0x8a/2] = 0x6025, + [0x8c/2] = 0x698c, + [0x8e/2] = 0x4204, + [0x90/2] = 0xd0f2, + [0x92/2] = 0xe7d0, + [0x94/2] = 0x2481, + [0x96/2] = 0x4252, + [0x98/2] = 0x402a, + [0x9a/2] = 0x1aad, + [0x9c/2] = 0x00e4, + [0x9e/2] = 0x604c, + [0xa0/2] = 0x0892, + [0xa2/2] = 0x6075, + [0xa4/2] = 0x3a01, + [0xa6/2] = 0xd308, + [0xa8/2] = 0x689c, + [0xaa/2] = 0x1d25, + [0xac/2] = 0x609d, + [0xae/2] = 0x6825, + [0xb0/2] = 0x681c, + [0xb2/2] = 0x1d26, + [0xb4/2] = 0x601e, + [0xb6/2] = 0x6025, + [0xb8/2] = 0xe7f4, + [0xba/2] = 0x698a, + [0xbc/2] = 0x4202, + [0xbe/2] = 0xd0bf, + [0xc0/2] = 0xe7b9, + [0xc2/2] = 0x46c0, + [0xc4/2] = 0x0004, 0x2000, + [0xc8/2] = 0xcdef, 0x89ab, + [0xcc/2] = 0x0405, 0x0203, + [0xd0/2] = 0xaebf, 0x8c9d, + [0xd4/2] = 0x1516, 0x1314, + [0xd8/2] = 0x0700, 0x0001, diff --git a/src/Makefile b/src/Makefile index 2d3e6b7..7a1f141 100644 --- a/src/Makefile +++ b/src/Makefile @@ -41,6 +41,7 @@ SRC = \ samd.c \ stm32f1.c \ stm32f4.c \ + stm32l0.c \ stm32l1.c \ swdptap.c \ target.c \ diff --git a/src/cortexm.c b/src/cortexm.c index a016342..71d0c6c 100644 --- a/src/cortexm.c +++ b/src/cortexm.c @@ -251,6 +251,7 @@ cortexm_probe(struct target_s *target) PROBE(stm32f1_probe); PROBE(stm32f4_probe); + PROBE(stm32l0_probe); /* STM32L0xx & STM32L1xx */ PROBE(stm32l1_probe); PROBE(lpc11xx_probe); PROBE(lpc43xx_probe); diff --git a/src/include/stm32lx-nvm.h b/src/include/stm32lx-nvm.h new file mode 100644 index 0000000..190a8ab --- /dev/null +++ b/src/include/stm32lx-nvm.h @@ -0,0 +1,203 @@ +/* @file stm32lx-nvm.h + * + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2014 Woollysoft + * Written by Marc Singer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#if !defined (STM32Lx_NVM_H_INCLUDED) +# define STM32Lx_NVM_H_INCLUDED + +/* ----- Includes */ + +#include + +/* ----- Macros */ + +/* ----- Types */ + +enum { + STM32Lx_STUB_PHYS = 0x20000000ul, + STM32Lx_STUB_INFO_PHYS = 0x20000004ul, + STM32Lx_STUB_DATA_PHYS = (0x20000000ul + 1024), + STM32Lx_STUB_DATA_MAX = 2048, + + STM32Lx_NVM_OPT_PHYS = 0x1ff80000ul, + STM32Lx_NVM_EEPROM_PHYS = 0x08080000ul, + + STM32L0_NVM_PHYS = 0x40022000ul, + STM32L0_NVM_PROG_PAGE_SIZE = 128, + STM32L0_NVM_DATA_PAGE_SIZE = 4, + STM32L0_NVM_OPT_SIZE = 12, + STM32L0_NVM_EEPROM_SIZE = 2*1024, + + STM32L1_NVM_PHYS = 0x40023c00ul, + STM32L1_NVM_PROG_PAGE_SIZE = 256, + STM32L1_NVM_DATA_PAGE_SIZE = 4, + STM32L1_NVM_OPT_SIZE = 32, + STM32L1_NVM_EEPROM_SIZE = 16*1024, + + STM32Lx_NVM_PEKEY1 = 0x89abcdeful, + STM32Lx_NVM_PEKEY2 = 0x02030405ul, + STM32Lx_NVM_PRGKEY1 = 0x8c9daebful, + STM32Lx_NVM_PRGKEY2 = 0x13141516ul, + STM32Lx_NVM_OPTKEY1 = 0xfbead9c8ul, + STM32Lx_NVM_OPTKEY2 = 0x24252627ul, + + STM32Lx_NVM_PECR_OBL_LAUNCH = (1<<18), + STM32Lx_NVM_PECR_ERRIE = (1<<17), + STM32Lx_NVM_PECR_EOPIE = (1<<16), + STM32Lx_NVM_PECR_FPRG = (1<<10), + STM32Lx_NVM_PECR_ERASE = (1<< 9), + STM32Lx_NVM_PECR_FIX = (1<< 8), /* FTDW */ + STM32Lx_NVM_PECR_DATA = (1<< 4), + STM32Lx_NVM_PECR_PROG = (1<< 3), + STM32Lx_NVM_PECR_OPTLOCK = (1<< 2), + STM32Lx_NVM_PECR_PRGLOCK = (1<< 1), + STM32Lx_NVM_PECR_PELOCK = (1<< 0), + + STM32Lx_NVM_SR_FWWERR = (1<<17), + STM32Lx_NVM_SR_NOTZEROERR = (1<<16), + STM32Lx_NVM_SR_RDERR = (1<<13), + STM32Lx_NVM_SR_OPTVER = (1<<11), + STM32Lx_NVM_SR_SIZERR = (1<<10), + STM32Lx_NVM_SR_PGAERR = (1<<9), + STM32Lx_NVM_SR_WRPERR = (1<<8), + STM32Lx_NVM_SR_READY = (1<<3), + STM32Lx_NVM_SR_HWOFF = (1<<2), + STM32Lx_NVM_SR_EOP = (1<<1), + STM32Lx_NVM_SR_BSY = (1<<0), + STM32Lx_NVM_SR_ERR_M = ( STM32Lx_NVM_SR_WRPERR + | STM32Lx_NVM_SR_PGAERR + | STM32Lx_NVM_SR_SIZERR + | STM32Lx_NVM_SR_NOTZEROERR), + + STM32L0_NVM_OPTR_BOOT1 = (1<<31), + STM32L0_NVM_OPTR_WDG_SW = (1<<20), + STM32L0_NVM_OPTR_WPRMOD = (1<<8), + STM32L0_NVM_OPTR_RDPROT_S = (0), + STM32L0_NVM_OPTR_RDPROT_M = (0xff), + STM32L0_NVM_OPTR_RDPROT_0 = (0xaa), + STM32L0_NVM_OPTR_RDPROT_2 = (0xcc), + + STM32L1_NVM_OPTR_nBFB2 = (1<<23), + STM32L1_NVM_OPTR_nRST_STDBY = (1<<22), + STM32L1_NVM_OPTR_nRST_STOP = (1<<21), + STM32L1_NVM_OPTR_WDG_SW = (1<<20), + STM32L1_NVM_OPTR_BOR_LEV_S = (16), + STM32L1_NVM_OPTR_BOR_LEV_M = (0xf), + STM32L1_NVM_OPTR_SPRMOD = (1<<8), + STM32L1_NVM_OPTR_RDPROT_S = (0), + STM32L1_NVM_OPTR_RDPROT_M = (0xff), + STM32L1_NVM_OPTR_RDPROT_0 = (0xaa), + STM32L1_NVM_OPTR_RDPROT_2 = (0xcc), + +}; + +#if defined (__cplusplus) + +namespace STM32 { + struct NVM { + volatile uint32_t acr; + volatile uint32_t pecr; + volatile uint32_t pdkeyr; + volatile uint32_t pkeyr; + volatile uint32_t prgkeyr; + volatile uint32_t optkeyr; + volatile uint32_t sr; + volatile uint32_t optr; + volatile uint32_t wrprot; + + static constexpr uint32_t PKEY1 = 0x89abcdef; + static constexpr uint32_t PKEY2 = 0x02030405; + static constexpr uint32_t PRGKEY1 = 0x8c9daebf; + static constexpr uint32_t PRGKEY2 = 0x13141516; + static constexpr uint32_t OPTKEY1 = 0xfbead9c8; + static constexpr uint32_t OPTKEY2 = 0x24252627; + static constexpr uint32_t PDKEY1 = 0x04152637; + static constexpr uint32_t PDKEY2 = 0xfafbfcfd; + }; + + static_assert(sizeof (NVM) == 9*4, "NVM size error"); +} +using stm32lx_stub_pointer_t = uint32_t*; + +#define Nvm(nvm) (*reinterpret_cast(nvm)) +#define Info (*reinterpret_cast(STM32Lx_STUB_INFO_PHYS)) + +namespace { + inline __attribute((always_inline)) bool unlock (STM32::NVM& nvm) { + // Lock guarantees unlock + nvm.pecr = STM32Lx_NVM_PECR_PELOCK; + + nvm.pkeyr = STM32::NVM::PKEY1; + nvm.pkeyr = STM32::NVM::PKEY2; + nvm.prgkeyr = STM32::NVM::PRGKEY1; + nvm.prgkeyr = STM32::NVM::PRGKEY2; + return !(nvm.pecr & STM32Lx_NVM_PECR_PRGLOCK); + } + inline __attribute((always_inline)) void lock (STM32::NVM& nvm) { + nvm.pecr = STM32Lx_NVM_PECR_PELOCK; } + +} + +#else + +typedef uint32_t stm32lx_stub_pointer_t; + +struct stm32lx_nvm { + volatile uint32_t acr; + volatile uint32_t pecr; + volatile uint32_t pdkeyr; + volatile uint32_t pekeyr; + volatile uint32_t prgkeyr; + volatile uint32_t optkeyr; + volatile uint32_t sr; + volatile uint32_t optr; /* or obr */ + volatile uint32_t wrprot; /* or wprot1 */ +}; + +#define STM32Lx_NVM(p) (*(struct stm32lx_nvm*) (p)) +#define STM32Lx_NVM_PECR(p) ((uint32_t) &STM32Lx_NVM(p).pecr) +#define STM32Lx_NVM_PEKEYR(p) ((uint32_t) &STM32Lx_NVM(p).pekeyr) +#define STM32Lx_NVM_PRGKEYR(p) ((uint32_t) &STM32Lx_NVM(p).prgkeyr) +#define STM32Lx_NVM_OPTKEYR(p) ((uint32_t) &STM32Lx_NVM(p).optkeyr) +#define STM32Lx_NVM_SR(p) ((uint32_t) &STM32Lx_NVM(p).sr) +#define STM32Lx_NVM_OPTR(p) ((uint32_t) &STM32Lx_NVM(p).optr) + +#endif + +enum { + OPT_STM32L1 = 1<<1, +}; + +struct stm32lx_nvm_stub_info { + stm32lx_stub_pointer_t destination; + int32_t size; + stm32lx_stub_pointer_t source; + uint32_t nvm; + uint16_t page_size; + uint16_t options; +} __attribute__((packed)); + +/* ----- Globals */ + +/* ----- Prototypes */ + + + +#endif /* STM32Lx_NVM_H_INCLUDED */ diff --git a/src/include/target.h b/src/include/target.h index 0e6d9c2..a2369e3 100644 --- a/src/include/target.h +++ b/src/include/target.h @@ -216,6 +216,7 @@ void target_add_commands(target *t, const struct command_s *cmds, const char *na bool cortexm_probe(struct target_s *target); bool stm32f1_probe(struct target_s *target); bool stm32f4_probe(struct target_s *target); +bool stm32l0_probe(struct target_s *target); bool stm32l1_probe(struct target_s *target); bool lmi_probe(struct target_s *target); bool lpc11xx_probe(struct target_s *target); diff --git a/src/stm32l0.c b/src/stm32l0.c new file mode 100644 index 0000000..954a277 --- /dev/null +++ b/src/stm32l0.c @@ -0,0 +1,1070 @@ +/* + * This file is part of the Black Magic Debug project. + * + * Copyright (C) 2014,2015 Marc Singer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Description + ----------- + + This is an implementation of the target-specific functions for the + STM32L0x[1] and STM32L1x[2] families of ST Microelectronics MCUs, + Cortex M0+ SOCs. The NVM interface is substantially similar to the + STM32L1x parts. This module is written to better generalize the + NVM interface and to provide more features. + + [1] ST Microelectronics Document RM0377 (DocID025942), "Reference + manual for Ultra-low-power STM32L0x1 advanced ARM-based 32-bit + MCUs," April 2014. + + [2] ST Microelectronics Document RM0038 (DocID15965, "..."Reference + manual for STM32L100xx, STM32L151xx, STM32L152xx and STM32L162xx + advanced ARMĀ®-based 32-bit MCUs, " July 2014 + + + NOTES + ===== + + o Stubbed and non-stubbed NVM operation functions. The STM32L0xx + appears to behave differently from other STM32 cores. When it + enters a fault state it will not exit this state without a + reset. However, the reset will immediately enter a fault state + if program flash is erased. When in this state, it will not + permit execution of code in RAM in the way that other cores + will. Changing the PC to the start of RAM and single stepping + will immediately HardFault. + + The stub functions can be both faster and simpler because they + have direct access to the MCU. So, to permit stub operation in + the best circumstances, the NVM operation functions will check + the MCU state and either execute the stub or non-stub version + accordingly. The user can override stubs as well with a command + in case the detection feature fails...which it seems to do in + most cases. + + o Erase would be more efficient if we checked for non-blank-ness + before initiating an erase. This would have to be done in a stub + for efficiency. + + o Mass erase unimplemented. The method for performing a mass erase + is to set the options for read protection, reload the option + bytes, set options for no protection, and then reload the option + bytes again. The command fails because we lose contact with the + target when we perform the option byte reload. For the time + being, the command is disabled. + + The body of the function was the following. It is left here for + reference in case someone either discovers what is wrong with + these lines, or a change is made to the emulator that allows it + to regain control of the target after the option byte reload. + + stm32l0_option_write(t, 0x1ff80000, 0xffff0000); + adiv5_ap_mem_write(ap, STM32L0_NVM_PECR, STM32L0_NVM_PECR_OBL_LAUNCH); + stm32l0_option_write(t, 0x1ff80000, 0xff5500aa); + adiv5_ap_mem_write(ap, STM32L0_NVM_PECR, STM32L0_NVM_PECR_OBL_LAUNCH); + + uint32_t sr; + do { + sr = adiv5_ap_mem_read(ap, STM32L0_NVM_SR); + } while (sr & STM32L0_NVM_SR_BSY); + + o Errors. We probably should clear SR errors immediately after + detecting them. If we don't then we always must wait for the NVM + module to complete the last operation before we can start another. + + o There are minor inconsistencies between the stm32l0 and the + stm32l1 in when handling NVM operations. + + o When we erase or write individual words (not half-pages) on the + stm32l0, we set the PROG bit. On the stm32l1 the PROG bit is + only set when erasing. This is not documented in the register + summaries, but in the functional quick reference. Argh. + + o On the STM32L1xx, PECR can only be changed when the NVM + hardware is idle. The STM32L0xx allows the PECR to be updated + while an operation is in progress. + + o Performance. The throughput for writing is not high. We + suspected it may be possible to improve throughput significantly + by increasing the MCU clock. The code, as is, offers a + simplicity without undue knowledge of the inner workings of the + MCUs. Increasing clock frequencies would require substantial + knowledge of the clock tree. + + FWIW, this was tried. We verified that the system clocks were + changed, but the flash write was no faster. It looks like this + is due to the fact that the emulator performs a target reset + before executing the flash operations, bringing the system back + to the reset state clocking. + +*/ + +#define CONFIG_STM32L1 /* Include support for STM32L1 */ + +#include +#include +#include + +#include "general.h" +#include "adiv5.h" +#include "target.h" +#include "command.h" + +#include "stm32lx-nvm.h" + +static int inhibit_stubs; /* Local option to force non-stub flash IO */ + +static int stm32lx_nvm_erase(struct target_s* target, + uint32_t addr, int len); +static int stm32lx_nvm_write(struct target_s* target, + uint32_t destination, + const uint8_t* source, + int size); + +static int stm32lx_nvm_prog_erase(struct target_s* target, + uint32_t addr, int len); +static int stm32lx_nvm_prog_write(struct target_s* target, + uint32_t destination, + const uint8_t* source, + int size); + +static int stm32lx_nvm_prog_erase_stubbed(struct target_s* target, + uint32_t addr, int len); +static int stm32lx_nvm_prog_write_stubbed(struct target_s* target, + uint32_t destination, + const uint8_t* source, + int size); + +static int stm32lx_nvm_data_erase(struct target_s* target, + uint32_t addr, int len); +static int stm32lx_nvm_data_write(struct target_s* target, + uint32_t destination, + const uint8_t* source, + int size); + +static bool stm32lx_cmd_option (target* t, int argc, char** argv); +static bool stm32lx_cmd_eeprom (target* t, int argc, char** argv); +static bool stm32lx_cmd_stubs (target* t, int argc, char** argv); + +static const struct command_s stm32lx_cmd_list[] = { + { "stubs", (cmd_handler) stm32lx_cmd_stubs, + "Enable/disable NVM operation stubs" }, + { "option", (cmd_handler) stm32lx_cmd_option, + "Manipulate option bytes"}, + { "eeprom", (cmd_handler) stm32lx_cmd_eeprom, + "Manipulate EEPROM(NVM data) memory"}, + { 0 }, +}; + +enum { + STM32L0_DBGMCU_IDCODE_PHYS = 0x40015800, + STM32L1_DBGMCU_IDCODE_PHYS = 0xe0042000, +}; + +static const char stm32l0_driver_str[] = "STM32L0xx"; + +static const char stm32l0_xml_memory_map[] = "" +/* ""*/ + "" + /* Program flash; ranges up to 64KiB(0x10000). */ + " " + " 0x80" + " " + /* Data(EEPROM) NVRAM; ranges up to 2KiB(0x800). */ + " " + " 0x4" + " " + /* SRAM; ranges up to 8KiB(0x2000). */ + " " + ""; + +#if defined(CONFIG_STM32L1) +static const char stm32l1_driver_str[] = "STM32L1xx"; + +static const char stm32l1_xml_memory_map[] = "" +/* ""*/ + "" + /* Program flash; ranges from 32KiB to 512KiB(0x80000). */ + " " + " 0x100" + " " + /* Data(EEPROM) NVRAM; ranges from 2K to 16KiB(0x4000). */ + " " + " 0x4" + " " + /* SRAM; ranges from 4KiB to 80KiB(0x14000). */ + " " + ""; +#endif + +static const uint16_t stm32l0_nvm_prog_write_stub [] = { +#include "../flashstub/stm32l05x-nvm-prog-write.stub" +}; + +static const uint16_t stm32l0_nvm_prog_erase_stub [] = { +#include "../flashstub/stm32l05x-nvm-prog-erase.stub" +}; + +static uint32_t stm32lx_nvm_prog_page_size(struct target_s* target) +{ + switch (target->idcode) { + case 0x417: /* STM32L0xx */ + return STM32L0_NVM_PROG_PAGE_SIZE; + default: /* STM32L1xx */ + return STM32L1_NVM_PROG_PAGE_SIZE; + } +} + +static bool stm32lx_is_stm32l1(struct target_s* target) +{ + switch (target->idcode) { + case 0x417: /* STM32L0xx */ + return false; + default: /* STM32L1xx */ + return true; + } +} + +static uint32_t stm32lx_nvm_eeprom_size(struct target_s* target) +{ + switch (target->idcode) { + case 0x417: /* STM32L0xx */ + return STM32L0_NVM_EEPROM_SIZE; + default: /* STM32L1xx */ + return STM32L1_NVM_EEPROM_SIZE; + } +} + +static uint32_t stm32lx_nvm_phys(struct target_s* target) +{ + switch (target->idcode) { + case 0x417: /* STM32L0xx */ + return STM32L0_NVM_PHYS; + default: /* STM32L1xx */ + return STM32L1_NVM_PHYS; + } +} + +static uint32_t stm32lx_nvm_data_page_size(struct target_s* target) +{ + switch (target->idcode) { + case 0x417: /* STM32L0xx */ + return STM32L0_NVM_DATA_PAGE_SIZE; + default: /* STM32L1xx */ + return STM32L1_NVM_DATA_PAGE_SIZE; + } +} + +static uint32_t stm32lx_nvm_option_size(struct target_s* target) +{ + switch (target->idcode) { + case 0x417: /* STM32L0xx */ + return STM32L0_NVM_OPT_SIZE; + default: /* STM32L1xx */ + return STM32L1_NVM_OPT_SIZE; + } +} + +/** Query MCU memory for an indication as to whether or not the + currently attached target is served by this module. We detect the + STM32L0xx parts as well as the STM32L1xx's. */ +bool stm32l0_probe(struct target_s* target) +{ + uint32_t idcode; + +#if defined(CONFIG_STM32L1) + + idcode = adiv5_ap_mem_read(adiv5_target_ap(target), + STM32L1_DBGMCU_IDCODE_PHYS) & 0xfff; + switch (idcode) { + case 0x416: /* CAT. 1 device */ + case 0x429: /* CAT. 2 device */ + case 0x427: /* CAT. 3 device */ + case 0x436: /* CAT. 4 device */ + case 0x437: /* CAT. 5 device */ + target->idcode = idcode; + target->driver = stm32l1_driver_str; + target->xml_mem_map = stm32l1_xml_memory_map; + target->flash_erase = stm32lx_nvm_erase; + target->flash_write = stm32lx_nvm_write; + target_add_commands(target, stm32lx_cmd_list, "STM32L1x"); + return true; + } +#endif + + idcode = adiv5_ap_mem_read(adiv5_target_ap(target), + STM32L0_DBGMCU_IDCODE_PHYS) & 0xfff; + switch (idcode) { + default: + break; + + case 0x417: /* STM32L0x[123] & probably others */ + target->idcode = idcode; + target->driver = stm32l0_driver_str; + target->xml_mem_map = stm32l0_xml_memory_map; + target->flash_erase = stm32lx_nvm_erase; + target->flash_write = stm32lx_nvm_write; + target_add_commands(target, stm32lx_cmd_list, "STM32L0x"); + return true; + } + + return false; +} + + +/** Lock the NVM control registers preventing writes or erases. */ +static void stm32lx_nvm_lock(ADIv5_AP_t* ap, uint32_t nvm) +{ + adiv5_ap_mem_write(ap, STM32Lx_NVM_PECR(nvm), STM32Lx_NVM_PECR_PELOCK); +} + + +/** Unlock the NVM control registers for modifying program or + data flash. Returns true if the unlock succeeds. */ +static bool stm32lx_nvm_prog_data_unlock(ADIv5_AP_t* ap, uint32_t nvm) +{ + /* Always lock first because that's the only way to know that the + unlock can succeed on the STM32L0's. */ + adiv5_ap_mem_write(ap, STM32Lx_NVM_PECR(nvm), STM32Lx_NVM_PECR_PELOCK); + adiv5_ap_mem_write(ap, STM32Lx_NVM_PEKEYR(nvm), STM32Lx_NVM_PEKEY1); + adiv5_ap_mem_write(ap, STM32Lx_NVM_PEKEYR(nvm), STM32Lx_NVM_PEKEY2); + adiv5_ap_mem_write(ap, STM32Lx_NVM_PRGKEYR(nvm), STM32Lx_NVM_PRGKEY1); + adiv5_ap_mem_write(ap, STM32Lx_NVM_PRGKEYR(nvm), STM32Lx_NVM_PRGKEY2); + + return !(adiv5_ap_mem_read(ap, STM32Lx_NVM_PECR(nvm)) + & STM32Lx_NVM_PECR_PRGLOCK); +} + + +/** Unlock the NVM control registers for modifying option bytes. + Returns true if the unlock succeeds. */ +static bool stm32lx_nvm_opt_unlock(ADIv5_AP_t* ap, uint32_t nvm) +{ + /* Always lock first because that's the only way to know that the + unlock can succeed on the STM32L0's. */ + adiv5_ap_mem_write(ap, STM32Lx_NVM_PECR(nvm), STM32Lx_NVM_PECR_PELOCK); + adiv5_ap_mem_write(ap, STM32Lx_NVM_PEKEYR(nvm), STM32Lx_NVM_PEKEY1); + adiv5_ap_mem_write(ap, STM32Lx_NVM_PEKEYR(nvm), STM32Lx_NVM_PEKEY2); + adiv5_ap_mem_write(ap, STM32Lx_NVM_OPTKEYR(nvm), STM32Lx_NVM_OPTKEY1); + adiv5_ap_mem_write(ap, STM32Lx_NVM_OPTKEYR(nvm), STM32Lx_NVM_OPTKEY2); + + return !(adiv5_ap_mem_read(ap, STM32Lx_NVM_PECR(nvm)) + & STM32Lx_NVM_PECR_OPTLOCK); +} + + +/** Erase a region of flash using a stub function. This only works + when the MCU hasn't entered a fault state(see NOTES). The flash + array is erased for all pages from addr to addr+len inclusive. */ +static int stm32lx_nvm_prog_erase_stubbed(struct target_s* target, + uint32_t addr, int size) +{ + struct stm32lx_nvm_stub_info info; + const uint32_t nvm = stm32lx_nvm_phys(target); + + info.nvm = nvm; + info.page_size = stm32lx_nvm_prog_page_size(target); + + /* Load the stub */ + target_mem_write_words(target, STM32Lx_STUB_PHYS, + (void*) &stm32l0_nvm_prog_erase_stub[0], + sizeof(stm32l0_nvm_prog_erase_stub)); + + /* Setup parameters */ + info.destination = addr; + info.size = size; + + /* Copy parameters */ + target_mem_write_words(target, STM32Lx_STUB_INFO_PHYS, + (void*) &info, sizeof(info)); + + /* Execute stub */ + target_pc_write(target, STM32Lx_STUB_PHYS); + if (target_check_error(target)) + return -1; + target_halt_resume(target, 0); + while (!target_halt_wait(target)) + ; + { + ADIv5_AP_t* ap = adiv5_target_ap(target); + if (adiv5_ap_mem_read(ap, STM32Lx_NVM_SR(nvm)) + & STM32Lx_NVM_SR_ERR_M) + return -1; + } + + return 0; +} + + +/** Write to program flash using a stub function. This only works + when the MCU hasn't entered a fault state. Once the MCU faults, + this function will not succeed because the MCU will fault before + executing a single instruction in the stub. */ +static int stm32lx_nvm_prog_write_stubbed(struct target_s* target, + uint32_t destination, + const uint8_t* source, + int size) +{ + struct stm32lx_nvm_stub_info info; + const uint32_t nvm = stm32lx_nvm_phys(target); + const size_t page_size = stm32lx_nvm_prog_page_size(target); + + /* We can only handle word aligned writes and even + word-multiple ranges. The stm32lx's cannot perform + anything smaller than a word write due to the ECC bits. + So, the caller must do the fixup. */ + if ((destination & 3) || (size & 3)) + return -1; + + info.nvm = nvm; + info.page_size = page_size; + + /* Load the stub */ + target_mem_write_words(target, STM32Lx_STUB_PHYS, + (void*) &stm32l0_nvm_prog_write_stub[0], + sizeof(stm32l0_nvm_prog_write_stub)); + + while (size > 0) { + + /* Max transfer size is adjusted in the event that the + destination isn't half-page aligned. This allows + the stub to write the first partial half-page and + then as many half-pages as will fit in the + buffer. */ + size_t max = STM32Lx_STUB_DATA_MAX + - (destination - (destination + & ~(info.page_size/2 - 1))); + size_t cb = size; + if (cb > max) + cb = max; + + /* Setup parameters */ + info.source = STM32Lx_STUB_DATA_PHYS; + info.destination = destination; + info.size = cb; + + /* Copy data to write to flash */ + target_mem_write_words(target, info.source, (void*) source, + info.size); + + /* Move pointers early */ + destination += cb; + source += cb; + size -= cb; + + /* Copy parameters */ + target_mem_write_words(target, STM32Lx_STUB_INFO_PHYS, + (void*) &info, sizeof(info)); + + /* Execute stub */ + target_pc_write(target, STM32Lx_STUB_PHYS); + if (target_check_error(target)) + return -1; + target_halt_resume(target, 0); + while (!target_halt_wait(target)) + ; + + if (adiv5_ap_mem_read(adiv5_target_ap(target), + STM32Lx_NVM_SR(nvm)) + & STM32Lx_NVM_SR_ERR_M) + return -1; + } + + return 0; +} + + +/** Erase a region of NVM for STM32Lx. This is the lead function and + it will invoke an implementation, stubbed or not depending on the + options and the range of addresses. */ +static int stm32lx_nvm_erase(struct target_s* target, uint32_t addr, int size) +{ + if (addr >= STM32Lx_NVM_EEPROM_PHYS) + return stm32lx_nvm_data_erase(target, addr, size); + + /* Use stub if not inhibited, the MCU is in a non-exceptonal state + and there is stub. */ + volatile uint32_t regs[20]; + target_regs_read(target, ®s); + if (inhibit_stubs || (regs[16] & 0xf)) + return stm32lx_nvm_prog_erase(target, addr, size); + + return stm32lx_nvm_prog_erase_stubbed(target, addr, size); +} + + +/** Write to a region on NVM for STM32Lxxx. This is the lead function + and it will invoke an implementation, stubbed or not depending on + the options and the range of addresses. Data (EEPROM) writes + don't have to care about alignment, but the program flash does. + There is a fixup for unaligned program flash writes. */ +static int stm32lx_nvm_write(struct target_s* target, + uint32_t destination, + const uint8_t* source, + int size) +{ + if (destination >= STM32Lx_NVM_EEPROM_PHYS) + return stm32lx_nvm_data_write(target, destination, source, + size); + + /* Unaligned destinations. To make this feature simple to + implement, we do a fixup on the source data as well as the + adjusting the write parameters if the caller has asked for + an unaligned operation. Padding of this data is zeros + because the STM32L's are built that way. */ + if ((destination & 3) || (size & 3)) { + size_t size_aligned = size + + (destination & 3) + + (((size + 3) & ~3) - size); + uint8_t* source_aligned = alloca (size_aligned); + memset (source_aligned, 0, size_aligned); + memcpy (source_aligned + (destination & 3), source, size); + source = source_aligned; + destination &= ~3; + size = size_aligned; + } + + /* Skip stub if the MCU is in a questionable state, or if the + user asks us to avoid stubs. */ + volatile uint32_t regs[20]; + target_regs_read(target, ®s); + if (inhibit_stubs || (regs[16] & 0xf)) + return stm32lx_nvm_prog_write(target, destination, source, + size); + + return stm32lx_nvm_prog_write_stubbed(target, destination, source, + size); +} + + +/** Erase a region of program flash using operations through the debug + interface. This is slower than stubbed versions(see NOTES). The + flash array is erased for all pages from addr to addr+len + inclusive. NVM register file address chosen from target. */ +static int stm32lx_nvm_prog_erase(struct target_s* target, + uint32_t addr, int len) +{ + ADIv5_AP_t* ap = adiv5_target_ap(target); + const size_t page_size = stm32lx_nvm_prog_page_size(target); + const uint32_t nvm = stm32lx_nvm_phys(target); + + /* Word align */ + len += (addr & 3); + addr &= ~3; + + if (!stm32lx_nvm_prog_data_unlock(ap, nvm)) + return -1; + + /* Flash page erase instruction */ + adiv5_ap_mem_write(ap, STM32Lx_NVM_PECR(nvm), + STM32Lx_NVM_PECR_ERASE | STM32Lx_NVM_PECR_PROG); + + { + uint32_t pecr = adiv5_ap_mem_read(ap, STM32Lx_NVM_PECR(nvm)); + if ((pecr & (STM32Lx_NVM_PECR_PROG | STM32Lx_NVM_PECR_ERASE)) + != (STM32Lx_NVM_PECR_PROG | STM32Lx_NVM_PECR_ERASE)) + return -1; + } + + /* Clear errors. Note that this only works when we wait for the NVM + block to complete the last operation. */ + adiv5_ap_mem_write(ap, STM32Lx_NVM_SR(nvm), STM32Lx_NVM_SR_ERR_M); + + while (len > 0) { + /* Write first word of page to 0 */ + adiv5_ap_mem_write(ap, addr, 0); + + len -= page_size; + addr += page_size; + } + + /* Disable further programming by locking PECR */ + stm32lx_nvm_lock(ap, nvm); + + /* Wait for completion or an error */ + while (1) { + uint32_t sr = adiv5_ap_mem_read(ap, STM32Lx_NVM_SR(nvm)); + if (target_check_error(target)) + return -1; + if (sr & STM32Lx_NVM_SR_BSY) + continue; + if ((sr & STM32Lx_NVM_SR_ERR_M) || !(sr & STM32Lx_NVM_SR_EOP)) + return -1; + break; + } + + return 0; +} + + +/** Write to program flash using operations through the debug + interface. This is slower than the stubbed write(see NOTES). + NVM register file address chosen from target. */ +static int stm32lx_nvm_prog_write(struct target_s* target, + uint32_t destination, + const uint8_t* source_8, + int size) +{ + ADIv5_AP_t* ap = adiv5_target_ap(target); + const uint32_t nvm = stm32lx_nvm_phys(target); + const bool is_stm32l1 = stm32lx_is_stm32l1(target); + + /* We can only handle word aligned writes and even + word-multiple ranges. The stm32lx's cannot perform + anything smaller than a word write due to the ECC bits. + So, the caller must do the fixup. */ + if ((destination & 3) || (size & 3)) + return -1; + + if (!stm32lx_nvm_prog_data_unlock(ap, nvm)) + return -1; + + const size_t half_page_size = stm32lx_nvm_prog_page_size(target)/2; + uint32_t* source = (uint32_t*) source_8; + + while (size > 0) { + + /* Wait for BSY to clear because we cannot write the PECR until + the previous operation completes on STM32Lxxx. */ + while (adiv5_ap_mem_read(ap, STM32Lx_NVM_SR(nvm)) + & STM32Lx_NVM_SR_BSY) + if (target_check_error(target)) { + return -1; + } + + // Either we're not half-page aligned or we have less + // than a half page to write + if (size < half_page_size + || (destination & (half_page_size - 1))) { + adiv5_ap_mem_write(ap, STM32Lx_NVM_PECR(nvm), + is_stm32l1 + ? 0 + : STM32Lx_NVM_PECR_PROG); + size_t c = half_page_size - (destination + & (half_page_size - 1)); + + if (c > size) + c = size; + size -= c; + + target_mem_write_words(target, destination, source, c); + source += c/4; + destination += c; + } + // Or we are writing a half-page(s) + else { + adiv5_ap_mem_write(ap, STM32Lx_NVM_PECR(nvm), + STM32Lx_NVM_PECR_PROG + | STM32Lx_NVM_PECR_FPRG); + + size_t c = size & ~(half_page_size - 1); + size -= c; + target_mem_write_words(target, destination, source, c); + source += c/4; + destination += c; + } + } + + /* Disable further programming by locking PECR */ + stm32lx_nvm_lock(ap, nvm); + + /* Wait for completion or an error */ + while (1) { + uint32_t sr = adiv5_ap_mem_read(ap, STM32Lx_NVM_SR(nvm)); + if (target_check_error(target)) { + return -1; + } + if (sr & STM32Lx_NVM_SR_BSY) + continue; + if ((sr & STM32Lx_NVM_SR_ERR_M) || !(sr & STM32Lx_NVM_SR_EOP)) { + return -1; + } + break; + } + + return 0; +} + + +/** Erase a region of data flash using operations through the debug + interface . The flash is erased for all pages from addr to + addr+len, inclusive, on a word boundary. NVM register file + address chosen from target. */ +static int stm32lx_nvm_data_erase(struct target_s* target, + uint32_t addr, int len) +{ + ADIv5_AP_t* ap = adiv5_target_ap(target); + const size_t page_size = stm32lx_nvm_data_page_size(target); + const uint32_t nvm = stm32lx_nvm_phys(target); + + /* Word align */ + len += (addr & 3); + addr &= ~3; + + if (!stm32lx_nvm_prog_data_unlock(ap, nvm)) + return -1; + + /* Flash data erase instruction */ + adiv5_ap_mem_write(ap, STM32Lx_NVM_PECR(nvm), + STM32Lx_NVM_PECR_ERASE | STM32Lx_NVM_PECR_DATA); + + { + uint32_t pecr = adiv5_ap_mem_read(ap, STM32Lx_NVM_PECR(nvm)); + if ((pecr & (STM32Lx_NVM_PECR_ERASE | STM32Lx_NVM_PECR_DATA)) + != (STM32Lx_NVM_PECR_ERASE | STM32Lx_NVM_PECR_DATA)) + return -1; + } + + while (len > 0) { + /* Write first word of page to 0 */ + adiv5_ap_mem_write(ap, addr, 0); + + len -= page_size; + addr += page_size; + } + + /* Disable further programming by locking PECR */ + stm32lx_nvm_lock(ap, nvm); + + /* Wait for completion or an error */ + while (1) { + uint32_t sr = adiv5_ap_mem_read(ap, STM32Lx_NVM_SR(nvm)); + if (target_check_error(target)) + return -1; + if (sr & STM32Lx_NVM_SR_BSY) + continue; + if ((sr & STM32Lx_NVM_SR_ERR_M) || !(sr & STM32Lx_NVM_SR_EOP)) + return -1; + break; + } + + return 0; +} + + +/** Write to data flash using operations through the debug interface. + NVM register file address chosen from target. Unaligned + destination writes are supported (though unaligned sources are + not). */ +static int stm32lx_nvm_data_write(struct target_s* target, + uint32_t destination, + const uint8_t* source_8, + int size) +{ + ADIv5_AP_t* ap = adiv5_target_ap(target); + const uint32_t nvm = stm32lx_nvm_phys(target); + const bool is_stm32l1 = stm32lx_is_stm32l1(target); + uint32_t* source = (uint32_t*) source_8; + + if (!stm32lx_nvm_prog_data_unlock(ap, nvm)) + return -1; + + + adiv5_ap_mem_write(ap, STM32Lx_NVM_PECR(nvm), + is_stm32l1 ? 0 : STM32Lx_NVM_PECR_DATA); + + while (size) { + size -= 4; + uint32_t v = *source++; + adiv5_ap_mem_write(ap, destination, v); + destination += 4; + + if (target_check_error(target)) + return -1; + } + + /* Disable further programming by locking PECR */ + stm32lx_nvm_lock(ap, nvm); + + /* Wait for completion or an error */ + while (1) { + uint32_t sr = adiv5_ap_mem_read(ap, STM32Lx_NVM_SR(nvm)); + if (target_check_error(target)) + return -1; + if (sr & STM32Lx_NVM_SR_BSY) + continue; + if ((sr & STM32Lx_NVM_SR_ERR_M) || !(sr & STM32Lx_NVM_SR_EOP)) + return -1; + break; + } + + return 0; +} + + +/** Write one option word. The address is the physical address of the + word and the value is a complete word value. The caller is + responsible for making sure that the value satisfies the proper + format where the upper 16 bits are the 1s complement of the lower + 16 bits. The funtion returns when the operation is complete. + The return value is true if the write succeeded. */ +static bool stm32lx_option_write(target *t, uint32_t address, uint32_t value) +{ + ADIv5_AP_t* ap = adiv5_target_ap(t); + const uint32_t nvm = stm32lx_nvm_phys(t); + + /* Erase and program option in one go. */ + adiv5_ap_mem_write(ap, STM32Lx_NVM_PECR(nvm), STM32Lx_NVM_PECR_FIX); + adiv5_ap_mem_write(ap, address, value); + + uint32_t sr; + do { + sr = adiv5_ap_mem_read(ap, STM32Lx_NVM_SR(nvm)); + } while (sr & STM32Lx_NVM_SR_BSY); + + return !(sr & STM32Lx_NVM_SR_ERR_M); +} + + +/** Write one eeprom value. This version is more flexible than that + bulk version used for writing data from the executable file. The + address is the physical address of the word and the value is a + complete word value. The funtion returns when the operation is + complete. The return value is true if the write succeeded. + FWIW, byte writing isn't supported because the adiv5 layer + doesn't support byte-level operations. */ +static bool stm32lx_eeprom_write(target *t, uint32_t address, + size_t cb, uint32_t value) +{ + ADIv5_AP_t* ap = adiv5_target_ap(t); + const uint32_t nvm = stm32lx_nvm_phys(t); + const bool is_stm32l1 = stm32lx_is_stm32l1(t); + + /* Clear errors. */ + adiv5_ap_mem_write(ap, STM32Lx_NVM_SR(nvm), STM32Lx_NVM_SR_ERR_M); + + /* Erase and program option in one go. */ + adiv5_ap_mem_write(ap, STM32Lx_NVM_PECR(nvm), + (is_stm32l1 ? 0 : STM32Lx_NVM_PECR_DATA) + | STM32Lx_NVM_PECR_FIX); + if (cb == 4) + adiv5_ap_mem_write(ap, address, value); + else if (cb == 2) + adiv5_ap_mem_write_halfword(ap, address, value); + else if (cb == 1) + adiv5_ap_mem_write_byte(ap, address, value); + else + return false; + + uint32_t sr; + do { + sr = adiv5_ap_mem_read(ap, STM32Lx_NVM_SR(nvm)); + } while (sr & STM32Lx_NVM_SR_BSY); + + return !(sr & STM32Lx_NVM_SR_ERR_M); +} + +static bool stm32lx_cmd_stubs(target* t, + int argc, char** argv) +{ + (void) t; + if (argc == 1) { + gdb_out("usage: mon stubs [enable/disable]\n"); + } + else if (argc == 2) { + size_t cb = strlen(argv[1]); + if (!strncasecmp(argv[1], "enable", cb)) + inhibit_stubs = 0; + if (!strncasecmp(argv[1], "disable", cb)) + inhibit_stubs = 1; + } + gdb_outf("stubs: %sabled\n", inhibit_stubs ? "dis" : "en"); + + return true; +} + +static bool stm32lx_cmd_option(target* t, int argc, char** argv) +{ + ADIv5_AP_t* ap = adiv5_target_ap(t); + const uint32_t nvm = stm32lx_nvm_phys(t); + const size_t opt_size = stm32lx_nvm_option_size(t); + + if (!stm32lx_nvm_opt_unlock(ap, nvm)) { + gdb_out("unable to unlock NVM option bytes\n"); + return true; + } + + size_t cb = strlen(argv[1]); + + if (argc == 2 && !strncasecmp(argv[1], "obl_launch", cb)) { + adiv5_ap_mem_write(ap, STM32Lx_NVM_PECR(nvm), + STM32Lx_NVM_PECR_OBL_LAUNCH); + } + else if (argc == 4 && !strncasecmp(argv[1], "raw", cb)) { + uint32_t addr = strtoul(argv[2], NULL, 0); + uint32_t val = strtoul(argv[3], NULL, 0); + gdb_outf("raw %08x <- %08x\n", addr, val); + if ( addr < STM32Lx_NVM_OPT_PHYS + || addr >= STM32Lx_NVM_OPT_PHYS + opt_size + || (addr & 3)) + goto usage; + if (!stm32lx_option_write(t, addr, val)) + gdb_out("option write failed\n"); + } + else if (argc == 4 && !strncasecmp(argv[1], "write", cb)) { + uint32_t addr = strtoul(argv[2], NULL, 0); + uint32_t val = strtoul(argv[3], NULL, 0); + val = (val & 0xffff) | ((~val & 0xffff) << 16); + gdb_outf("write %08x <- %08x\n", addr, val); + if ( addr < STM32Lx_NVM_OPT_PHYS + || addr >= STM32Lx_NVM_OPT_PHYS + opt_size + || (addr & 3)) + goto usage; + if (!stm32lx_option_write(t, addr, val)) + gdb_out("option write failed\n"); + } + else if (argc == 2 && !strncasecmp(argv[1], "show", cb)) + ; + else + goto usage; + + /* Report the current option values */ + for(int i = 0; i < opt_size; i += sizeof(uint32_t)) { + uint32_t addr = STM32Lx_NVM_OPT_PHYS + i; + uint32_t val = adiv5_ap_mem_read(ap, addr); + gdb_outf("0x%08x: 0x%04x 0x%04x %s\n", + addr, val & 0xffff, (val >> 16) & 0xffff, + ((val & 0xffff) == ((~val >> 16) & 0xffff)) + ? "OK" : "ERR"); + } + + if (stm32lx_is_stm32l1(t)) { + uint32_t optr = adiv5_ap_mem_read(ap, STM32Lx_NVM_OPTR(nvm)); + uint8_t rdprot = (optr >> STM32L1_NVM_OPTR_RDPROT_S) + & STM32L1_NVM_OPTR_RDPROT_M; + if (rdprot == STM32L1_NVM_OPTR_RDPROT_0) + rdprot = 0; + else if (rdprot == STM32L1_NVM_OPTR_RDPROT_2) + rdprot = 2; + else + rdprot = 1; + gdb_outf("OPTR: 0x%08x, RDPRT %d, SPRMD %d, " + "BOR %d, WDG_SW %d, nRST_STP %d, nRST_STBY %d, " + "nBFB2 %d\n", + optr, rdprot, + (optr & STM32L1_NVM_OPTR_SPRMOD) ? 1 : 0, + (optr >> STM32L1_NVM_OPTR_BOR_LEV_S) + & STM32L1_NVM_OPTR_BOR_LEV_M, + (optr & STM32L1_NVM_OPTR_WDG_SW) ? 1 : 0, + (optr & STM32L1_NVM_OPTR_nRST_STOP) ? 1 : 0, + (optr & STM32L1_NVM_OPTR_nRST_STDBY) ? 1 : 0, + (optr & STM32L1_NVM_OPTR_nBFB2) ? 1 : 0); + } + else { + uint32_t optr = adiv5_ap_mem_read(ap, STM32Lx_NVM_OPTR(nvm)); + uint8_t rdprot = (optr >> STM32L0_NVM_OPTR_RDPROT_S) + & STM32L0_NVM_OPTR_RDPROT_M; + if (rdprot == STM32L0_NVM_OPTR_RDPROT_0) + rdprot = 0; + else if (rdprot == STM32L0_NVM_OPTR_RDPROT_2) + rdprot = 2; + else + rdprot = 1; + gdb_outf("OPTR: 0x%08x, RDPROT %d, WPRMOD %d, WDG_SW %d, " + "BOOT1 %d\n", + optr, rdprot, + (optr & STM32L0_NVM_OPTR_WPRMOD) ? 1 : 0, + (optr & STM32L0_NVM_OPTR_WDG_SW) ? 1 : 0, + (optr & STM32L0_NVM_OPTR_BOOT1) ? 1 : 0); + } + + goto done; + +usage: + gdb_out("usage: monitor option [ARGS]\n"); + gdb_out(" show - Show options in NVM and as" + " loaded\n"); + gdb_out(" obl_launch - Reload options from NVM\n"); + gdb_out(" write - Set option half-word; " + "complement computed\n"); + gdb_out(" raw - Set option word\n"); + gdb_outf("The value of must be word aligned and from 0x%08x " + "to +0x%x\n", + STM32Lx_NVM_OPT_PHYS, + STM32Lx_NVM_OPT_PHYS + opt_size - sizeof(uint32_t)); + +done: + stm32lx_nvm_lock(ap, nvm); + return true; +} + + +static bool stm32lx_cmd_eeprom(target* t, int argc, char** argv) +{ + ADIv5_AP_t* ap = adiv5_target_ap(t); + const uint32_t nvm = stm32lx_nvm_phys(t); + + if (!stm32lx_nvm_prog_data_unlock(ap, nvm)) { + gdb_out("unable to unlock EEPROM\n"); + return true; + } + + size_t cb = strlen(argv[1]); + + if (argc == 4) { + uint32_t addr = strtoul(argv[2], NULL, 0); + uint32_t val = strtoul(argv[3], NULL, 0); + + if ( addr < STM32Lx_NVM_EEPROM_PHYS + || addr >= STM32Lx_NVM_EEPROM_PHYS + + stm32lx_nvm_eeprom_size(t)) + goto usage; + + if (!strncasecmp(argv[1], "byte", cb)) { + gdb_outf("write byte 0x%08x <- 0x%08x\n", addr, val); + if (!stm32lx_eeprom_write(t, addr, 1, val)) + gdb_out("eeprom write failed\n"); + } else if (!strncasecmp(argv[1], "halfword", cb)) { + val &= 0xffff; + gdb_outf("write halfword 0x%08x <- 0x%04x\n", + addr, val); + if (addr & 1) + goto usage; + if (!stm32lx_eeprom_write(t, addr, 2, val)) + gdb_out("eeprom write failed\n"); + } else if (!strncasecmp(argv[1], "word", cb)) { + gdb_outf("write word 0x%08x <- 0x%08x\n", addr, val); + if (addr & 3) + goto usage; + if (!stm32lx_eeprom_write(t, addr, 4, val)) + gdb_out("eeprom write failed\n"); + } + else + goto usage; + } + else + goto usage; + + goto done; + +usage: + gdb_out("usage: monitor eeprom [ARGS]\n"); + gdb_out(" byte - Write a byte\n"); + gdb_out(" halfword - Write a half-word\n"); + gdb_out(" word - Write a word\n"); + gdb_outf("The value of must in the interval [0x%08x, 0x%x)\n", + STM32Lx_NVM_EEPROM_PHYS, + STM32Lx_NVM_EEPROM_PHYS + + stm32lx_nvm_eeprom_size(t)); + +done: + stm32lx_nvm_lock(ap, nvm); + return true; +}