diff --git a/flashstub/README b/flashstub/README index 05172a4..155c8d9 100644 --- a/flashstub/README +++ b/flashstub/README @@ -1,5 +1,13 @@ -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. diff --git a/flashstub/code-to-array.pl b/flashstub/code-to-array.pl new file mode 100755 index 0000000..5333e31 --- /dev/null +++ b/flashstub/code-to-array.pl @@ -0,0 +1,24 @@ +#!/usr/bin/perl +# +# Convert the output of objdump to an array of bytes are can include +# into our program. + +while (<>) { + if (m/^\s*([0-9a-fA-F]+):\s*([0-9a-fA-F]+)(.*)/) { + my $addr = "0x$1"; + my $value = $2; + if (length ($value) == 4) { + print " [$addr/2] = 0x$value, // $_"; + } + else { + my $lsb = substr ($value, 4, 4); + my $msb = substr ($value, 0, 4); + print " [$addr/2] = 0x$lsb, // $_"; + print " [$addr/2 + 1] = 0x$msb,\n"; + } + } + else { + print "// ", $_; + } +} + diff --git a/flashstub/stm32l05x-nvm-prog-erase.cc b/flashstub/stm32l05x-nvm-prog-erase.cc new file mode 100644 index 0000000..28c11a3 --- /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 -S stm32l05x-nvm-prog-erase.o | ./code-to-array.pl > 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..ebb6fbb --- /dev/null +++ b/flashstub/stm32l05x-nvm-prog-erase.stub @@ -0,0 +1,159 @@ +// +// stm32l05x-nvm-prog-erase.o: file format elf32-littlearm +// +// +// Disassembly of section .text: +// +// 00000000 : +// ".word 0\n\t" +// ".word 0\n\t" +// ".word 0\n\t" +// ".word 0\n\t" +// ".word 0\n\t" +// "0:"); + [0x0/2] = 0xe00a, // 0: e00a b.n 18 + [0x2/2] = 0x46c0, // 2: 46c0 nop ; (mov r8, r8) +// ... +// +// auto& nvm = Nvm (Info.nvm); + [0x18/2] = 0x491a, // 18: 491a ldr r1, [pc, #104] ; (84 ) +// +// // 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); + [0x1a/2] = 0x8a08, // 1a: 8a08 ldrh r0, [r1, #16] + [0x1c/2] = 0x680c, // 1c: 680c ldr r4, [r1, #0] +// Info.size += remainder; + [0x1e/2] = 0x684d, // 1e: 684d ldr r5, [r1, #4] +// 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); + [0x20/2] = 0x1e42, // 20: 1e42 subs r2, r0, #1 + [0x22/2] = 0x4022, // 22: 4022 ands r2, r4 +// Info.size += remainder; + [0x24/2] = 0x1955, // 24: 1955 adds r5, r2, r5 +// Info.destination -= remainder/sizeof (*Info.destination); + [0x26/2] = 0x0892, // 26: 0892 lsrs r2, r2, #2 + [0x28/2] = 0x0092, // 28: 0092 lsls r2, r2, #2 + [0x2a/2] = 0x1aa2, // 2a: 1aa2 subs r2, r4, r2 + [0x2c/2] = 0x600a, // 2c: 600a str r2, [r1, #0] +// #define Nvm(nvm) (*reinterpret_cast(nvm)) +// #define Info (*reinterpret_cast(STM32Lx_STUB_INFO_PHYS)) +// +// namespace { +// inline __attribute((always_inline)) bool unlock (STM32::NVM& nvm) { +// nvm.pecr = STM32Lx_NVM_PECR_PELOCK; // Lock to guarantee unlock + [0x2e/2] = 0x2201, // 2e: 2201 movs r2, #1 +// ".word 0\n\t" +// ".word 0\n\t" +// ".word 0\n\t" +// "0:"); +// +// auto& nvm = Nvm (Info.nvm); + [0x30/2] = 0x68cb, // 30: 68cb ldr r3, [r1, #12] +// +// // 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; + [0x32/2] = 0x604d, // 32: 604d str r5, [r1, #4] + [0x34/2] = 0x605a, // 34: 605a str r2, [r3, #4] +// nvm.pkeyr = STM32::NVM::PKEY1; + [0x36/2] = 0x4a14, // 36: 4a14 ldr r2, [pc, #80] ; (88 ) + [0x38/2] = 0x60da, // 38: 60da str r2, [r3, #12] +// nvm.pkeyr = STM32::NVM::PKEY2; + [0x3a/2] = 0x4a14, // 3a: 4a14 ldr r2, [pc, #80] ; (8c ) + [0x3c/2] = 0x60da, // 3c: 60da str r2, [r3, #12] +// nvm.prgkeyr = STM32::NVM::PRGKEY1; + [0x3e/2] = 0x4a14, // 3e: 4a14 ldr r2, [pc, #80] ; (90 ) + [0x40/2] = 0x611a, // 40: 611a str r2, [r3, #16] +// nvm.prgkeyr = STM32::NVM::PRGKEY2; + [0x42/2] = 0x4a14, // 42: 4a14 ldr r2, [pc, #80] ; (94 ) + [0x44/2] = 0x611a, // 44: 611a str r2, [r3, #16] +// return !(nvm.pecr & STM32Lx_NVM_PECR_PRGLOCK); + [0x46/2] = 0x685a, // 46: 685a ldr r2, [r3, #4] +// Info.destination -= remainder/sizeof (*Info.destination); +// +// if (!unlock (nvm)) + [0x48/2] = 0x0792, // 48: 0792 lsls r2, r2, #30 + [0x4a/2] = 0xd502, // 4a: d502 bpl.n 52 +// } +// inline __attribute((always_inline)) void lock (STM32::NVM& nvm) { +// nvm.pecr = STM32Lx_NVM_PECR_PELOCK; } + [0x4c/2] = 0x2201, // 4c: 2201 movs r2, #1 + [0x4e/2] = 0x605a, // 4e: 605a str r2, [r3, #4] +// Info.size -= Info.page_size; +// } +// +// quit: +// lock (nvm); +// __asm volatile ("bkpt"); + [0x50/2] = 0xbe00, // 50: be00 bkpt 0x0000 +// Info.destination -= remainder/sizeof (*Info.destination); +// +// if (!unlock (nvm)) +// goto quit; +// +// nvm.sr = STM32Lx_NVM_SR_ERR_M; // Clear errors + [0x52/2] = 0x4a11, // 52: 4a11 ldr r2, [pc, #68] ; (98 ) + [0x54/2] = 0x619a, // 54: 619a str r2, [r3, #24] +// +// // Enable erasing +// nvm.pecr = STM32Lx_NVM_PECR_PROG | STM32Lx_NVM_PECR_ERASE; + [0x56/2] = 0x2282, // 56: 2282 movs r2, #130 ; 0x82 + [0x58/2] = 0x0092, // 58: 0092 lsls r2, r2, #2 + [0x5a/2] = 0x605a, // 5a: 605a str r2, [r3, #4] +// if ((nvm.pecr & (STM32Lx_NVM_PECR_PROG | STM32Lx_NVM_PECR_ERASE)) + [0x5c/2] = 0x685c, // 5c: 685c ldr r4, [r3, #4] + [0x5e/2] = 0x4014, // 5e: 4014 ands r4, r2 + [0x60/2] = 0x4294, // 60: 4294 cmp r4, r2 + [0x62/2] = 0xd1f3, // 62: d1f3 bne.n 4c +// goto quit; +// +// while (Info.size > 0) { +// *Info.destination = 0; // Initiate erase +// +// Info.destination += Info.page_size/sizeof (*Info.destination); + [0x64/2] = 0x0884, // 64: 0884 lsrs r4, r0, #2 + [0x66/2] = 0x00a4, // 66: 00a4 lsls r4, r4, #2 +// 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) { + [0x68/2] = 0x684d, // 68: 684d ldr r5, [r1, #4] + [0x6a/2] = 0x4a06, // 6a: 4a06 ldr r2, [pc, #24] ; (84 ) + [0x6c/2] = 0x2d00, // 6c: 2d00 cmp r5, #0 + [0x6e/2] = 0xdded, // 6e: dded ble.n 4c +// *Info.destination = 0; // Initiate erase + [0x70/2] = 0x2600, // 70: 2600 movs r6, #0 + [0x72/2] = 0x6815, // 72: 6815 ldr r5, [r2, #0] + [0x74/2] = 0x602e, // 74: 602e str r6, [r5, #0] +// +// Info.destination += Info.page_size/sizeof (*Info.destination); + [0x76/2] = 0x6815, // 76: 6815 ldr r5, [r2, #0] + [0x78/2] = 0x192d, // 78: 192d adds r5, r5, r4 + [0x7a/2] = 0x6015, // 7a: 6015 str r5, [r2, #0] +// Info.size -= Info.page_size; + [0x7c/2] = 0x6855, // 7c: 6855 ldr r5, [r2, #4] + [0x7e/2] = 0x1a2d, // 7e: 1a2d subs r5, r5, r0 + [0x80/2] = 0x6055, // 80: 6055 str r5, [r2, #4] + [0x82/2] = 0xe7f1, // 82: e7f1 b.n 68 + [0x84/2] = 0x0004, // 84: 20000004 .word 0x20000004 + [0x84/2 + 1] = 0x2000, + [0x88/2] = 0xcdef, // 88: 89abcdef .word 0x89abcdef + [0x88/2 + 1] = 0x89ab, + [0x8c/2] = 0x0405, // 8c: 02030405 .word 0x02030405 + [0x8c/2 + 1] = 0x0203, + [0x90/2] = 0xaebf, // 90: 8c9daebf .word 0x8c9daebf + [0x90/2 + 1] = 0x8c9d, + [0x94/2] = 0x1516, // 94: 13141516 .word 0x13141516 + [0x94/2 + 1] = 0x1314, + [0x98/2] = 0x0700, // 98: 00010700 .word 0x00010700 + [0x98/2 + 1] = 0x0001, diff --git a/flashstub/stm32l05x-nvm-prog-write.cc b/flashstub/stm32l05x-nvm-prog-write.cc new file mode 100644 index 0000000..78799d9 --- /dev/null +++ b/flashstub/stm32l05x-nvm-prog-write.cc @@ -0,0 +1,112 @@ +/* @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 -S stm32l05x-nvm-prog-write.o | ./code-to-array.pl > 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..6fd661f --- /dev/null +++ b/flashstub/stm32l05x-nvm-prog-write.stub @@ -0,0 +1,201 @@ +// +// stm32l05x-nvm-prog-write.o: file format elf32-littlearm +// +// +// Disassembly of section .text: +// +// 00000000 : +// ".word 0\n\t" +// ".word 0\n\t" +// ".word 0\n\t" +// ".word 0\n\t" +// ".word 0\n\t" +// "0:"); + [0x0/2] = 0xe00a, // 0: e00a b.n 18 + [0x2/2] = 0x46c0, // 2: 46c0 nop ; (mov r8, r8) +// ... +// #define Nvm(nvm) (*reinterpret_cast(nvm)) +// #define Info (*reinterpret_cast(STM32Lx_STUB_INFO_PHYS)) +// +// namespace { +// inline __attribute((always_inline)) bool unlock (STM32::NVM& nvm) { +// nvm.pecr = STM32Lx_NVM_PECR_PELOCK; // Lock to guarantee unlock + [0x18/2] = 0x2201, // 18: 2201 movs r2, #1 +// +// auto& nvm = Nvm (Info.nvm); + [0x1a/2] = 0x4b2a, // 1a: 4b2a ldr r3, [pc, #168] ; (c4 ) + [0x1c/2] = 0x68d9, // 1c: 68d9 ldr r1, [r3, #12] + [0x1e/2] = 0x604a, // 1e: 604a str r2, [r1, #4] +// nvm.pkeyr = STM32::NVM::PKEY1; + [0x20/2] = 0x4a29, // 20: 4a29 ldr r2, [pc, #164] ; (c8 ) + [0x22/2] = 0x60ca, // 22: 60ca str r2, [r1, #12] +// nvm.pkeyr = STM32::NVM::PKEY2; + [0x24/2] = 0x4a29, // 24: 4a29 ldr r2, [pc, #164] ; (cc ) + [0x26/2] = 0x60ca, // 26: 60ca str r2, [r1, #12] +// nvm.prgkeyr = STM32::NVM::PRGKEY1; + [0x28/2] = 0x4a29, // 28: 4a29 ldr r2, [pc, #164] ; (d0 ) + [0x2a/2] = 0x610a, // 2a: 610a str r2, [r1, #16] +// nvm.prgkeyr = STM32::NVM::PRGKEY2; + [0x2c/2] = 0x4a29, // 2c: 4a29 ldr r2, [pc, #164] ; (d4 ) + [0x2e/2] = 0x610a, // 2e: 610a str r2, [r1, #16] +// return !(nvm.pecr & STM32Lx_NVM_PECR_PRGLOCK); + [0x30/2] = 0x684a, // 30: 684a ldr r2, [r1, #4] +// +// if (!unlock (nvm)) + [0x32/2] = 0x0792, // 32: 0792 lsls r2, r2, #30 + [0x34/2] = 0xd502, // 34: d502 bpl.n 3c +// } +// inline __attribute((always_inline)) void lock (STM32::NVM& nvm) { +// nvm.pecr = STM32Lx_NVM_PECR_PELOCK; } + [0x36/2] = 0x2301, // 36: 2301 movs r3, #1 + [0x38/2] = 0x604b, // 38: 604b str r3, [r1, #4] +// } +// } +// +// quit: +// lock (nvm); +// __asm volatile ("bkpt"); + [0x3a/2] = 0xbe00, // 3a: be00 bkpt 0x0000 +// auto& nvm = Nvm (Info.nvm); +// +// if (!unlock (nvm)) +// goto quit; +// +// nvm.sr = STM32Lx_NVM_SR_ERR_M; // Clear errors + [0x3c/2] = 0x4826, // 3c: 4826 ldr r0, [pc, #152] ; (d8 ) + [0x3e/2] = 0x6188, // 3e: 6188 str r0, [r1, #24] +// +// while (Info.size > 0) { + [0x40/2] = 0x685d, // 40: 685d ldr r5, [r3, #4] + [0x42/2] = 0x4e20, // 42: 4e20 ldr r6, [pc, #128] ; (c4 ) + [0x44/2] = 0x2d00, // 44: 2d00 cmp r5, #0 + [0x46/2] = 0xddf6, // 46: ddf6 ble.n 36 +// +// // Either we're not half-page aligned or we have less than a half +// // page to write +// if (Info.size < Info.page_size/2 + [0x48/2] = 0x8a32, // 48: 8a32 ldrh r2, [r6, #16] + [0x4a/2] = 0x0852, // 4a: 0852 lsrs r2, r2, #1 + [0x4c/2] = 0x1e54, // 4c: 1e54 subs r4, r2, #1 + [0x4e/2] = 0x4295, // 4e: 4295 cmp r5, r2 + [0x50/2] = 0xdb02, // 50: db02 blt.n 58 +// || (reinterpret_cast (Info.destination) + [0x52/2] = 0x6837, // 52: 6837 ldr r7, [r6, #0] + [0x54/2] = 0x4227, // 54: 4227 tst r7, r4 + [0x56/2] = 0xd01d, // 56: d01d beq.n 94 +// & (Info.page_size/2 - 1))) { +// nvm.pecr = (Info.options & OPT_STM32L1) ? 0 +// : STM32Lx_NVM_PECR_PROG; // Word programming + [0x58/2] = 0x2602, // 58: 2602 movs r6, #2 +// // 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 + [0x5a/2] = 0x8a5f, // 5a: 8a5f ldrh r7, [r3, #18] +// : STM32Lx_NVM_PECR_PROG; // Word programming + [0x5c/2] = 0x4037, // 5c: 4037 ands r7, r6 + [0x5e/2] = 0x427e, // 5e: 427e negs r6, r7 + [0x60/2] = 0x417e, // 60: 417e adcs r6, r7 + [0x62/2] = 0x00f6, // 62: 00f6 lsls r6, r6, #3 + [0x64/2] = 0x604e, // 64: 604e str r6, [r1, #4] +// size_t c = Info.page_size/2 +// - (reinterpret_cast (Info.destination) +// & (Info.page_size/2 - 1)); + [0x66/2] = 0x681e, // 66: 681e ldr r6, [r3, #0] + [0x68/2] = 0x4034, // 68: 4034 ands r4, r6 + [0x6a/2] = 0x1b12, // 6a: 1b12 subs r2, r2, r4 + [0x6c/2] = 0x42aa, // 6c: 42aa cmp r2, r5 + [0x6e/2] = 0xd900, // 6e: d900 bls.n 72 + [0x70/2] = 0x1c2a, // 70: 1c2a adds r2, r5, #0 +// if (c > Info.size) +// c = Info.size; +// Info.size -= c; + [0x72/2] = 0x1aad, // 72: 1aad subs r5, r5, r2 + [0x74/2] = 0x605d, // 74: 605d str r5, [r3, #4] +// c /= 4; + [0x76/2] = 0x0892, // 76: 0892 lsrs r2, r2, #2 +// while (c--) { + [0x78/2] = 0x3a01, // 78: 3a01 subs r2, #1 + [0x7a/2] = 0xd3e1, // 7a: d3e1 bcc.n 40 +// uint32_t v = *Info.source++; + [0x7c/2] = 0x689c, // 7c: 689c ldr r4, [r3, #8] + [0x7e/2] = 0x1d25, // 7e: 1d25 adds r5, r4, #4 + [0x80/2] = 0x609d, // 80: 609d str r5, [r3, #8] + [0x82/2] = 0x6825, // 82: 6825 ldr r5, [r4, #0] +// *Info.destination++ = v; + [0x84/2] = 0x681c, // 84: 681c ldr r4, [r3, #0] + [0x86/2] = 0x1d26, // 86: 1d26 adds r6, r4, #4 + [0x88/2] = 0x601e, // 88: 601e str r6, [r3, #0] + [0x8a/2] = 0x6025, // 8a: 6025 str r5, [r4, #0] +// if (nvm.sr & STM32Lx_NVM_SR_ERR_M) + [0x8c/2] = 0x698c, // 8c: 698c ldr r4, [r1, #24] + [0x8e/2] = 0x4204, // 8e: 4204 tst r4, r0 + [0x90/2] = 0xd0f2, // 90: d0f2 beq.n 78 + [0x92/2] = 0xe7d0, // 92: e7d0 b.n 36 +// goto quit; +// } +// } +// // Or we are writing a half-page(s) +// else { +// nvm.pecr = STM32Lx_NVM_PECR_PROG | STM32Lx_NVM_PECR_FPRG; // Half-page prg + [0x94/2] = 0x2481, // 94: 2481 movs r4, #129 ; 0x81 +// size_t c = Info.size & ~(Info.page_size/2 - 1); + [0x96/2] = 0x4252, // 96: 4252 negs r2, r2 + [0x98/2] = 0x402a, // 98: 402a ands r2, r5 +// Info.size -= c; + [0x9a/2] = 0x1aad, // 9a: 1aad subs r5, r5, r2 +// goto quit; +// } +// } +// // Or we are writing a half-page(s) +// else { +// nvm.pecr = STM32Lx_NVM_PECR_PROG | STM32Lx_NVM_PECR_FPRG; // Half-page prg + [0x9c/2] = 0x00e4, // 9c: 00e4 lsls r4, r4, #3 + [0x9e/2] = 0x604c, // 9e: 604c str r4, [r1, #4] +// size_t c = Info.size & ~(Info.page_size/2 - 1); +// Info.size -= c; +// c /= 4; + [0xa0/2] = 0x0892, // a0: 0892 lsrs r2, r2, #2 +// } +// // 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; + [0xa2/2] = 0x6075, // a2: 6075 str r5, [r6, #4] +// c /= 4; +// while (c--) { + [0xa4/2] = 0x3a01, // a4: 3a01 subs r2, #1 + [0xa6/2] = 0xd308, // a6: d308 bcc.n ba +// uint32_t v = *Info.source++; + [0xa8/2] = 0x689c, // a8: 689c ldr r4, [r3, #8] + [0xaa/2] = 0x1d25, // aa: 1d25 adds r5, r4, #4 + [0xac/2] = 0x609d, // ac: 609d str r5, [r3, #8] + [0xae/2] = 0x6825, // ae: 6825 ldr r5, [r4, #0] +// *Info.destination++ = v; + [0xb0/2] = 0x681c, // b0: 681c ldr r4, [r3, #0] + [0xb2/2] = 0x1d26, // b2: 1d26 adds r6, r4, #4 + [0xb4/2] = 0x601e, // b4: 601e str r6, [r3, #0] + [0xb6/2] = 0x6025, // b6: 6025 str r5, [r4, #0] + [0xb8/2] = 0xe7f4, // b8: e7f4 b.n a4 +// } +// if (nvm.sr & STM32Lx_NVM_SR_ERR_M) + [0xba/2] = 0x698a, // ba: 698a ldr r2, [r1, #24] + [0xbc/2] = 0x4202, // bc: 4202 tst r2, r0 + [0xbe/2] = 0xd0bf, // be: d0bf beq.n 40 + [0xc0/2] = 0xe7b9, // c0: e7b9 b.n 36 + [0xc2/2] = 0x46c0, // c2: 46c0 nop ; (mov r8, r8) + [0xc4/2] = 0x0004, // c4: 20000004 .word 0x20000004 + [0xc4/2 + 1] = 0x2000, + [0xc8/2] = 0xcdef, // c8: 89abcdef .word 0x89abcdef + [0xc8/2 + 1] = 0x89ab, + [0xcc/2] = 0x0405, // cc: 02030405 .word 0x02030405 + [0xcc/2 + 1] = 0x0203, + [0xd0/2] = 0xaebf, // d0: 8c9daebf .word 0x8c9daebf + [0xd0/2 + 1] = 0x8c9d, + [0xd4/2] = 0x1516, // d4: 13141516 .word 0x13141516 + [0xd4/2 + 1] = 0x1314, + [0xd8/2] = 0x0700, // d8: 00010700 .word 0x00010700 + [0xd8/2 + 1] = 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..126f2ab --- /dev/null +++ b/src/include/stm32lx-nvm.h @@ -0,0 +1,201 @@ +/* @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) { + nvm.pecr = STM32Lx_NVM_PECR_PELOCK; // Lock to guarantee unlock + 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..0f6dcdb --- /dev/null +++ b/src/stm32l0.c @@ -0,0 +1,1076 @@ +/* + * 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. + + 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 broken. 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. + + 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. + + 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 suspect + 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. + + 0x1ff80000: 0x00aa 0xff55 OK + 0x1ff80004: 0x8070 0x7f8f OK + 0x1ff80008: 0x0000 0xffff OK + OPTR: 0x807000aa, RDPROT 0, WPRMOD 0, WDG_SW 1, BOOT1 1 + + + Options code +p *(int*)0x40022004 = 1 +p *(int*)0x4002200c = 0x89abcdef +p *(int*)0x4002200c = 0x02030405 +p *(int*)0x40022014 = 0xfbead9c8 +p *(int*)0x40022014 = 0x24252627 +p *(int*)0x40022004 = 0x10 +x/4wx 0x40022000 +p *(int*)0x1ff80000 = 0xff5500aa + + l1 program +p *(int*)0x40023c04 = 1 +p *(int*)0x40023c0c = 0x89abcdef +p *(int*)0x40023c0c = 0x02030405 +p *(int*)0x40023c10 = 0x8c9daebf +p *(int*)0x40023c10 = 0x13141516 + + +p *(int*)0x40023c14 = 0xfbead9c8 +p *(int*)0x40023c14 = 0x24252627 + +*/ + +#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; + +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 stm32l0_cmd_erase_mass (target* t, int argc, char** argv); +static bool stm32lx_cmd_option (target* t, int argc, char** argv); +static bool stm32lx_cmd_eeprom (target* t, int argc, char** argv); +//static bool stm32l0_cmd_reset (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" }, +// { "erase_mass", (cmd_handler) stm32l0_cmd_erase_mass, +// "Erase NVM flash and data" }, +// { "reset", (cmd_handler) stm32l0_cmd_reset, "Reset target" }, + { "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't handle unaligned destination or non-word writes. */ + /* *** FIXME: we should handle misaligned writes by padding with + *** zeros. Probably, the only real time we'd see something + *** misaligned would be on a write to a final half-word. Perhaps + *** this could be handled with the stub? In fact, aligning the + *** start is going to be mandatory. We will change the code to + *** cope with a trailing half-word. */ + 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 + sub 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 STM32L1xx. This is the lead function + and it will ibvoke an implementation, stubbed or not depending on + the options and the range of addresses. */ +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); + + /* 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 stm32l0 cannot perform anything smaller than a word + write due to the ECC bits. */ + 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 STM32L1xx. */ + 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. + + *** FIXME: need to make this work with writing a single byte as + well as words. */ +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); + + /* *** FIXME: need to make this work with writing a single byte. */ + if ((destination & 3) || (size & 3)) + return -1; + + if (!stm32lx_nvm_prog_data_unlock (ap, nvm)) + return -1; + + uint32_t* source = (uint32_t*) source_8; + + 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 + 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) +{ + 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; +} + +#if 0 +static bool stm32l0_cmd_erase_mass (target* t, int argc , char** argv) +{ + ADIv5_AP_t* ap = adiv5_target_ap (t); + + stm32lx_nvm_opt_unlock (ap); + + 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); + + stm32l0_nvm_lock (ap); + return true; +} +#endif + +#if 0 +static bool stm32l0_cmd_reset (target* t, int argc, char** argv) +{ + gdb_out ("Resetting target\n"); + target_reset (t); + + return true; +} +#endif + +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 0 + if (!strncasecmp (argv[1], "byte", cb)) { + gdb_outf ("write byte 0x%08x <- 0x%08x\n", addr, val); + if (!stm32l0_eeprom_write (t, addr, 1, val)) + gdb_out ("eeprom write failed\n"); + } else +#endif + 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; +}