Add working GDB remote debug server.
authorPeter Zotov <whitequark@whitequark.org>
Tue, 15 Feb 2011 02:15:28 +0000 (05:15 +0300)
committerPeter Zotov <whitequark@whitequark.org>
Tue, 15 Feb 2011 02:15:28 +0000 (05:15 +0300)
AUTHORS
README
build/.gitignore [new file with mode: 0644]
build/Makefile
src/gdb-remote.c [new file with mode: 0644]
src/gdb-remote.h [new file with mode: 0644]
src/gdb-server.c [new file with mode: 0644]
src/stlink-access-test.c [deleted file]
src/stlink-hw.c [new file with mode: 0644]
src/stlink-hw.h [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
index f0ec8e599e806c030226c10c8f73a8da65d6ee26..b7fbf6a401cfe7919f3ee6970a2c0454d994aecc 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,3 +1,4 @@
 Martin Capitanio <m@capitanio.org>
 Spencer Oliver <spen@spen-soft.co.uk>
 Le Mentec Fabien <fabien.lementec@gmail.com>
+Peter Zotov <whitequark@whitequark.org>
diff --git a/README b/README
index 986169702cf2d84a3de349ba4a547958092e9d3f..77697c89b50864c19b9a2669f1ef42215c1567d9 100644 (file)
--- a/README
+++ b/README
@@ -1,5 +1,18 @@
-cd build
-make
+HOWTO
+=====
 
-edit Makefile
-make run
+To run the gdb server, do (you do not need sudo if you have
+set up permissions correctly):
+$ make -C build && sudo ./build/st-util 1234 /dev/sg1
+
+Then, in gdb:
+(gdb) target remote :1234
+
+Have fun!
+
+Caveats
+=======
+
+`continue' GDB command does not work: target does not step at
+all or steps with a turtle speed. Looks like there's something
+wrong with SCSI requests.
diff --git a/build/.gitignore b/build/.gitignore
new file mode 100644 (file)
index 0000000..bc74c12
--- /dev/null
@@ -0,0 +1,3 @@
+*.o
+*.d
+st-util
index 34c25c609cb2c2c5b5ae051210d2ef1b097541cc..076f974abb284d6842f469d4cd1a4900af487968 100644 (file)
@@ -1,5 +1,4 @@
-PRG := stlink-access-test
-DEV := /dev/sg2
+PRG := st-util
 
 all: $(PRG)
 
@@ -7,21 +6,16 @@ LIBS := \
        -lsgutils2
 
 OBJS += \
-       stlink-access-test.o 
+       stlink-hw.o gdb-remote.o gdb-server.o
 
 $(PRG): $(OBJS)
-       @echo 'Invoking: GCC C Linker'
-       gcc  -o$(PRG) $(OBJS) $(LIBS)
+       gcc -o $(PRG) $(OBJS) $(LIBS)
 
 %.o: ../src/%.c
-       @echo 'Building file: $<'
-       gcc -O0 -g3 -Wall -c -fmessage-length=0 -std=gnu99 -MMD -MP \
+       gcc -O3 -g3 -Wall -Werror -c -std=gnu99 -MMD -MP \
+               -fno-strict-aliasing -Wno-unused \
                -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)"\
-               -o"$@" "$<"
+               -o "$@" "$<"
 
 clean:
        @rm -vf *.d *.o $(PRG)
-
-run: all
-       cp $(PRG) /tmp/
-       sudo /tmp/$(PRG) $(DEV)
diff --git a/src/gdb-remote.c b/src/gdb-remote.c
new file mode 100644 (file)
index 0000000..8ed132f
--- /dev/null
@@ -0,0 +1,145 @@
+/* -*- tab-width:8 -*- */
+
+/*
+ Copyright (C)  2011 Peter Zotov <whitequark@whitequark.org>
+ Use of this source code is governed by a BSD-style
+ license that can be found in the LICENSE file.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+
+static const char hex[] = "0123456789abcdef";
+
+int gdb_send_packet(int fd, char* data) {
+       unsigned length = strlen(data) + 5;
+       char* packet = malloc(length); /* '$' data (hex) '#' cksum (hex) */
+
+       memset(packet, 0, length);
+
+       packet[0] = '$';
+
+       uint8_t cksum = 0;
+       for(int i = 0; i < strlen(data); i++) {
+               packet[i + 1] = data[i];
+               cksum += data[i];
+       }
+
+       packet[length - 4] = '#';
+       packet[length - 3] = hex[cksum >> 4];
+       packet[length - 2] = hex[cksum & 0xf];
+
+       while(1) {
+               if(write(fd, packet, length) != length) {
+                       free(packet);
+                       return -2;
+               }
+
+               char ack;
+               if(read(fd, &ack, 1) != 1) {
+                       free(packet);
+                       return -2;
+               }
+
+               if(ack == '+') {
+                       free(packet);
+                       return 0;
+               }
+       }
+}
+
+#define ALLOC_STEP 1024
+
+int gdb_recv_packet(int fd, char** buffer) {
+       unsigned packet_size = ALLOC_STEP + 1, packet_idx = 0;
+       uint8_t cksum = 0;
+       char recv_cksum[3] = {0};
+       char* packet_buffer = malloc(packet_size);
+       unsigned state;
+
+start:
+       state = 0;
+       /*
+        * 0: waiting $
+        * 1: data, waiting #
+        * 2: cksum 1
+        * 3: cksum 2
+        * 4: fin
+        */
+
+       char c;
+       while(state != 4) {
+               if(read(fd, &c, 1) != 1) {
+                       return -2;
+               }
+
+               switch(state) {
+               case 0:
+                       if(c != '$') {
+                               // ignore
+                       } else {
+                               state = 1;
+                       }
+                       break;
+
+               case 1:
+                       if(c == '#') {
+                               state = 2;
+                       } else {
+                               packet_buffer[packet_idx++] = c;
+                               cksum += c;
+
+                               if(packet_idx == packet_size) {
+                                       packet_size += ALLOC_STEP;
+                                       packet_buffer = realloc(packet_buffer, packet_size);
+                               }
+                       }
+                       break;
+
+               case 2:
+                       recv_cksum[0] = c;
+                       state = 3;
+                       break;
+
+               case 3:
+                       recv_cksum[1] = c;
+                       state = 4;
+                       break;
+               }
+       }
+
+       uint8_t recv_cksum_int = strtoul(recv_cksum, NULL, 16);
+       if(recv_cksum_int != cksum) {
+               char nack = '-';
+               if(write(fd, &nack, 1) != 1) {
+                       return -2;
+               }
+
+               goto start;
+       } else {
+               char ack = '+';
+               if(write(fd, &ack, 1) != 1) {
+                       return -2;
+               }
+       }
+
+       packet_buffer[packet_idx] = 0;
+       *buffer = packet_buffer;
+
+       return packet_size;
+}
+
+int gdb_wait_for_interrupt(int fd) {
+       char c;
+       while(1) {
+               if(read(fd, &c, 1) != 1)
+                       return -2;
+
+               if(c == '\x03') // ^C
+                       return 0;
+       }
+}
+
diff --git a/src/gdb-remote.h b/src/gdb-remote.h
new file mode 100644 (file)
index 0000000..d4c7d7a
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef _GDB_REMOTE_H_
+#define _GDB_REMOTE_H_
+
+int gdb_send_packet(int fd, char* data);
+int gdb_recv_packet(int fd, char** buffer);
+int gdb_wait_for_interrupt(int fd);
+
+#endif
diff --git a/src/gdb-server.c b/src/gdb-server.c
new file mode 100644 (file)
index 0000000..cd44cc0
--- /dev/null
@@ -0,0 +1,244 @@
+/* -*- tab-width:8 -*- */
+
+/*
+ Copyright (C)  2011 Peter Zotov <whitequark@whitequark.org>
+ Use of this source code is governed by a BSD-style
+ license that can be found in the LICENSE file.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include "gdb-remote.h"
+#include "stlink-hw.h"
+
+static const char hex[] = "0123456789abcdef";
+
+int main(int argc, char** argv) {
+       struct sockaddr_in serv_addr;
+
+       if(argc != 3) {
+               fprintf(stderr, "Usage: %s <port> /dev/sgX\n", argv[0]);
+               return 1;
+       }
+
+       int port = atoi(argv[1]);
+
+       int sock = socket(AF_INET, SOCK_STREAM, 0);
+       if(sock < 0) {
+               perror("socket");
+               return 1;
+       }
+
+       memset((char *) &serv_addr, 0, sizeof(serv_addr));
+       serv_addr.sin_family = AF_INET;
+       serv_addr.sin_addr.s_addr = INADDR_ANY;
+       serv_addr.sin_port = htons(port);
+
+       if(bind(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
+               perror("bind");
+               return 1;
+       }
+
+       if(listen(sock, 5) < 0) {
+               perror("listen");
+               return 1;
+       }
+
+       struct stlink *sl = stlink_quirk_open(argv[2], 0);
+       if (sl == NULL)
+               return 1;
+
+       if(stlink_current_mode(sl) != STLINK_DEV_DEBUG_MODE)
+               stlink_enter_swd_mode(sl);
+
+       stlink_core_id(sl);
+       printf("Debugging ARM core %08x.\n", sl->core_id);
+
+       stlink_force_debug(sl);
+       stlink_reset(sl);
+
+       printf("Listening at *:%d...\n", port);
+
+       int client = accept(sock, NULL, NULL);
+       if(client < 0) {
+               perror("accept");
+               return 1;
+       }
+
+       close(sock);
+
+       printf("GDB connected.\n");
+
+       while(1) {
+               char* packet;
+
+               int status = gdb_recv_packet(client, &packet);
+               if(status < 0) {
+                       fprintf(stderr, "cannot recv: %d\n", status);
+                       return 1;
+               }
+
+               //printf("recv: %s\n", packet);
+
+               char* reply = NULL;
+
+               switch(packet[0]) {
+               case 'c':
+                       stlink_run(sl);
+
+                       printf("Core running, waiting for interrupt.\n");
+
+                       int status = gdb_wait_for_interrupt(client);
+                       if(status < 0) {
+                               fprintf(stderr, "cannot wait for int: %d\n", status);
+                               return 1;
+                       }
+
+                       stlink_force_debug(sl);
+
+                       reply = strdup("S05"); // TRAP
+                       break;
+
+               case 's':
+                       stlink_step(sl);
+
+                       reply = strdup("S05"); // TRAP
+                       break;
+
+               case '?':
+                       reply = strdup("S05"); // TRAP
+                       break;
+
+               case 'g':
+                       stlink_read_all_regs(sl);
+
+                       reply = calloc(8 * 16 + 1, 1);
+                       for(int i = 0; i < 16; i++)
+                               sprintf(&reply[i * 8], "%08x", htonl(sl->reg.r[i]));
+
+                       break;
+
+               case 'p': {
+                       unsigned id = strtoul(&packet[1], NULL, 16), reg = 0xDEADDEAD;
+
+                       if(id < 16) {
+                               stlink_read_reg(sl, id);
+                               reg = htonl(sl->reg.r[id]);
+                       } else if(id == 0x19) {
+                               stlink_read_reg(sl, 16);
+                               reg = htonl(sl->reg.xpsr);
+                       } else {
+                               reply = strdup("E00");
+                       }
+
+                       reply = calloc(8 + 1, 1);
+                       sprintf(reply, "%08x", reg);
+
+                       break;
+               }
+
+               case 'P': {
+                       char* s_reg = &packet[1];
+                       char* s_value = strstr(&packet[1], "=") + 1;
+
+                       unsigned reg   = strtoul(s_reg,   NULL, 16);
+                       unsigned value = strtoul(s_value, NULL, 16);
+
+                       if(reg < 16) {
+                               stlink_write_reg(sl, ntohl(value), reg);
+                       } else if(reg == 0x19) {
+                               stlink_write_reg(sl, ntohl(value), 16);
+                       } else {
+                               reply = strdup("E00");
+                       }
+
+                       if(!reply) {
+                               reply = strdup("OK");
+                       }
+
+                       break;
+               }
+
+               case 'G':
+                       for(int i = 0; i < 16; i++) {
+                               char str[9] = {0};
+                               strncpy(str, &packet[1 + i * 8], 8);
+                               uint32_t reg = strtoul(str, NULL, 16);
+                               stlink_write_reg(sl, ntohl(reg), i);
+                       }
+
+                       reply = strdup("OK");
+                       break;
+
+               case 'm': {
+                       char* s_start = &packet[1];
+                       char* s_count = strstr(&packet[1], ",") + 1;
+
+                       stm32_addr_t start = strtoul(s_start, NULL, 16);
+                       unsigned     count = strtoul(s_count, NULL, 16);
+
+                       unsigned adj_start = start % 4;
+
+                       stlink_read_mem32(sl, start - adj_start, (count % 4 == 0) ?
+                                               count : count + 4 - (count % 4));
+
+                       reply = calloc(count * 2 + 1, 1);
+                       for(int i = 0; i < count; i++) {
+                               reply[i * 2 + 0] = hex[sl->q_buf[i + adj_start] >> 4];
+                               reply[i * 2 + 1] = hex[sl->q_buf[i + adj_start] & 0xf];
+                       }
+
+                       break;
+               }
+
+               case 'M': {
+                       char* s_start = &packet[1];
+                       char* s_count = strstr(&packet[1], ",") + 1;
+                       char* hexdata = strstr(packet, ":") + 1;
+
+                       stm32_addr_t start = strtoul(s_start, NULL, 16);
+                       unsigned     count = strtoul(s_count, NULL, 16);
+
+                       for(int i = 0; i < count; i ++) {
+                               char hex[3] = { hexdata[i*2], hexdata[i*2+1], 0 };
+                               uint8_t byte = strtoul(hex, NULL, 16);
+                               sl->q_buf[i] = byte;
+                       }
+
+                       if((count % 4) == 0 && (start % 4) == 0) {
+                               stlink_write_mem32(sl, start, count);
+                       } else {
+                               stlink_write_mem8(sl, start, count);
+                       }
+
+                       reply = strdup("OK");
+
+                       break;
+               }
+
+               default:
+                       reply = strdup("");
+               }
+
+               if(reply) {
+                       //printf("send: %s\n", reply);
+
+                       int result = gdb_send_packet(client, reply);
+                       if(result != 0) {
+                               fprintf(stderr, "cannot send: %d\n", result);
+                               return 1;
+                       }
+
+                       free(reply);
+               }
+
+               free(packet);
+       }
+
+       return 0;
+}
diff --git a/src/stlink-access-test.c b/src/stlink-access-test.c
deleted file mode 100644 (file)
index 1fa6e5f..0000000
+++ /dev/null
@@ -1,1964 +0,0 @@
-/*
- Copyright (c) 2010 "Capt'ns Missing Link" Authors. All rights reserved.
- Use of this source code is governed by a BSD-style
- license that can be found in the LICENSE file.
-
- A linux stlink access demo. The purpose of this file is to mitigate the usual
- "reinventing the wheel" force by incompatible licenses and give you an idea,
- how to access the stlink device. That doesn't mean you should be a free-loader
- and not contribute your improvements to this code.
-
- Author: Martin Capitanio <m@capitanio.org>
- The stlink related constants kindly provided by Oliver Spencer (OpenOCD)
- for use in a GPL compatible license.
-
- Notes:
- gcc -O0 -g3 -Wall -c -std=gnu99 -o stlink-access-test.o stlink-access-test.c
- gcc  -o stlink-access-test stlink-access-test.o -lsgutils2
-
- Code format ~ TAB = 8, K&R, linux kernel source, golang oriented
- Tested compatibility: linux, gcc >= 4.3.3
-
- The communication is based on standard USB mass storage device
- BOT (Bulk Only Transfer)
- - Endpoint 1: BULK_IN, 64 bytes max
- - Endpoint 2: BULK_OUT, 64 bytes max
-
- All CBW transfers are ordered with the LSB (byte 0) first (little endian).
- Any command must be answered before sending the next command.
- Each USB transfer must complete in less than 1s.
-
- SB Device Class Definition for Mass Storage Devices:
- www.usb.org/developers/devclass_docs/usbmassbulk_10.pdf
-
- dt            - Data Transfer (IN/OUT)
- CBW           - Command Block Wrapper
- CSW           - Command Status Wrapper
- RFU           - Reserved for Future Use
- scsi_pt       - SCSI pass-through
- sg            - SCSI generic
-
- * usb-storage.quirks
- http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob_plain;f=Documentation/kernel-parameters.txt
- Each entry has the form VID:PID:Flags where VID and PID are Vendor and Product
- ID values (4-digit hex numbers) and Flags is a set of characters, each corresponding
- to a common usb-storage quirk flag as follows:
-
- a = SANE_SENSE (collect more than 18 bytes of sense data);
- b = BAD_SENSE (don't collect more than 18 bytes of sense data);
- c = FIX_CAPACITY (decrease the reported device capacity by one sector);
- h = CAPACITY_HEURISTICS (decrease the reported device capacity by one sector if the number is odd);
- i = IGNORE_DEVICE (don't bind to this device);
- l = NOT_LOCKABLE (don't try to lock and unlock ejectable media);
- m = MAX_SECTORS_64 (don't transfer more than 64 sectors = 32 KB at a time);
- o = CAPACITY_OK (accept the capacity reported by the device);
- r = IGNORE_RESIDUE (the device reports bogus residue values);
- s = SINGLE_LUN (the device has only one Logical Unit);
- w = NO_WP_DETECT (don't test whether the medium is write-protected).
-
- Example: quirks=0419:aaf5:rl,0421:0433:rc
- http://permalink.gmane.org/gmane.linux.usb.general/35053
-
- modprobe -r usb-storage && modprobe usb-storage quirks=483:3744:l
-
- Equivalently, you can add a line saying
-
- options usb-storage quirks=483:3744:l
-
- to your /etc/modprobe.conf or /etc/modprobe.d/local.conf (or add the "quirks=..."
- part to an existing options line for usb-storage).
- */
-
-#define __USE_GNU
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-
-// sgutils2 (apt-get install libsgutils2-dev)
-#include <scsi/sg_lib.h>
-#include <scsi/sg_pt.h>
-
-// device access
-#define RDWR           0
-#define RO             1
-#define SG_TIMEOUT_SEC 1 // actually 1 is about 2 sec
-// Each CDB can be a total of 6, 10, 12, or 16 bytes, later version
-// of the SCSI standard also allow for variable-length CDBs (min. CDB is 6).
-// the stlink needs max. 10 bytes.
-#define CDB_6          6
-#define CDB_10         10
-#define CDB_12         12
-#define CDB_16         16
-
-#define CDB_SL         10
-
-// Query data flow direction.
-#define Q_DATA_OUT     0
-#define Q_DATA_IN      1
-
-// The SCSI Request Sense command is used to obtain sense data
-// (error information) from a target device.
-// http://en.wikipedia.org/wiki/SCSI_Request_Sense_Command
-#define SENSE_BUF_LEN          32
-
-// Max data transfer size.
-// 6kB = max mem32_read block, 8kB sram
-//#define Q_BUF_LEN    96
-#define Q_BUF_LEN      1024 * 100
-
-// st-link vendor cmd's
-#define USB_ST_VID                     0x0483
-#define USB_STLINK_PID                 0x3744
-
-// STLINK_DEBUG_RESETSYS, etc:
-#define STLINK_OK                      0x80
-#define STLINK_FALSE                   0x81
-#define STLINK_CORE_RUNNINIG           0x80
-#define STLINK_CORE_HALTED             0x81
-#define STLINK_CORE_STAT_UNKNOWN       -1
-
-#define STLINK_GET_VERSION             0xf1
-#define STLINK_GET_CURRENT_MODE        0xf5
-
-#define STLINK_DEBUG_COMMAND           0xF2
-#define STLINK_DFU_COMMAND             0xF3
-#define STLINK_DFU_EXIT                0x07
-
-// STLINK_GET_CURRENT_MODE
-#define STLINK_DEV_DFU_MODE            0x00
-#define STLINK_DEV_MASS_MODE           0x01
-#define STLINK_DEV_DEBUG_MODE          0x02
-#define STLINK_DEV_UNKNOWN_MODE        -1
-
-// jtag mode cmds
-#define STLINK_DEBUG_ENTER             0x20
-#define STLINK_DEBUG_EXIT              0x21
-#define STLINK_DEBUG_READCOREID        0x22
-#define STLINK_DEBUG_GETSTATUS         0x01
-#define STLINK_DEBUG_FORCEDEBUG        0x02
-#define STLINK_DEBUG_RESETSYS          0x03
-#define STLINK_DEBUG_READALLREGS       0x04
-#define STLINK_DEBUG_READREG           0x05
-#define STLINK_DEBUG_WRITEREG          0x06
-#define STLINK_DEBUG_READMEM_32BIT     0x07
-#define STLINK_DEBUG_WRITEMEM_32BIT    0x08
-#define STLINK_DEBUG_RUNCORE           0x09
-#define STLINK_DEBUG_STEPCORE          0x0a
-#define STLINK_DEBUG_SETFP             0x0b
-#define STLINK_DEBUG_WRITEMEM_8BIT     0x0d
-#define STLINK_DEBUG_CLEARFP           0x0e
-#define STLINK_DEBUG_WRITEDEBUGREG     0x0f
-#define STLINK_DEBUG_ENTER_SWD         0xa3
-#define STLINK_DEBUG_ENTER_JTAG        0x00
-
-typedef struct {
-       uint32_t r[16];
-       uint32_t xpsr;
-       uint32_t main_sp;
-       uint32_t process_sp;
-       uint32_t rw;
-       uint32_t rw2;
-} reg;
-
-typedef uint32_t stm32_addr_t;
-
-struct stlink {
-       int sg_fd;
-       int do_scsi_pt_err;
-       // sg layer verboseness: 0 for no debug info, 10 for lots
-       int verbose;
-
-       unsigned char cdb_cmd_blk[CDB_SL];
-
-       // Data transferred from or to device
-       unsigned char q_buf[Q_BUF_LEN];
-       int q_len;
-       int q_data_dir; // Q_DATA_IN, Q_DATA_OUT
-       // the start of the query data in the device memory space
-       uint32_t q_addr;
-
-       // Sense (error information) data
-       unsigned char sense_buf[SENSE_BUF_LEN];
-
-       uint32_t st_vid;
-       uint32_t stlink_pid;
-       uint32_t stlink_v;
-       uint32_t jtag_v;
-       uint32_t swim_v;
-       uint32_t core_id;
-
-       reg reg;
-       int core_stat;
-
-       /* medium density stm32 flash settings */
-#define STM32_FLASH_BASE 0x08000000
-#define STM32_FLASH_SIZE (128 * 1024)
-#define STM32_FLASH_PGSZ 1024
-       stm32_addr_t flash_base;
-       size_t flash_size;
-       size_t flash_pgsz;
-
-       /* in flash system memory */
-#define STM32_SYSTEM_BASE 0x1ffff000
-#define STM32_SYSTEM_SIZE (2 * 1024)
-       stm32_addr_t sys_base;
-       size_t sys_size;
-
-       /* sram settings */
-#define STM32_SRAM_BASE 0x20000000
-#define STM32_SRAM_SIZE (8 * 1024)
-       stm32_addr_t sram_base;
-       size_t sram_size;
-};
-
-static void D(struct stlink *sl, char *txt) {
-       if (sl->verbose > 1)
-               fputs(txt, stderr);
-}
-
-// Suspends execution of the calling process for
-// (at least) ms milliseconds.
-static void delay(int ms) {
-       fprintf(stderr, "*** wait %d ms\n", ms);
-       usleep(1000 * ms);
-}
-
-// Endianness
-// http://www.ibm.com/developerworks/aix/library/au-endianc/index.html
-// const int i = 1;
-// #define is_bigendian() ( (*(char*)&i) == 0 )
-static inline unsigned int is_bigendian(void) {
-       static volatile const unsigned int i = 1;
-       return *(volatile const char*) &i == 0;
-}
-
-static void write_uint32(unsigned char* buf, uint32_t ui) {
-       if (!is_bigendian()) { // le -> le (don't swap)
-               buf[0] = ((unsigned char*) &ui)[0];
-               buf[1] = ((unsigned char*) &ui)[1];
-               buf[2] = ((unsigned char*) &ui)[2];
-               buf[3] = ((unsigned char*) &ui)[3];
-       } else {
-               buf[0] = ((unsigned char*) &ui)[3];
-               buf[1] = ((unsigned char*) &ui)[2];
-               buf[2] = ((unsigned char*) &ui)[1];
-               buf[3] = ((unsigned char*) &ui)[0];
-       }
-}
-
-static void write_uint16(unsigned char* buf, uint16_t ui) {
-       if (!is_bigendian()) { // le -> le (don't swap)
-               buf[0] = ((unsigned char*) &ui)[0];
-               buf[1] = ((unsigned char*) &ui)[1];
-       } else {
-               buf[0] = ((unsigned char*) &ui)[1];
-               buf[1] = ((unsigned char*) &ui)[0];
-       }
-}
-
-static uint32_t read_uint32(const unsigned char *c, const int pt) {
-       uint32_t ui;
-       char *p = (char *) &ui;
-
-       if (!is_bigendian()) { // le -> le (don't swap)
-               p[0] = c[pt];
-               p[1] = c[pt + 1];
-               p[2] = c[pt + 2];
-               p[3] = c[pt + 3];
-       } else {
-               p[0] = c[pt + 3];
-               p[1] = c[pt + 2];
-               p[2] = c[pt + 1];
-               p[3] = c[pt];
-       }
-       return ui;
-}
-
-static void clear_cdb(struct stlink *sl) {
-       for (int i = 0; i < sizeof(sl->cdb_cmd_blk); i++)
-               sl->cdb_cmd_blk[i] = 0;
-       // set default
-       sl->cdb_cmd_blk[0] = STLINK_DEBUG_COMMAND;
-       sl->q_data_dir = Q_DATA_IN;
-}
-
-// E.g. make the valgrind happy.
-static void clear_buf(struct stlink *sl) {
-       fprintf(stderr, "*** clear_buf ***\n");
-       for (int i = 0; i < sizeof(sl->q_buf); i++)
-               sl->q_buf[i] = 0;
-
-}
-
-static struct stlink* stlink_open(const char *dev_name, const int verbose) {
-       fprintf(stderr, "\n*** stlink_open [%s] ***\n", dev_name);
-       int sg_fd = scsi_pt_open_device(dev_name, RDWR, verbose);
-       if (sg_fd < 0) {
-               fprintf(stderr, "error opening device: %s: %s\n", dev_name,
-                       safe_strerror(-sg_fd));
-               return NULL;
-       }
-
-       struct stlink *sl = malloc(sizeof(struct stlink));
-       if (sl == NULL) {
-               fprintf(stderr, "struct stlink: out of memory\n");
-               return NULL;
-       }
-
-       sl->sg_fd = sg_fd;
-       sl->verbose = verbose;
-       sl->core_stat = STLINK_CORE_STAT_UNKNOWN;
-       sl->core_id = 0;
-       sl->q_addr = 0;
-       clear_buf(sl);
-
-       /* flash memory settings */
-       sl->flash_base = STM32_FLASH_BASE;
-       sl->flash_size = STM32_FLASH_SIZE;
-       sl->flash_pgsz = STM32_FLASH_PGSZ;
-
-       /* system memory */
-       sl->sys_base = STM32_SYSTEM_BASE;
-       sl->sys_size = STM32_SYSTEM_SIZE;
-
-       /* sram memory settings */
-       sl->sram_base = STM32_SRAM_BASE;
-       sl->sram_size = STM32_SRAM_SIZE;
-
-       return sl;
-}
-
-// close the device, free the allocated memory
-void stlink_close(struct stlink *sl) {
-       D(sl, "\n*** stlink_close ***\n");
-       if (sl) {
-               scsi_pt_close_device(sl->sg_fd);
-               free(sl);
-       }
-}
-
-//TODO rewrite/cleanup, save the error in sl
-static void stlink_confirm_inq(struct stlink *sl, struct sg_pt_base *ptvp) {
-       const int e = sl->do_scsi_pt_err;
-       if (e < 0) {
-               fprintf(stderr, "scsi_pt error: pass through os error: %s\n",
-                       safe_strerror(-e));
-               return;
-       } else if (e == SCSI_PT_DO_BAD_PARAMS) {
-               fprintf(stderr, "scsi_pt error: bad pass through setup\n");
-               return;
-       } else if (e == SCSI_PT_DO_TIMEOUT) {
-               fprintf(stderr, "  pass through timeout\n");
-               return;
-       }
-       const int duration = get_scsi_pt_duration_ms(ptvp);
-       if ((sl->verbose > 1) && (duration >= 0))
-               fprintf(stderr, "      duration=%d ms\n", duration);
-
-       // XXX stlink fw sends broken residue, so ignore it and use the known q_len
-       // "usb-storage quirks=483:3744:r"
-       // forces residue to be ignored and calculated, but this causes aboard if
-       // data_len = 0 and by some other data_len values.
-
-       const int resid = get_scsi_pt_resid(ptvp);
-       const int dsize = sl->q_len - resid;
-
-       const int cat = get_scsi_pt_result_category(ptvp);
-       char buf[512];
-       unsigned int slen;
-
-       switch (cat) {
-       case SCSI_PT_RESULT_GOOD:
-               if (sl->verbose && (resid > 0))
-                       fprintf(stderr, "      notice: requested %d bytes but "
-                               "got %d bytes, ignore [broken] residue = %d\n",
-                               sl->q_len, dsize, resid);
-               break;
-       case SCSI_PT_RESULT_STATUS:
-               if (sl->verbose) {
-                       sg_get_scsi_status_str(
-                               get_scsi_pt_status_response(ptvp), sizeof(buf),
-                               buf);
-                       fprintf(stderr, "  scsi status: %s\n", buf);
-               }
-               return;
-       case SCSI_PT_RESULT_SENSE:
-               slen = get_scsi_pt_sense_len(ptvp);
-               if (sl->verbose) {
-                       sg_get_sense_str("", sl->sense_buf, slen, (sl->verbose
-                               > 1), sizeof(buf), buf);
-                       fprintf(stderr, "%s", buf);
-               }
-               if (sl->verbose && (resid > 0)) {
-                       if ((sl->verbose) || (sl->q_len > 0))
-                               fprintf(stderr, "    requested %d bytes but "
-                                       "got %d bytes\n", sl->q_len, dsize);
-               }
-               return;
-       case SCSI_PT_RESULT_TRANSPORT_ERR:
-               if (sl->verbose) {
-                       get_scsi_pt_transport_err_str(ptvp, sizeof(buf), buf);
-                       // http://tldp.org/HOWTO/SCSI-Generic-HOWTO/x291.html
-                       // These codes potentially come from the firmware on a host adapter
-                       // or from one of several hosts that an adapter driver controls.
-                       // The 'host_status' field has the following values:
-                       //      [0x07] Internal error detected in the host adapter.
-                       // This may not be fatal (and the command may have succeeded).
-                       fprintf(stderr, "  transport: %s", buf);
-               }
-               return;
-       case SCSI_PT_RESULT_OS_ERR:
-               if (sl->verbose) {
-                       get_scsi_pt_os_err_str(ptvp, sizeof(buf), buf);
-                       fprintf(stderr, "  os: %s", buf);
-               }
-               return;
-       default:
-               fprintf(stderr, "  unknown pass through result "
-                       "category (%d)\n", cat);
-       }
-}
-
-static void stlink_q(struct stlink* sl) {
-       fputs("CDB[", stderr);
-       for (int i = 0; i < CDB_SL; i++)
-               fprintf(stderr, " 0x%02x", (unsigned int) sl->cdb_cmd_blk[i]);
-       fputs("]\n", stderr);
-
-       // Get control command descriptor of scsi structure,
-       // (one object per command!!)
-       struct sg_pt_base *ptvp = construct_scsi_pt_obj();
-       if (NULL == ptvp) {
-               fprintf(stderr, "construct_scsi_pt_obj: out of memory\n");
-               return;
-       }
-
-       set_scsi_pt_cdb(ptvp, sl->cdb_cmd_blk, sizeof(sl->cdb_cmd_blk));
-
-       // set buffer for sense (error information) data
-       set_scsi_pt_sense(ptvp, sl->sense_buf, sizeof(sl->sense_buf));
-
-       // Set a buffer to be used for data transferred from device
-       if (sl->q_data_dir == Q_DATA_IN) {
-               //clear_buf(sl);
-               set_scsi_pt_data_in(ptvp, sl->q_buf, sl->q_len);
-       } else {
-               set_scsi_pt_data_out(ptvp, sl->q_buf, sl->q_len);
-       }
-       // Executes SCSI command (or at least forwards it to lower layers).
-       sl->do_scsi_pt_err = do_scsi_pt(ptvp, sl->sg_fd, SG_TIMEOUT_SEC,
-               sl->verbose);
-
-       // check for scsi errors
-       stlink_confirm_inq(sl, ptvp);
-       // TODO recycle: clear_scsi_pt_obj(struct sg_pt_base * objp);
-       destruct_scsi_pt_obj(ptvp);
-}
-
-static void stlink_print_data(struct stlink *sl) {
-       if (sl->q_len <= 0 || sl->verbose < 2)
-               return;
-       if (sl->verbose > 2)
-               fprintf(stdout, "data_len = %d 0x%x\n", sl->q_len, sl->q_len);
-
-       for (uint32_t i = 0; i < sl->q_len; i++) {
-               if (i % 16 == 0) {
-                       if (sl->q_data_dir == Q_DATA_OUT)
-                               fprintf(stdout, "\n<- 0x%08x ", sl->q_addr + i);
-                       else
-                               fprintf(stdout, "\n-> 0x%08x ", sl->q_addr + i);
-               }
-               fprintf(stdout, " %02x", (unsigned int) sl->q_buf[i]);
-       }
-       fputs("\n\n", stdout);
-}
-
-// TODO thinking, cleanup
-static void stlink_parse_version(struct stlink *sl) {
-       sl->st_vid = 0;
-       sl->stlink_pid = 0;
-       if (sl->q_len <= 0) {
-               fprintf(stderr, "Error: could not parse the stlink version");
-               return;
-       }
-       stlink_print_data(sl);
-       uint32_t b0 = sl->q_buf[0]; //lsb
-       uint32_t b1 = sl->q_buf[1];
-       uint32_t b2 = sl->q_buf[2];
-       uint32_t b3 = sl->q_buf[3];
-       uint32_t b4 = sl->q_buf[4];
-       uint32_t b5 = sl->q_buf[5]; //msb
-
-       // b0 b1                       || b2 b3  | b4 b5
-       // 4b        | 6b     | 6b     || 2B     | 2B
-       // stlink_v  | jtag_v | swim_v || st_vid | stlink_pid
-
-       sl->stlink_v = (b0 & 0xf0) >> 4;
-       sl->jtag_v = ((b0 & 0x0f) << 2) | ((b1 & 0xc0) >> 6);
-       sl->swim_v = b1 & 0x3f;
-       sl->st_vid = (b3 << 8) | b2;
-       sl->stlink_pid = (b5 << 8) | b4;
-
-       if (sl->verbose < 2)
-               return;
-
-       fprintf(stderr, "st vid         = 0x%04x (expect 0x%04x)\n",
-               sl->st_vid, USB_ST_VID);
-       fprintf(stderr, "stlink pid     = 0x%04x (expect 0x%04x)\n",
-               sl->stlink_pid, USB_STLINK_PID);
-       fprintf(stderr, "stlink version = 0x%x\n", sl->stlink_v);
-       fprintf(stderr, "jtag version   = 0x%x\n", sl->jtag_v);
-       fprintf(stderr, "swim version   = 0x%x\n", sl->swim_v);
-       if (sl->jtag_v == 0)
-               fprintf(stderr,
-                       "    notice: the firmware doesn't support a jtag/swd interface\n");
-       if (sl->swim_v == 0)
-               fprintf(stderr,
-                       "    notice: the firmware doesn't support a swim interface\n");
-
-}
-
-static int stlink_mode(struct stlink *sl) {
-       if (sl->q_len <= 0)
-               return STLINK_DEV_UNKNOWN_MODE;
-
-       stlink_print_data(sl);
-
-       switch (sl->q_buf[0]) {
-       case STLINK_DEV_DFU_MODE:
-               fprintf(stderr, "stlink mode: dfu\n");
-               return STLINK_DEV_DFU_MODE;
-       case STLINK_DEV_DEBUG_MODE:
-               fprintf(stderr, "stlink mode: debug (jtag or swd)\n");
-               return STLINK_DEV_DEBUG_MODE;
-       case STLINK_DEV_MASS_MODE:
-               fprintf(stderr, "stlink mode: mass\n");
-               return STLINK_DEV_MASS_MODE;
-       }
-       return STLINK_DEV_UNKNOWN_MODE;
-}
-
-static void stlink_stat(struct stlink *sl, char *txt) {
-       if (sl->q_len <= 0)
-               return;
-
-       stlink_print_data(sl);
-
-       switch (sl->q_buf[0]) {
-       case STLINK_OK:
-               fprintf(stderr, "  %s: ok\n", txt);
-               return;
-       case STLINK_FALSE:
-               fprintf(stderr, "  %s: false\n", txt);
-               return;
-       default:
-               fprintf(stderr, "  %s: unknown\n", txt);
-       }
-}
-
-static void stlink_core_stat(struct stlink *sl) {
-       if (sl->q_len <= 0)
-               return;
-
-       stlink_print_data(sl);
-
-       switch (sl->q_buf[0]) {
-       case STLINK_CORE_RUNNINIG:
-               sl->core_stat = STLINK_CORE_RUNNINIG;
-               fprintf(stderr, "  core status: running\n");
-               return;
-       case STLINK_CORE_HALTED:
-               sl->core_stat = STLINK_CORE_HALTED;
-               fprintf(stderr, "  core status: halted\n");
-               return;
-       default:
-               sl->core_stat = STLINK_CORE_STAT_UNKNOWN;
-               fprintf(stderr, "  core status: unknown\n");
-       }
-}
-
-void stlink_version(struct stlink *sl) {
-       D(sl, "\n*** stlink_version ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[0] = STLINK_GET_VERSION;
-       sl->q_len = 6;
-       sl->q_addr = 0;
-       stlink_q(sl);
-       stlink_parse_version(sl);
-}
-
-// Get stlink mode:
-// STLINK_DEV_DFU_MODE || STLINK_DEV_MASS_MODE || STLINK_DEV_DEBUG_MODE
-// usb dfu             || usb mass             || jtag or swd
-int stlink_current_mode(struct stlink *sl) {
-       D(sl, "\n*** stlink_current_mode ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[0] = STLINK_GET_CURRENT_MODE;
-       sl->q_len = 2;
-       sl->q_addr = 0;
-       stlink_q(sl);
-       return stlink_mode(sl);
-}
-
-// Exit the mass mode and enter the swd debug mode.
-void stlink_enter_swd_mode(struct stlink *sl) {
-       D(sl, "\n*** stlink_enter_swd_mode ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_ENTER;
-       sl->cdb_cmd_blk[2] = STLINK_DEBUG_ENTER_SWD;
-       sl->q_len = 0; // >0 -> aboard
-       stlink_q(sl);
-}
-
-// Exit the mass mode and enter the jtag debug mode.
-// (jtag is disabled in the discovery's stlink firmware)
-void stlink_enter_jtag_mode(struct stlink *sl) {
-       D(sl, "\n*** stlink_enter_jtag_mode ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_ENTER;
-       sl->cdb_cmd_blk[2] = STLINK_DEBUG_ENTER_JTAG;
-       sl->q_len = 0;
-       stlink_q(sl);
-}
-
-// Exit the jtag or swd mode and enter the mass mode.
-void stlink_exit_debug_mode(struct stlink *sl) {
-       D(sl, "\n*** stlink_exit_debug_mode ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_EXIT;
-       sl->q_len = 0; // >0 -> aboard
-       stlink_q(sl);
-}
-
-// XXX kernel driver performs reset, the device temporally disappears
-static void stlink_exit_dfu_mode(struct stlink *sl) {
-       D(sl, "\n*** stlink_exit_dfu_mode ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[0] = STLINK_DFU_COMMAND;
-       sl->cdb_cmd_blk[1] = STLINK_DFU_EXIT;
-       sl->q_len = 0; // ??
-       stlink_q(sl);
-       /*
-        [135121.844564] sd 19:0:0:0: [sdb] Unhandled error code
-        [135121.844569] sd 19:0:0:0: [sdb] Result: hostbyte=DID_ERROR driverbyte=DRIVER_OK
-        [135121.844574] sd 19:0:0:0: [sdb] CDB: Read(10): 28 00 00 00 10 00 00 00 08 00
-        [135121.844584] end_request: I/O error, dev sdb, sector 4096
-        [135121.844590] Buffer I/O error on device sdb, logical block 512
-        [135130.122567] usb 6-1: reset full speed USB device using uhci_hcd and address 7
-        [135130.274551] usb 6-1: device firmware changed
-        [135130.274618] usb 6-1: USB disconnect, address 7
-        [135130.275186] VFS: busy inodes on changed media or resized disk sdb
-        [135130.275424] VFS: busy inodes on changed media or resized disk sdb
-        [135130.286758] VFS: busy inodes on changed media or resized disk sdb
-        [135130.292796] VFS: busy inodes on changed media or resized disk sdb
-        [135130.301481] VFS: busy inodes on changed media or resized disk sdb
-        [135130.304316] VFS: busy inodes on changed media or resized disk sdb
-        [135130.431113] usb 6-1: new full speed USB device using uhci_hcd and address 8
-        [135130.629444] usb-storage 6-1:1.0: Quirks match for vid 0483 pid 3744: 102a1
-        [135130.629492] scsi20 : usb-storage 6-1:1.0
-        [135131.625600] scsi 20:0:0:0: Direct-Access     STM32                          PQ: 0 ANSI: 0
-        [135131.627010] sd 20:0:0:0: Attached scsi generic sg2 type 0
-        [135131.633603] sd 20:0:0:0: [sdb] 64000 512-byte logical blocks: (32.7 MB/31.2 MiB)
-        [135131.633613] sd 20:0:0:0: [sdb] Assuming Write Enabled
-        [135131.633620] sd 20:0:0:0: [sdb] Assuming drive cache: write through
-        [135131.640584] sd 20:0:0:0: [sdb] Assuming Write Enabled
-        [135131.640592] sd 20:0:0:0: [sdb] Assuming drive cache: write through
-        [135131.640609]  sdb:
-        [135131.652634] sd 20:0:0:0: [sdb] Assuming Write Enabled
-        [135131.652639] sd 20:0:0:0: [sdb] Assuming drive cache: write through
-        [135131.652645] sd 20:0:0:0: [sdb] Attached SCSI removable disk
-        [135131.671536] sd 20:0:0:0: [sdb] Result: hostbyte=DID_OK driverbyte=DRIVER_SENSE
-        [135131.671548] sd 20:0:0:0: [sdb] Sense Key : Illegal Request [current]
-        [135131.671553] sd 20:0:0:0: [sdb] Add. Sense: Logical block address out of range
-        [135131.671560] sd 20:0:0:0: [sdb] CDB: Read(10): 28 00 00 00 f9 80 00 00 08 00
-        [135131.671570] end_request: I/O error, dev sdb, sector 63872
-        [135131.671575] Buffer I/O error on device sdb, logical block 7984
-        [135131.678527] sd 20:0:0:0: [sdb] Result: hostbyte=DID_OK driverbyte=DRIVER_SENSE
-        [135131.678532] sd 20:0:0:0: [sdb] Sense Key : Illegal Request [current]
-        [135131.678537] sd 20:0:0:0: [sdb] Add. Sense: Logical block address out of range
-        [135131.678542] sd 20:0:0:0: [sdb] CDB: Read(10): 28 00 00 00 f9 80 00 00 08 00
-        [135131.678551] end_request: I/O error, dev sdb, sector 63872
-        ...
-        [135131.853565] end_request: I/O error, dev sdb, sector 4096
-        */
-}
-
-static void stlink_core_id(struct stlink *sl) {
-       D(sl, "\n*** stlink_core_id ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_READCOREID;
-       sl->q_len = 4;
-       sl->q_addr = 0;
-       stlink_q(sl);
-       sl->core_id = read_uint32(sl->q_buf, 0);
-       if (sl->verbose < 2)
-               return;
-       stlink_print_data(sl);
-       fprintf(stderr, "core_id = 0x%08x\n", sl->core_id);
-}
-
-// Arm-core reset -> halted state.
-void stlink_reset(struct stlink *sl) {
-       D(sl, "\n*** stlink_reset ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_RESETSYS;
-       sl->q_len = 2;
-       sl->q_addr = 0;
-       stlink_q(sl);
-       stlink_stat(sl, "core reset");
-}
-
-// Arm-core status: halted or running.
-void stlink_status(struct stlink *sl) {
-       D(sl, "\n*** stlink_status ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_GETSTATUS;
-       sl->q_len = 2;
-       sl->q_addr = 0;
-       stlink_q(sl);
-       stlink_core_stat(sl);
-}
-
-// Force the core into the debug mode -> halted state.
-void stlink_force_debug(struct stlink *sl) {
-       D(sl, "\n*** stlink_force_debug ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_FORCEDEBUG;
-       sl->q_len = 2;
-       sl->q_addr = 0;
-       stlink_q(sl);
-       stlink_stat(sl, "force debug");
-}
-
-// Read all arm-core registers.
-void stlink_read_all_regs(struct stlink *sl) {
-       D(sl, "\n*** stlink_read_all_regs ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_READALLREGS;
-       sl->q_len = 84;
-       sl->q_addr = 0;
-       stlink_q(sl);
-       stlink_print_data(sl);
-
-       // 0-3 | 4-7 | ... | 60-63 | 64-67 | 68-71   | 72-75      | 76-79 | 80-83
-       // r0  | r1  | ... | r15   | xpsr  | main_sp | process_sp | rw    | rw2
-       for (int i = 0; i < 16; i++) {
-               sl->reg.r[i] = read_uint32(sl->q_buf, 4 * i);
-               if (sl->verbose > 1)
-                       fprintf(stderr, "r%2d = 0x%08x\n", i, sl->reg.r[i]);
-       }
-       sl->reg.xpsr = read_uint32(sl->q_buf, 64);
-       sl->reg.main_sp = read_uint32(sl->q_buf, 68);
-       sl->reg.process_sp = read_uint32(sl->q_buf, 72);
-       sl->reg.rw = read_uint32(sl->q_buf, 76);
-       sl->reg.rw2 = read_uint32(sl->q_buf, 80);
-       if (sl->verbose < 2)
-               return;
-
-       fprintf(stderr, "xpsr       = 0x%08x\n", sl->reg.xpsr);
-       fprintf(stderr, "main_sp    = 0x%08x\n", sl->reg.main_sp);
-       fprintf(stderr, "process_sp = 0x%08x\n", sl->reg.process_sp);
-       fprintf(stderr, "rw         = 0x%08x\n", sl->reg.rw);
-       fprintf(stderr, "rw2        = 0x%08x\n", sl->reg.rw2);
-}
-
-// Read an arm-core register, the index must be in the range 0..20.
-//  0  |  1  | ... |  15   |  16   |   17    |   18       |  19   |  20
-// r0  | r1  | ... | r15   | xpsr  | main_sp | process_sp | rw    | rw2
-void stlink_read_reg(struct stlink *sl, int r_idx) {
-       D(sl, "\n*** stlink_read_reg");
-       fprintf(stderr, " (%d) ***\n", r_idx);
-
-       if (r_idx > 20 || r_idx < 0) {
-               fprintf(stderr, "Error: register index must be in [0..20]\n");
-               return;
-       }
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_READREG;
-       sl->cdb_cmd_blk[2] = r_idx;
-       sl->q_len = 4;
-       sl->q_addr = 0;
-       stlink_q(sl);
-       //  0  |  1  | ... |  15   |  16   |   17    |   18       |  19   |  20
-       // 0-3 | 4-7 | ... | 60-63 | 64-67 | 68-71   | 72-75      | 76-79 | 80-83
-       // r0  | r1  | ... | r15   | xpsr  | main_sp | process_sp | rw    | rw2
-       stlink_print_data(sl);
-
-       uint32_t r = read_uint32(sl->q_buf, 0);
-       fprintf(stderr, "r_idx (%2d) = 0x%08x\n", r_idx, r);
-
-       switch (r_idx) {
-       case 16:
-               sl->reg.xpsr = r;
-               break;
-       case 17:
-               sl->reg.main_sp = r;
-               break;
-       case 18:
-               sl->reg.process_sp = r;
-               break;
-       case 19:
-               sl->reg.rw = r; //XXX ?(primask, basemask etc.)
-               break;
-       case 20:
-               sl->reg.rw2 = r; //XXX ?(primask, basemask etc.)
-               break;
-       default:
-               sl->reg.r[r_idx] = r;
-       }
-}
-
-// Write an arm-core register. Index:
-//  0  |  1  | ... |  15   |  16   |   17    |   18       |  19   |  20
-// r0  | r1  | ... | r15   | xpsr  | main_sp | process_sp | rw    | rw2
-void stlink_write_reg(struct stlink *sl, uint32_t reg, int idx) {
-       D(sl, "\n*** stlink_write_reg ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_WRITEREG;
-       //   2: reg index
-       // 3-6: reg content
-       sl->cdb_cmd_blk[2] = idx;
-       write_uint32(sl->cdb_cmd_blk + 3, reg);
-       sl->q_len = 2;
-       sl->q_addr = 0;
-       stlink_q(sl);
-       stlink_stat(sl, "write reg");
-}
-
-// Write a register of the debug module of the core.
-// XXX ?(atomic writes)
-// TODO test
-void stlink_write_dreg(struct stlink *sl, uint32_t reg, uint32_t addr) {
-       D(sl, "\n*** stlink_write_dreg ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_WRITEDEBUGREG;
-       // 2-5: address of reg of the debug module
-       // 6-9: reg content
-       write_uint32(sl->cdb_cmd_blk + 2, addr);
-       write_uint32(sl->cdb_cmd_blk + 6, reg);
-       sl->q_len = 2;
-       sl->q_addr = addr;
-       stlink_q(sl);
-       stlink_stat(sl, "write debug reg");
-}
-
-// Force the core exit the debug mode.
-void stlink_run(struct stlink *sl) {
-       D(sl, "\n*** stlink_run ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_RUNCORE;
-       sl->q_len = 2;
-       sl->q_addr = 0;
-       stlink_q(sl);
-       stlink_stat(sl, "run core");
-}
-
-// same as above with entrypoint.
-static unsigned int is_core_halted(struct stlink*);
-void stlink_run_at(struct stlink *sl, stm32_addr_t addr) {
-       stlink_write_reg(sl, addr, 15); /* pc register */
-
-       stlink_run(sl);
-
-       while (is_core_halted(sl) == 0)
-         usleep(3000000);
-}
-
-// Step the arm-core.
-void stlink_step(struct stlink *sl) {
-       D(sl, "\n*** stlink_step ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_STEPCORE;
-       sl->q_len = 2;
-       sl->q_addr = 0;
-       stlink_q(sl);
-       stlink_stat(sl, "step core");
-}
-
-// TODO test
-// see Cortex-M3 Technical Reference Manual
-void stlink_set_hw_bp(struct stlink *sl, int fp_nr, uint32_t addr, int fp) {
-       D(sl, "\n*** stlink_set_hw_bp ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_SETFP;
-       // 2:The number of the flash patch used to set the breakpoint
-       // 3-6: Address of the breakpoint (LSB)
-       // 7: FP_ALL (0x02) / FP_UPPER (0x01) / FP_LOWER (0x00)
-       sl->q_buf[2] = fp_nr;
-       write_uint32(sl->q_buf, addr);
-       sl->q_buf[7] = fp;
-
-       sl->q_len = 2;
-       stlink_q(sl);
-       stlink_stat(sl, "set flash breakpoint");
-}
-
-// TODO test
-void stlink_clr_hw_bp(struct stlink *sl, int fp_nr) {
-       D(sl, "\n*** stlink_clr_hw_bp ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_CLEARFP;
-       sl->cdb_cmd_blk[2] = fp_nr;
-
-       sl->q_len = 2;
-       stlink_q(sl);
-       stlink_stat(sl, "clear flash breakpoint");
-}
-
-// Read a "len" bytes to the sl->q_buf from the memory, max 6kB (6144 bytes)
-void stlink_read_mem32(struct stlink *sl, uint32_t addr, uint16_t len) {
-       D(sl, "\n*** stlink_read_mem32 ***\n");
-       if (len % 4 != 0) { // !!! never ever: fw gives just wrong values
-               fprintf(
-                       stderr,
-                       "Error: Data length doesn't have a 32 bit alignment: +%d byte.\n",
-                       len % 4);
-               return;
-       }
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_READMEM_32BIT;
-       // 2-5: addr
-       // 6-7: len
-       write_uint32(sl->cdb_cmd_blk + 2, addr);
-       write_uint16(sl->cdb_cmd_blk + 6, len);
-
-       // data_in 0-0x40-len
-       // !!! len _and_ q_len must be max 6k,
-       //     i.e. >1024 * 6 = 6144 -> aboard)
-       // !!! if len < q_len: 64*k, 1024*n, n=1..5  -> aboard
-       //     (broken residue issue)
-       sl->q_len = len;
-       sl->q_addr = addr;
-       stlink_q(sl);
-       stlink_print_data(sl);
-}
-
-// Write a "len" bytes from the sl->q_buf to the memory, max 64 Bytes.
-void stlink_write_mem8(struct stlink *sl, uint32_t addr, uint16_t len) {
-       D(sl, "\n*** stlink_write_mem8 ***\n");
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_WRITEMEM_8BIT;
-       // 2-5: addr
-       // 6-7: len (>0x40 (64) -> aboard)
-       write_uint32(sl->cdb_cmd_blk + 2, addr);
-       write_uint16(sl->cdb_cmd_blk + 6, len);
-
-       // data_out 0-len
-       sl->q_len = len;
-       sl->q_addr = addr;
-       sl->q_data_dir = Q_DATA_OUT;
-       stlink_q(sl);
-       stlink_print_data(sl);
-}
-
-void stlink_write_mem32(struct stlink *sl, uint32_t addr, uint16_t len);
-void stlink_write_mem16(struct stlink *sl, uint32_t addr, uint16_t len) {
-  stlink_write_mem32(sl, addr, 4);
-}
-
-// Write a "len" bytes from the sl->q_buf to the memory, max Q_BUF_LEN bytes.
-void stlink_write_mem32(struct stlink *sl, uint32_t addr, uint16_t len) {
-       D(sl, "\n*** stlink_write_mem32 ***\n");
-       if (len % 4 != 0) {
-               fprintf(
-                       stderr,
-                       "Error: Data length doesn't have a 32 bit alignment: +%d byte.\n",
-                       len % 4);
-               return;
-       }
-       clear_cdb(sl);
-       sl->cdb_cmd_blk[1] = STLINK_DEBUG_WRITEMEM_32BIT;
-       // 2-5: addr
-       // 6-7: len "unlimited"
-       write_uint32(sl->cdb_cmd_blk + 2, addr);
-       write_uint16(sl->cdb_cmd_blk + 6, len);
-
-       // data_out 0-0x40-...-len
-       sl->q_len = len;
-       sl->q_addr = addr;
-       sl->q_data_dir = Q_DATA_OUT;
-       stlink_q(sl);
-       stlink_print_data(sl);
-}
-
-/* FPEC flash controller interface, pm0063 manual
- */
-
-#define FLASH_REGS_ADDR 0x40022000
-#define FLASH_REGS_SIZE 0x28
-
-#define FLASH_ACR (FLASH_REGS_ADDR + 0x00)
-#define FLASH_KEYR (FLASH_REGS_ADDR + 0x04)
-#define FLASH_SR (FLASH_REGS_ADDR + 0x0c)
-#define FLASH_CR (FLASH_REGS_ADDR + 0x10)
-#define FLASH_AR (FLASH_REGS_ADDR + 0x14)
-#define FLASH_OBR (FLASH_REGS_ADDR + 0x1c)
-#define FLASH_WRPR (FLASH_REGS_ADDR + 0x20)
-
-#define FLASH_RDPTR_KEY 0x00a5
-#define FLASH_KEY1 0x45670123
-#define FLASH_KEY2 0xcdef89ab
-
-#define FLASH_SR_BSY 0
-#define FLASH_SR_EOP 5
-
-#define FLASH_CR_PG 0
-#define FLASH_CR_PER 1
-#define FLASH_CR_MER 2
-#define FLASH_CR_STRT 6
-#define FLASH_CR_LOCK 7
-
-static uint32_t __attribute__((unused)) read_flash_rdp(struct stlink* sl)
-{
-  stlink_read_mem32(sl, FLASH_WRPR, sizeof(uint32_t));
-  return (*(uint32_t*)sl->q_buf) & 0xff;
-}
-
-static inline uint32_t read_flash_wrpr(struct stlink* sl)
-{
-  stlink_read_mem32(sl, FLASH_WRPR, sizeof(uint32_t));
-  return *(uint32_t*)sl->q_buf;
-}
-
-static inline uint32_t read_flash_obr(struct stlink* sl)
-{
-  stlink_read_mem32(sl, FLASH_OBR, sizeof(uint32_t));
-  return *(uint32_t*)sl->q_buf;
-}
-
-static inline uint32_t read_flash_cr(struct stlink* sl)
-{
-  stlink_read_mem32(sl, FLASH_CR, sizeof(uint32_t));
-  return *(uint32_t*)sl->q_buf;
-}
-
-static inline unsigned int is_flash_locked(struct stlink* sl)
-{
-  /* return non zero for true */
-  return read_flash_cr(sl) & (1 << FLASH_CR_LOCK);
-}
-
-static void unlock_flash(struct stlink* sl)
-{
-  /* the unlock sequence consists of 2 write cycles where
-     2 key values are written to the FLASH_KEYR register.
-     an invalid sequence results in a definitive lock of
-     the FPEC block until next reset.
-  */
-
-  write_uint32(sl->q_buf, FLASH_KEY1);
-  stlink_write_mem32(sl, FLASH_KEYR, sizeof(uint32_t));
-
-  write_uint32(sl->q_buf, FLASH_KEY2);
-  stlink_write_mem32(sl, FLASH_KEYR, sizeof(uint32_t));
-}
-
-static int unlock_flash_if(struct stlink* sl)
-{
-  /* unlock flash if already locked */
-
-  if (is_flash_locked(sl))
-  {
-    unlock_flash(sl);
-    if (is_flash_locked(sl))
-      return -1;
-  }
-
-  return 0;
-}
-
-static void lock_flash(struct stlink* sl)
-{
-  /* write to 1 only. reset by hw at unlock sequence */
-
-  const uint32_t n = read_flash_cr(sl) | (1 << FLASH_CR_LOCK);
-
-  write_uint32(sl->q_buf, n);
-  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
-}
-
-static void set_flash_cr_pg(struct stlink* sl)
-{
-  const uint32_t n = 1 << FLASH_CR_PG;
-  write_uint32(sl->q_buf, n);
-  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
-}
-
-static void __attribute__((unused)) clear_flash_cr_pg(struct stlink* sl)
-{
-  const uint32_t n = read_flash_cr(sl) & ~(1 << FLASH_CR_PG);
-  write_uint32(sl->q_buf, n);
-  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
-}
-
-static void set_flash_cr_per(struct stlink* sl)
-{
-  const uint32_t n = 1 << FLASH_CR_PER;
-  write_uint32(sl->q_buf, n);
-  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
-}
-
-static void __attribute__((unused)) clear_flash_cr_per(struct stlink* sl)
-{
-  const uint32_t n = read_flash_cr(sl) & ~(1 << FLASH_CR_PER);
-  write_uint32(sl->q_buf, n);
-  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
-}
-
-static void set_flash_cr_mer(struct stlink* sl)
-{
-  const uint32_t n = 1 << FLASH_CR_MER;
-  write_uint32(sl->q_buf, n);
-  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
-}
-
-static void __attribute__((unused)) clear_flash_cr_mer(struct stlink* sl)
-{
-  const uint32_t n = read_flash_cr(sl) & ~(1 << FLASH_CR_MER);
-  write_uint32(sl->q_buf, n);
-  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
-}
-
-static void set_flash_cr_strt(struct stlink* sl)
-{
-  /* assume come on the flash_cr_per path */
-  const uint32_t n = (1 << FLASH_CR_PER) | (1 << FLASH_CR_STRT);
-  write_uint32(sl->q_buf, n);
-  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
-}
-
-static inline uint32_t read_flash_acr(struct stlink* sl)
-{
-  stlink_read_mem32(sl, FLASH_ACR, sizeof(uint32_t));
-  return *(uint32_t*)sl->q_buf;
-}
-
-static inline uint32_t read_flash_sr(struct stlink* sl)
-{
-  stlink_read_mem32(sl, FLASH_SR, sizeof(uint32_t));
-  return *(uint32_t*)sl->q_buf;
-}
-
-static inline unsigned int is_flash_busy(struct stlink* sl)
-{
-  return read_flash_sr(sl) & (1 << FLASH_SR_BSY);
-}
-
-static void wait_flash_busy(struct stlink* sl)
-{
-  /* todo: add some delays here */
-  while (is_flash_busy(sl))
-    ;
-}
-
-static inline unsigned int is_flash_eop(struct stlink* sl)
-{
-  return read_flash_sr(sl) & (1 << FLASH_SR_EOP);  
-}
-
-static void __attribute__((unused)) clear_flash_sr_eop(struct stlink* sl)
-{
-  const uint32_t n = read_flash_sr(sl) & ~(1 << FLASH_SR_EOP);
-  write_uint32(sl->q_buf, n);
-  stlink_write_mem32(sl, FLASH_SR, sizeof(uint32_t));
-}
-
-static void __attribute__((unused)) wait_flash_eop(struct stlink* sl)
-{
-  /* todo: add some delays here */
-  while (is_flash_eop(sl) == 0)
-    ;
-}
-
-static inline void write_flash_ar(struct stlink* sl, uint32_t n)
-{
-  write_uint32(sl->q_buf, n);
-  stlink_write_mem32(sl, FLASH_AR, sizeof(uint32_t));
-}
-
-#if 0 /* todo */
-static void disable_flash_read_protection(struct stlink* sl)
-{
-  /* erase the option byte area */
-  /* rdp = 0x00a5; */
-  /* reset */
-}
-#endif /* todo */
-
-#if 0 /* not working */
-static int write_flash_mem16
-(struct stlink* sl, uint32_t addr, uint16_t val)
-{
-  /* half word writes */
-  if (addr % 2) return -1;
-
-  /* unlock if locked */
-  unlock_flash_if(sl);
-
-  /* set flash programming chosen bit */
-  set_flash_cr_pg(sl);
-
-  write_uint16(sl->q_buf, val);
-  stlink_write_mem16(sl, addr, 2);
-
-  /* wait for non business */
-  wait_flash_busy(sl);
-
-  lock_flash(sl);
-
-  /* check the programmed value back */
-  stlink_read_mem16(sl, addr, 2);
-  if (*(const uint16_t*)sl->q_buf != val)
-  {
-    /* values differ at i * sizeof(uint16_t) */
-    return -1;
-  }
-
-  /* success */
-  return 0;
-}
-#endif /* not working */
-
-static int erase_flash_page(struct stlink* sl, stm32_addr_t page)
-{
-  /* page an addr in the page to erase */
-
-  /* wait for ongoing op to finish */
-  wait_flash_busy(sl);
-
-  /* unlock if locked */
-  unlock_flash_if(sl);
-
-  /* set the page erase bit */
-  set_flash_cr_per(sl);
-
-  /* select the page to erase */
-  write_flash_ar(sl, page);
-
-  /* start erase operation, reset by hw with bsy bit */
-  set_flash_cr_strt(sl);
-
-  /* wait for completion */
-  wait_flash_busy(sl);
-
-  /* relock the flash */
-  lock_flash(sl);
-
-  /* todo: verify the erased page */
-
-  return 0;
-}
-
-static int __attribute__((unused)) erase_flash_mass(struct stlink* sl)
-{
-  /* wait for ongoing op to finish */
-  wait_flash_busy(sl);
-
-  /* unlock if locked */
-  unlock_flash_if(sl);
-
-  /* set the mass erase bit */
-  set_flash_cr_mer(sl);
-
-  /* start erase operation, reset by hw with bsy bit */
-  set_flash_cr_strt(sl);
-
-  /* wait for completion */
-  wait_flash_busy(sl);
-
-  /* relock the flash */
-  lock_flash(sl);
-
-  /* todo: verify the erased memory */
-
-  return 0;
-}
-
-static unsigned int is_core_halted(struct stlink* sl)
-{
-  /* return non zero if core is halted */
-  stlink_status(sl);
-  return sl->q_buf[0] == STLINK_CORE_HALTED;
-}
-
-static int write_loader_to_sram
-(struct stlink* sl, stm32_addr_t* addr, size_t* size)
-{
-  /* from openocd, contrib/loaders/flash/stm32.s */
-  static const uint8_t loader_code[] =
-  {
-    0x08, 0x4c,                        /* ldr  r4, STM32_FLASH_BASE */
-    0x1c, 0x44,                        /* add  r4, r3 */
-    /* write_half_word: */
-    0x01, 0x23,                        /* movs r3, #0x01 */
-    0x23, 0x61,                        /* str  r3, [r4, #STM32_FLASH_CR_OFFSET] */
-    0x30, 0xf8, 0x02, 0x3b,    /* ldrh r3, [r0], #0x02 */
-    0x21, 0xf8, 0x02, 0x3b,    /* strh r3, [r1], #0x02 */
-    /* busy: */
-    0xe3, 0x68,                        /* ldr  r3, [r4, #STM32_FLASH_SR_OFFSET] */
-    0x13, 0xf0, 0x01, 0x0f,    /* tst  r3, #0x01 */
-    0xfb, 0xd0,                        /* beq  busy */
-    0x13, 0xf0, 0x14, 0x0f,    /* tst  r3, #0x14 */
-    0x01, 0xd1,                        /* bne  exit */
-    0x01, 0x3a,                        /* subs r2, r2, #0x01 */
-    0xf0, 0xd1,                        /* bne  write_half_word */
-    /* exit: */
-    0x00, 0xbe,                        /* bkpt #0x00 */
-    0x00, 0x20, 0x02, 0x40,    /* STM32_FLASH_BASE: .word 0x40022000 */
-  };
-
-  memcpy(sl->q_buf, loader_code, sizeof(loader_code));
-  stlink_write_mem32(sl, sl->sram_base, sizeof(loader_code));
-
-  *addr = sl->sram_base;
-  *size = sizeof(loader_code);
-
-  /* success */
-  return 0;
-}
-
-typedef struct flash_loader
-{
-  stm32_addr_t loader_addr; /* loader sram adddr */
-  stm32_addr_t buf_addr; /* buffer sram address */
-} flash_loader_t;
-
-static int write_buffer_to_sram
-(struct stlink* sl, flash_loader_t* fl, const uint8_t* buf, size_t size)
-{
-  /* write the buffer right after the loader */
-  memcpy(sl->q_buf, buf, size);
-  stlink_write_mem8(sl, fl->buf_addr, size);
-  return 0;
-}
-
-static int init_flash_loader
-(struct stlink* sl, flash_loader_t* fl)
-{
-  size_t size;
-
-  /* allocate the loader in sram */
-  if (write_loader_to_sram(sl, &fl->loader_addr, &size) == -1)
-  {
-    fprintf(stderr, "write_loader_to_sram() == -1\n");
-    return -1;
-  }
-
-  /* allocate a one page buffer in sram right after loader */
-  fl->buf_addr = fl->loader_addr + size;
-
-  return 0;
-}
-
-static int run_flash_loader
-(struct stlink* sl, flash_loader_t* fl, stm32_addr_t target, const uint8_t* buf, size_t size)
-{
-  const size_t count = size / sizeof(uint16_t);
-
-  if (write_buffer_to_sram(sl, fl, buf, size) == -1)
-  {
-    fprintf(stderr, "write_buffer_to_sram() == -1\n");
-    return -1;
-  }
-
-  /* setup core */
-  stlink_write_reg(sl, fl->buf_addr, 0); /* source */
-  stlink_write_reg(sl, target, 1); /* target */
-  stlink_write_reg(sl, count, 2); /* count (16 bits half words) */
-  stlink_write_reg(sl, 0, 3); /* flash bank 0 (input) */
-  stlink_write_reg(sl, fl->loader_addr, 15); /* pc register */
-
-  /* unlock and set programming mode */
-  unlock_flash_if(sl);
-  set_flash_cr_pg(sl);
-
-  /* run loader */
-  stlink_run(sl);
-  
-  while (is_core_halted(sl) == 0)
-    ;
-
-  lock_flash(sl);
-
-  /* not all bytes have been written */
-  stlink_read_reg(sl, 2);
-  if (sl->reg.r[2] != 0)
-  {
-    fprintf(stderr, "write error, count == %u\n", sl->reg.r[2]);
-    return -1;
-  }
-
-  return 0;
-}
-
-/* memory mapped file */
-
-typedef struct mapped_file
-{
-  uint8_t* base;
-  size_t len;
-} mapped_file_t;
-
-#define MAPPED_FILE_INITIALIZER { NULL, 0 }
-
-static int map_file(mapped_file_t* mf, const char* path)
-{
-  int error = -1;
-  struct stat st;
-
-  const int fd = open(path, O_RDONLY);
-  if (fd == -1)
-  {
-    fprintf(stderr, "open(%s) == -1\n", path);
-    return -1;
-  }
-
-  if (fstat(fd, &st) == -1)
-  {
-    fprintf(stderr, "fstat() == -1\n");
-    goto on_error;
-  }
-
-  mf->base = (uint8_t*)mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
-  if (mf->base == MAP_FAILED)
-  {
-    fprintf(stderr, "mmap() == MAP_FAILED\n");
-    goto on_error;
-  }
-
-  mf->len = st.st_size;
-
-  /* success */
-  error = 0;
-
- on_error:
-  close(fd);
-
-  return error;
-}
-
-static void unmap_file(mapped_file_t* mf)
-{
-  munmap((void*)mf->base, mf->len);
-  mf->base = (unsigned char*)MAP_FAILED;
-  mf->len = 0;
-}
-
-static int check_file
-(struct stlink* sl, mapped_file_t* mf, stm32_addr_t addr)
-{
-  size_t off;
-
-  for (off = 0; off < mf->len; off += sl->flash_pgsz)
-  {
-    size_t aligned_size;
-
-    /* adjust last page size */
-    size_t cmp_size = sl->flash_pgsz;
-    if ((off + sl->flash_pgsz) > mf->len)
-      cmp_size = mf->len - off;
-
-    aligned_size = cmp_size;
-    if (aligned_size & (4 - 1))
-      aligned_size = (cmp_size + 4) & ~(4 - 1);
-
-    stlink_read_mem32(sl, addr + off, aligned_size);
-
-    if (memcmp(sl->q_buf, mf->base + off, cmp_size))
-      return -1;
-  }
-
-  return 0;
-}
-
-static int stlink_fcheck_flash
-(struct stlink* sl, const char* path, stm32_addr_t addr)
-{
-  /* check the contents of path are at addr */
-
-  int res;
-  mapped_file_t mf = MAPPED_FILE_INITIALIZER;
-
-  if (map_file(&mf, path) == -1)
-    return -1;
-
-  res = check_file(sl, &mf, addr);
-
-  unmap_file(&mf);
-
-  return res;
-}
-
-static int stlink_fwrite_flash
-(struct stlink* sl, const char* path, stm32_addr_t addr)
-{
-  /* write the file in flash at addr */
-
-  int error = -1;
-  size_t off;
-  mapped_file_t mf = MAPPED_FILE_INITIALIZER;
-  flash_loader_t fl;
-
-  if (map_file(&mf, path) == -1)
-  {
-    fprintf(stderr, "map_file() == -1\n");
-    return -1;
-  }
-
-  /* check addr range is inside the flash */
-  if (addr < sl->flash_base)
-  {
-    fprintf(stderr, "addr too low\n");
-    goto on_error;
-  }
-  else if ((addr + mf.len) < addr)
-  {
-    fprintf(stderr, "addr overruns\n");
-    goto on_error;
-  }
-  else if ((addr + mf.len) > (sl->flash_base + sl->flash_size))
-  {
-    fprintf(stderr, "addr too high\n");
-    goto on_error;
-  }
-  else if ((addr & 1) || (mf.len & 1))
-  {
-    /* todo */
-    fprintf(stderr, "unaligned addr or size\n");
-    goto on_error;
-  }
-
-  /* erase each page. todo: mass erase faster? */
-  for (off = 0; off < mf.len; off += sl->flash_pgsz)
-  {
-    /* addr must be an addr inside the page */
-    if (erase_flash_page(sl, addr + off) == -1)
-    {
-      fprintf(stderr, "erase_flash_page(0x%x) == -1\n", addr + off);
-      goto on_error;
-    }
-  }
-
-  /* flash loader initialization */
-  if (init_flash_loader(sl, &fl) == -1)
-  {
-    fprintf(stderr, "init_flash_loader() == -1\n");
-    goto on_error;
-  }
-
-  /* write each page. above WRITE_BLOCK_SIZE fails? */
-#define WRITE_BLOCK_SIZE 0x40
-  for (off = 0; off < mf.len; off += WRITE_BLOCK_SIZE)
-  {
-    /* adjust last write size */
-    size_t size = WRITE_BLOCK_SIZE;
-    if ((off + WRITE_BLOCK_SIZE) > mf.len)
-      size = mf.len - off;
-
-    if (run_flash_loader(sl, &fl, addr + off, mf.base + off, size) == -1)
-    {
-      fprintf(stderr, "run_flash_loader(0x%x) == -1\n", addr + off);
-      goto on_error;
-    }
-  }
-
-  /* check the file ha been written */
-  if (check_file(sl, &mf, addr) == -1)
-  {
-    fprintf(stderr, "check_file() == -1\n");
-    goto on_error;
-  }
-
-  /* success */
-  error = 0;
-
- on_error:
-  unmap_file(&mf);
-  return error;
-}
-
-static int stlink_fwrite_sram
-(struct stlink* sl, const char* path, stm32_addr_t addr)
-{
-  /* write the file in sram at addr */
-
-  int error = -1;
-  size_t off;
-  mapped_file_t mf = MAPPED_FILE_INITIALIZER;
-
-  if (map_file(&mf, path) == -1)
-  {
-    fprintf(stderr, "map_file() == -1\n");
-    return -1;
-  }
-
-  /* check addr range is inside the sram */
-  if (addr < sl->sram_base)
-  {
-    fprintf(stderr, "addr too low\n");
-    goto on_error;
-  }
-  else if ((addr + mf.len) < addr)
-  {
-    fprintf(stderr, "addr overruns\n");
-    goto on_error;
-  }
-  else if ((addr + mf.len) > (sl->sram_base + sl->sram_size))
-  {
-    fprintf(stderr, "addr too high\n");
-    goto on_error;
-  }
-  else if ((addr & 3) || (mf.len & 3))
-  {
-    /* todo */
-    fprintf(stderr, "unaligned addr or size\n");
-    goto on_error;
-  }
-
-  /* do the copy by 1k blocks */
-  for (off = 0; off < mf.len; off += 1024)
-  {
-    size_t size = 1024;
-    if ((off + size) > mf.len)
-      size = mf.len - off;
-
-    memcpy(sl->q_buf, mf.base + off, size);
-
-    /* round size if needed */
-    if (size & 3)
-      size += 2;
-
-    stlink_write_mem32(sl, addr + off, size);
-  }
-
-  /* check the file ha been written */
-  if (check_file(sl, &mf, addr) == -1)
-  {
-    fprintf(stderr, "check_file() == -1\n");
-    goto on_error;
-  }
-
-  /* success */
-  error = 0;
-
- on_error:
-  unmap_file(&mf);
-  return error;
-}
-
-
-static int stlink_fread
-(struct stlink* sl, const char* path, stm32_addr_t addr, size_t size)
-{
-  /* read size bytes from addr to file */
-
-  int error = -1;
-  size_t off;
-
-  const int fd = open(path, O_RDWR | O_TRUNC | O_CREAT, 00700);
-  if (fd == -1)
-  {
-    fprintf(stderr, "open(%s) == -1\n", path);
-    return -1;
-  }
-
-  /* do the copy by 1k blocks */
-  for (off = 0; off < size; off += 1024)
-  {
-    size_t read_size = 1024;
-    if ((off + read_size) > size)
-      read_size = off + read_size;
-
-    /* round size if needed */
-    if (read_size & 3)
-      read_size = (read_size + 4) & ~(3);
-
-    stlink_read_mem32(sl, addr + off, read_size);
-
-    if (write(fd, sl->q_buf, read_size) != (ssize_t)read_size)
-    {
-      fprintf(stderr, "write() != read_size\n");
-      goto on_error;
-    }
-  }
-
-  /* success */
-  error = 0;
-
- on_error:
-  close(fd);
-
-  return error;
-}
-
-// 1) open a sg device, switch the stlink from dfu to mass mode
-// 2) wait 5s until the kernel driver stops reseting the broken device
-// 3) reopen the device
-// 4) the device driver is now ready for a switch to jtag/swd mode
-// TODO thinking, better error handling, wait until the kernel driver stops reseting the plugged-in device
-struct stlink* stlink_force_open(const char *dev_name, const int verbose) {
-       struct stlink *sl = stlink_open(dev_name, verbose);
-       if (sl == NULL) {
-               fputs("Error: could not open stlink device\n", stderr);
-               return NULL;
-       }
-
-       stlink_version(sl);
-
-       if (sl->st_vid != USB_ST_VID || sl->stlink_pid != USB_STLINK_PID) {
-               fprintf(stderr, "Error: the device %s is not a stlink\n",
-                       dev_name);
-               fprintf(stderr, "       VID: got %04x expect %04x \n",
-                       sl->st_vid, USB_ST_VID);
-               fprintf(stderr, "       PID: got %04x expect %04x \n",
-                       sl->stlink_pid, USB_STLINK_PID);
-               return NULL;
-       }
-
-       D(sl, "\n*** stlink_force_open ***\n");
-       switch (stlink_current_mode(sl)) {
-       case STLINK_DEV_MASS_MODE:
-               return sl;
-       case STLINK_DEV_DEBUG_MODE:
-               // TODO go to mass?
-               return sl;
-       }
-       fprintf(stderr, "\n*** switch the stlink to mass mode ***\n");
-       stlink_exit_dfu_mode(sl);
-       // exit the dfu mode -> the device is gone
-       fprintf(stderr, "\n*** reopen the stlink device ***\n");
-       delay(1000);
-       stlink_close(sl);
-       delay(5000);
-
-       sl = stlink_open(dev_name, verbose);
-       if (sl == NULL) {
-               fputs("Error: could not open stlink device\n", stderr);
-               return NULL;
-       }
-       // re-query device info
-       stlink_version(sl);
-       return sl;
-}
-
-static void __attribute__((unused)) mark_buf(struct stlink *sl) {
-       clear_buf(sl);
-       sl->q_buf[0] = 0x12;
-       sl->q_buf[1] = 0x34;
-       sl->q_buf[2] = 0x56;
-       sl->q_buf[3] = 0x78;
-       sl->q_buf[4] = 0x90;
-       sl->q_buf[15] = 0x42;
-       sl->q_buf[16] = 0x43;
-       sl->q_buf[63] = 0x42;
-       sl->q_buf[64] = 0x43;
-       sl->q_buf[1024 * 6 - 1] = 0x42; //6kB
-       sl->q_buf[1024 * 8 - 1] = 0x42; //8kB
-}
-
-int main(int argc, char *argv[]) {
-       // set scpi lib debug level: 0 for no debug info, 10 for lots
-       const int scsi_verbose = 2;
-       char *dev_name;
-
-       switch (argc) {
-       case 1:
-               fputs(
-                       "\nUsage: stlink-access-test /dev/sg0, sg1, ...\n"
-                               "\n*** Notice: The stlink firmware violates the USB standard.\n"
-                               "*** If you plug-in the discovery's stlink, wait a several\n"
-                               "*** minutes to let the kernel driver swallow the broken device.\n"
-                               "*** Watch:\ntail -f /var/log/messages\n"
-                               "*** This command sequence can shorten the waiting time and fix some issues.\n"
-                               "*** Unplug the stlink and execute once as root:\n"
-                               "modprobe -r usb-storage && modprobe usb-storage quirks=483:3744:lrwsro\n\n",
-                       stderr);
-               return EXIT_FAILURE;
-       case 2:
-               dev_name = argv[1];
-               break;
-       default:
-               return EXIT_FAILURE;
-       }
-
-       fputs("*** stlink access test ***\n", stderr);
-       fprintf(stderr, "Using sg_lib %s : scsi_pt %s\n", sg_lib_version(),
-               scsi_pt_version());
-
-       struct stlink *sl = stlink_force_open(dev_name, scsi_verbose);
-       if (sl == NULL)
-               return EXIT_FAILURE;
-
-       // we are in mass mode, go to swd
-       stlink_enter_swd_mode(sl);
-       stlink_current_mode(sl);
-       stlink_core_id(sl);
-       //----------------------------------------------------------------------
-
-       stlink_status(sl);
-       //stlink_force_debug(sl);
-       stlink_reset(sl);
-       stlink_status(sl);
-#if 0
-       // core system control block
-       stlink_read_mem32(sl, 0xe000ed00, 4);
-       fprintf(stderr, "cpu id base register: SCB_CPUID = got 0x%08x expect 0x411fc231", read_uint32(sl->q_buf, 0));
-       // no MPU
-       stlink_read_mem32(sl, 0xe000ed90, 4);
-       fprintf(stderr, "mpu type register: MPU_TYPER = got 0x%08x expect 0x0", read_uint32(sl->q_buf, 0));
-
-       stlink_read_mem32(sl, 0xe000edf0, 4);
-       fprintf(stderr, "DHCSR = 0x%08x", read_uint32(sl->q_buf, 0));
-
-       stlink_read_mem32(sl, 0x4001100c, 4);
-       fprintf(stderr, "GPIOC_ODR = 0x%08x", read_uint32(sl->q_buf, 0));
-#endif
-#if 0
-       // happy new year 2011: let blink all the leds
-       // see "RM0041 Reference manual - STM32F100xx advanced ARM-based 32-bit MCUs"
-
-#define GPIOC          0x40011000 // port C
-#define GPIOC_CRH      (GPIOC + 0x04) // port configuration register high
-#define GPIOC_ODR      (GPIOC + 0x0c) // port output data register
-#define LED_BLUE       (1<<8) // pin 8
-#define LED_GREEN      (1<<9) // pin 9
-       stlink_read_mem32(sl, GPIOC_CRH, 4);
-       uint32_t io_conf = read_uint32(sl->q_buf, 0);
-       fprintf(stderr, "GPIOC_CRH = 0x%08x", io_conf);
-
-       // set: general purpose output push-pull, output mode, max speed 10 MHz.
-       write_uint32(sl->q_buf, 0x44444411);
-       stlink_write_mem32(sl, GPIOC_CRH, 4);
-
-       clear_buf(sl);
-       for (int i = 0; i < 100; i++) {
-               write_uint32(sl->q_buf, LED_BLUE | LED_GREEN);
-               stlink_write_mem32(sl, GPIOC_ODR, 4);
-               /* stlink_read_mem32(sl, 0x4001100c, 4); */
-               /* fprintf(stderr, "GPIOC_ODR = 0x%08x", read_uint32(sl->q_buf, 0)); */
-               delay(100);
-
-               clear_buf(sl);
-               stlink_write_mem32(sl, GPIOC_ODR, 4); // PC lo
-               delay(100);
-       }
-       write_uint32(sl->q_buf, io_conf); // set old state
-
-#endif
-#if 0
-       // TODO rtfm: stlink doesn't have flash write routines
-       // writing to the flash area confuses the fw for the next read access
-
-       //stlink_read_mem32(sl, 0, 1024*6);
-       // flash 0x08000000 128kB
-       fputs("++++++++++ read a flash at 0x0800 0000\n", stderr);
-       stlink_read_mem32(sl, 0x08000000, 1024 * 6); //max 6kB
-       clear_buf(sl);
-       stlink_read_mem32(sl, 0x08000c00, 5);
-       stlink_read_mem32(sl, 0x08000c00, 4);
-       mark_buf(sl);
-       stlink_write_mem32(sl, 0x08000c00, 4);
-       stlink_read_mem32(sl, 0x08000c00, 256);
-       stlink_read_mem32(sl, 0x08000c00, 256);
-#endif
-#if 0
-       // sram 0x20000000 8kB
-       fputs("\n++++++++++ read/write 8bit, sram at 0x2000 0000 ++++++++++++++++\n\n", stderr);
-       clear_buf(sl);
-       stlink_write_mem8(sl, 0x20000000, 16);
-
-       mark_buf(sl);
-       stlink_write_mem8(sl, 0x20000000, 1);
-       stlink_write_mem8(sl, 0x20000001, 1);
-       stlink_write_mem8(sl, 0x2000000b, 3);
-       stlink_read_mem32(sl, 0x20000000, 16);
-#endif
-#if 0
-       // a not aligned mem32 access doesn't work indeed
-       fputs("\n++++++++++ read/write 32bit, sram at 0x2000 0000 ++++++++++++++++\n\n", stderr);
-       clear_buf(sl);
-       stlink_write_mem8(sl, 0x20000000, 32);
-
-       mark_buf(sl);
-       stlink_write_mem32(sl, 0x20000000, 1);
-       stlink_read_mem32(sl, 0x20000000, 16);
-       mark_buf(sl);
-       stlink_write_mem32(sl, 0x20000001, 1);
-       stlink_read_mem32(sl, 0x20000000, 16);
-       mark_buf(sl);
-       stlink_write_mem32(sl, 0x2000000b, 3);
-       stlink_read_mem32(sl, 0x20000000, 16);
-
-       mark_buf(sl);
-       stlink_write_mem32(sl, 0x20000000, 17);
-       stlink_read_mem32(sl, 0x20000000, 32);
-#endif
-#if 0
-       // sram 0x20000000 8kB
-       fputs("++++++++++ read/write 32bit, sram at 0x2000 0000 ++++++++++++\n", stderr);
-       mark_buf(sl);
-       stlink_write_mem8(sl, 0x20000000, 64);
-       stlink_read_mem32(sl, 0x20000000, 64);
-
-       mark_buf(sl);
-       stlink_write_mem32(sl, 0x20000000, 1024 * 8); //8kB
-       stlink_read_mem32(sl, 0x20000000, 1024 * 6);
-       stlink_read_mem32(sl, 0x20000000 + 1024 * 6, 1024 * 2);
-#endif
-#if 0
-       stlink_read_all_regs(sl);
-       stlink_step(sl);
-       fputs("++++++++++ write r0 = 0x12345678\n", stderr);
-       stlink_write_reg(sl, 0x12345678, 0);
-       stlink_read_reg(sl, 0);
-       stlink_read_all_regs(sl);
-#endif
-#if 0
-       stlink_run(sl);
-       stlink_status(sl);
-
-       stlink_force_debug(sl);
-       stlink_status(sl);
-#endif
-#if 1 /* read the system bootloader */
-       fputs("\n++++++++++ reading bootloader ++++++++++++++++\n\n", stderr);
-       stlink_fread(sl, "/tmp/barfoo", sl->sys_base, sl->sys_size);
-#endif
-#if 0 /* read the flash memory */
-       fputs("\n+++++++ read flash memory\n\n", stderr);
-       /* mark_buf(sl); */
-       stlink_read_mem32(sl, 0x08000000, 4);
-#endif
-#if 0 /* flash programming */
-       fputs("\n+++++++ program flash memory\n\n", stderr);
-       stlink_fwrite_flash(sl, "/tmp/foobar", 0x08000000);
-#endif
-#if 0 /* check file contents */
-       fputs("\n+++++++ check flash memory\n\n", stderr);
-       {
-         const int res = stlink_fcheck_flash(sl, "/tmp/foobar", 0x08000000);
-         printf("_____ stlink_fcheck_flash() == %d\n", res);
-       }
-#endif
-#if 0
-       fputs("\n+++++++ sram write and execute\n\n", stderr);
-       stlink_fwrite_sram(sl, "/tmp/foobar", sl->sram_base);
-       stlink_run_at(sl, sl->sram_base);
-#endif
-
-       stlink_run(sl);
-       stlink_status(sl);
-       //----------------------------------------------------------------------
-       // back to mass mode, just in case ...
-       stlink_exit_debug_mode(sl);
-       stlink_current_mode(sl);
-       stlink_close(sl);
-
-       //fflush(stderr); fflush(stdout);
-       return EXIT_SUCCESS;
-}
diff --git a/src/stlink-hw.c b/src/stlink-hw.c
new file mode 100644 (file)
index 0000000..441b692
--- /dev/null
@@ -0,0 +1,1850 @@
+/*
+ Copyright (c) 2010 "Capt'ns Missing Link" Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style
+ license that can be found in the LICENSE file.
+
+ A linux stlink access demo. The purpose of this file is to mitigate the usual
+ "reinventing the wheel" force by incompatible licenses and give you an idea,
+ how to access the stlink device. That doesn't mean you should be a free-loader
+ and not contribute your improvements to this code.
+
+ Author: Martin Capitanio <m@capitanio.org>
+ The stlink related constants kindly provided by Oliver Spencer (OpenOCD)
+ for use in a GPL compatible license.
+
+ Code format ~ TAB = 8, K&R, linux kernel source, golang oriented
+ Tested compatibility: linux, gcc >= 4.3.3
+
+ The communication is based on standard USB mass storage device
+ BOT (Bulk Only Transfer)
+ - Endpoint 1: BULK_IN, 64 bytes max
+ - Endpoint 2: BULK_OUT, 64 bytes max
+
+ All CBW transfers are ordered with the LSB (byte 0) first (little endian).
+ Any command must be answered before sending the next command.
+ Each USB transfer must complete in less than 1s.
+
+ SB Device Class Definition for Mass Storage Devices:
+ www.usb.org/developers/devclass_docs/usbmassbulk_10.pdf
+
+ dt            - Data Transfer (IN/OUT)
+ CBW           - Command Block Wrapper
+ CSW           - Command Status Wrapper
+ RFU           - Reserved for Future Use
+ scsi_pt       - SCSI pass-through
+ sg            - SCSI generic
+
+ * usb-storage.quirks
+ http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob_plain;f=Documentation/kernel-parameters.txt
+ Each entry has the form VID:PID:Flags where VID and PID are Vendor and Product
+ ID values (4-digit hex numbers) and Flags is a set of characters, each corresponding
+ to a common usb-storage quirk flag as follows:
+
+ a = SANE_SENSE (collect more than 18 bytes of sense data);
+ b = BAD_SENSE (don't collect more than 18 bytes of sense data);
+ c = FIX_CAPACITY (decrease the reported device capacity by one sector);
+ h = CAPACITY_HEURISTICS (decrease the reported device capacity by one sector if the number is odd);
+ i = IGNORE_DEVICE (don't bind to this device);
+ l = NOT_LOCKABLE (don't try to lock and unlock ejectable media);
+ m = MAX_SECTORS_64 (don't transfer more than 64 sectors = 32 KB at a time);
+ o = CAPACITY_OK (accept the capacity reported by the device);
+ r = IGNORE_RESIDUE (the device reports bogus residue values);
+ s = SINGLE_LUN (the device has only one Logical Unit);
+ w = NO_WP_DETECT (don't test whether the medium is write-protected).
+
+ Example: quirks=0419:aaf5:rl,0421:0433:rc
+ http://permalink.gmane.org/gmane.linux.usb.general/35053
+
+ modprobe -r usb-storage && modprobe usb-storage quirks=483:3744:l
+
+ Equivalently, you can add a line saying
+
+ options usb-storage quirks=483:3744:l
+
+ to your /etc/modprobe.conf or /etc/modprobe.d/local.conf (or add the "quirks=..."
+ part to an existing options line for usb-storage).
+ */
+
+#define __USE_GNU
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+// sgutils2 (apt-get install libsgutils2-dev)
+#include <scsi/sg_lib.h>
+#include <scsi/sg_pt.h>
+
+#include "stlink-hw.h"
+
+static void D(struct stlink *sl, char *txt) {
+       if (sl->verbose > 1)
+               fputs(txt, stderr);
+}
+
+static void DD(struct stlink *sl, char *format, ...) {
+       if (sl->verbose > 0) {
+               va_list list;
+               va_start(list, format);
+               vfprintf(stderr, format, list);
+               va_end(list);
+       }
+}
+
+// Suspends execution of the calling process for
+// (at least) ms milliseconds.
+static void delay(int ms) {
+       //fprintf(stderr, "*** wait %d ms\n", ms);
+       usleep(1000 * ms);
+}
+
+// Endianness
+// http://www.ibm.com/developerworks/aix/library/au-endianc/index.html
+// const int i = 1;
+// #define is_bigendian() ( (*(char*)&i) == 0 )
+static inline unsigned int is_bigendian(void) {
+       static volatile const unsigned int i = 1;
+       return *(volatile const char*) &i == 0;
+}
+
+static void write_uint32(unsigned char* buf, uint32_t ui) {
+       if (!is_bigendian()) { // le -> le (don't swap)
+               buf[0] = ((unsigned char*) &ui)[0];
+               buf[1] = ((unsigned char*) &ui)[1];
+               buf[2] = ((unsigned char*) &ui)[2];
+               buf[3] = ((unsigned char*) &ui)[3];
+       } else {
+               buf[0] = ((unsigned char*) &ui)[3];
+               buf[1] = ((unsigned char*) &ui)[2];
+               buf[2] = ((unsigned char*) &ui)[1];
+               buf[3] = ((unsigned char*) &ui)[0];
+       }
+}
+
+static void write_uint16(unsigned char* buf, uint16_t ui) {
+       if (!is_bigendian()) { // le -> le (don't swap)
+               buf[0] = ((unsigned char*) &ui)[0];
+               buf[1] = ((unsigned char*) &ui)[1];
+       } else {
+               buf[0] = ((unsigned char*) &ui)[1];
+               buf[1] = ((unsigned char*) &ui)[0];
+       }
+}
+
+static uint32_t read_uint32(const unsigned char *c, const int pt) {
+       uint32_t ui;
+       char *p = (char *) &ui;
+
+       if (!is_bigendian()) { // le -> le (don't swap)
+               p[0] = c[pt];
+               p[1] = c[pt + 1];
+               p[2] = c[pt + 2];
+               p[3] = c[pt + 3];
+       } else {
+               p[0] = c[pt + 3];
+               p[1] = c[pt + 2];
+               p[2] = c[pt + 1];
+               p[3] = c[pt];
+       }
+       return ui;
+}
+
+static uint16_t read_uint16(const unsigned char *c, const int pt) {
+       uint32_t ui;
+       char *p = (char *) &ui;
+
+       if (!is_bigendian()) { // le -> le (don't swap)
+               p[0] = c[pt];
+               p[1] = c[pt + 1];
+       } else {
+               p[0] = c[pt + 1];
+               p[1] = c[pt];
+       }
+       return ui;
+}
+
+static void clear_cdb(struct stlink *sl) {
+       for (int i = 0; i < sizeof(sl->cdb_cmd_blk); i++)
+               sl->cdb_cmd_blk[i] = 0;
+       // set default
+       sl->cdb_cmd_blk[0] = STLINK_DEBUG_COMMAND;
+       sl->q_data_dir = Q_DATA_IN;
+}
+
+// E.g. make the valgrind happy.
+static void clear_buf(struct stlink *sl) {
+       DD(sl, "*** clear_buf ***\n");
+       for (int i = 0; i < sizeof(sl->q_buf); i++)
+               sl->q_buf[i] = 0;
+
+}
+
+static struct stlink* stlink_open(const char *dev_name, const int verbose) {
+       fprintf(stderr, "\n*** stlink_open [%s] ***\n", dev_name);
+       int sg_fd = scsi_pt_open_device(dev_name, RDWR, verbose);
+       if (sg_fd < 0) {
+               fprintf(stderr, "error opening device: %s: %s\n", dev_name,
+                       safe_strerror(-sg_fd));
+               return NULL;
+       }
+
+       struct stlink *sl = malloc(sizeof(struct stlink));
+       if (sl == NULL) {
+               fprintf(stderr, "struct stlink: out of memory\n");
+               return NULL;
+       }
+
+       sl->sg_fd = sg_fd;
+       sl->verbose = verbose;
+       sl->core_stat = STLINK_CORE_STAT_UNKNOWN;
+       sl->core_id = 0;
+       sl->q_addr = 0;
+       clear_buf(sl);
+
+       /* flash memory settings */
+       sl->flash_base = STM32_FLASH_BASE;
+       sl->flash_size = STM32_FLASH_SIZE;
+       sl->flash_pgsz = STM32_FLASH_PGSZ;
+
+       /* system memory */
+       sl->sys_base = STM32_SYSTEM_BASE;
+       sl->sys_size = STM32_SYSTEM_SIZE;
+
+       /* sram memory settings */
+       sl->sram_base = STM32_SRAM_BASE;
+       sl->sram_size = STM32_SRAM_SIZE;
+
+       return sl;
+}
+
+// close the device, free the allocated memory
+void stlink_close(struct stlink *sl) {
+       D(sl, "\n*** stlink_close ***\n");
+       if (sl) {
+               scsi_pt_close_device(sl->sg_fd);
+               free(sl);
+       }
+}
+
+//TODO rewrite/cleanup, save the error in sl
+static void stlink_confirm_inq(struct stlink *sl, struct sg_pt_base *ptvp) {
+       const int e = sl->do_scsi_pt_err;
+       if (e < 0) {
+               fprintf(stderr, "scsi_pt error: pass through os error: %s\n",
+                       safe_strerror(-e));
+               return;
+       } else if (e == SCSI_PT_DO_BAD_PARAMS) {
+               fprintf(stderr, "scsi_pt error: bad pass through setup\n");
+               return;
+       } else if (e == SCSI_PT_DO_TIMEOUT) {
+               fprintf(stderr, "  pass through timeout\n");
+               return;
+       }
+       const int duration = get_scsi_pt_duration_ms(ptvp);
+       if ((sl->verbose > 1) && (duration >= 0))
+               DD(sl, "      duration=%d ms\n", duration);
+
+       // XXX stlink fw sends broken residue, so ignore it and use the known q_len
+       // "usb-storage quirks=483:3744:r"
+       // forces residue to be ignored and calculated, but this causes aboard if
+       // data_len = 0 and by some other data_len values.
+
+       const int resid = get_scsi_pt_resid(ptvp);
+       const int dsize = sl->q_len - resid;
+
+       const int cat = get_scsi_pt_result_category(ptvp);
+       char buf[512];
+       unsigned int slen;
+
+       switch (cat) {
+       case SCSI_PT_RESULT_GOOD:
+               if (sl->verbose && (resid > 0))
+                       DD(sl, "      notice: requested %d bytes but "
+                               "got %d bytes, ignore [broken] residue = %d\n",
+                               sl->q_len, dsize, resid);
+               break;
+       case SCSI_PT_RESULT_STATUS:
+               if (sl->verbose) {
+                       sg_get_scsi_status_str(
+                               get_scsi_pt_status_response(ptvp), sizeof(buf),
+                               buf);
+                       DD(sl, "  scsi status: %s\n", buf);
+               }
+               return;
+       case SCSI_PT_RESULT_SENSE:
+               slen = get_scsi_pt_sense_len(ptvp);
+               if (sl->verbose) {
+                       sg_get_sense_str("", sl->sense_buf, slen, (sl->verbose
+                               > 1), sizeof(buf), buf);
+                       DD(sl, "%s", buf);
+               }
+               if (sl->verbose && (resid > 0)) {
+                       if ((sl->verbose) || (sl->q_len > 0))
+                               DD(sl, "    requested %d bytes but "
+                                       "got %d bytes\n", sl->q_len, dsize);
+               }
+               return;
+       case SCSI_PT_RESULT_TRANSPORT_ERR:
+               if (sl->verbose) {
+                       get_scsi_pt_transport_err_str(ptvp, sizeof(buf), buf);
+                       // http://tldp.org/HOWTO/SCSI-Generic-HOWTO/x291.html
+                       // These codes potentially come from the firmware on a host adapter
+                       // or from one of several hosts that an adapter driver controls.
+                       // The 'host_status' field has the following values:
+                       //      [0x07] Internal error detected in the host adapter.
+                       // This may not be fatal (and the command may have succeeded).
+                       DD(sl, "  transport: %s", buf);
+               }
+               return;
+       case SCSI_PT_RESULT_OS_ERR:
+               if (sl->verbose) {
+                       get_scsi_pt_os_err_str(ptvp, sizeof(buf), buf);
+                       DD(sl, "  os: %s", buf);
+               }
+               return;
+       default:
+               fprintf(stderr, "  unknown pass through result "
+                       "category (%d)\n", cat);
+       }
+}
+
+static void stlink_q(struct stlink* sl) {
+       DD(sl, "CDB[");
+       for (int i = 0; i < CDB_SL; i++)
+               DD(sl, " 0x%02x", (unsigned int) sl->cdb_cmd_blk[i]);
+       DD(sl, "]\n");
+
+       // Get control command descriptor of scsi structure,
+       // (one object per command!!)
+       struct sg_pt_base *ptvp = construct_scsi_pt_obj();
+       if (NULL == ptvp) {
+               fprintf(stderr, "construct_scsi_pt_obj: out of memory\n");
+               return;
+       }
+
+       set_scsi_pt_cdb(ptvp, sl->cdb_cmd_blk, sizeof(sl->cdb_cmd_blk));
+
+       // set buffer for sense (error information) data
+       set_scsi_pt_sense(ptvp, sl->sense_buf, sizeof(sl->sense_buf));
+
+       // Set a buffer to be used for data transferred from device
+       if (sl->q_data_dir == Q_DATA_IN) {
+               //clear_buf(sl);
+               set_scsi_pt_data_in(ptvp, sl->q_buf, sl->q_len);
+       } else {
+               set_scsi_pt_data_out(ptvp, sl->q_buf, sl->q_len);
+       }
+       // Executes SCSI command (or at least forwards it to lower layers).
+       sl->do_scsi_pt_err = do_scsi_pt(ptvp, sl->sg_fd, SG_TIMEOUT_SEC,
+               sl->verbose);
+
+       // check for scsi errors
+       stlink_confirm_inq(sl, ptvp);
+       // TODO recycle: clear_scsi_pt_obj(struct sg_pt_base * objp);
+       destruct_scsi_pt_obj(ptvp);
+}
+
+static void stlink_print_data(struct stlink *sl) {
+       if (sl->q_len <= 0 || sl->verbose < 2)
+               return;
+       if (sl->verbose > 2)
+               fprintf(stdout, "data_len = %d 0x%x\n", sl->q_len, sl->q_len);
+
+       for (uint32_t i = 0; i < sl->q_len; i++) {
+               if (i % 16 == 0) {
+                       if (sl->q_data_dir == Q_DATA_OUT)
+                               fprintf(stdout, "\n<- 0x%08x ", sl->q_addr + i);
+                       else
+                               fprintf(stdout, "\n-> 0x%08x ", sl->q_addr + i);
+               }
+               fprintf(stdout, " %02x", (unsigned int) sl->q_buf[i]);
+       }
+       fputs("\n\n", stdout);
+}
+
+// TODO thinking, cleanup
+static void stlink_parse_version(struct stlink *sl) {
+       sl->st_vid = 0;
+       sl->stlink_pid = 0;
+       if (sl->q_len <= 0) {
+               fprintf(stderr, "Error: could not parse the stlink version");
+               return;
+       }
+       stlink_print_data(sl);
+       uint32_t b0 = sl->q_buf[0]; //lsb
+       uint32_t b1 = sl->q_buf[1];
+       uint32_t b2 = sl->q_buf[2];
+       uint32_t b3 = sl->q_buf[3];
+       uint32_t b4 = sl->q_buf[4];
+       uint32_t b5 = sl->q_buf[5]; //msb
+
+       // b0 b1                       || b2 b3  | b4 b5
+       // 4b        | 6b     | 6b     || 2B     | 2B
+       // stlink_v  | jtag_v | swim_v || st_vid | stlink_pid
+
+       sl->stlink_v = (b0 & 0xf0) >> 4;
+       sl->jtag_v = ((b0 & 0x0f) << 2) | ((b1 & 0xc0) >> 6);
+       sl->swim_v = b1 & 0x3f;
+       sl->st_vid = (b3 << 8) | b2;
+       sl->stlink_pid = (b5 << 8) | b4;
+
+       if (sl->verbose < 2)
+               return;
+
+       DD(sl, "st vid         = 0x%04x (expect 0x%04x)\n",
+               sl->st_vid, USB_ST_VID);
+       DD(sl, "stlink pid     = 0x%04x (expect 0x%04x)\n",
+               sl->stlink_pid, USB_STLINK_PID);
+       DD(sl, "stlink version = 0x%x\n", sl->stlink_v);
+       DD(sl, "jtag version   = 0x%x\n", sl->jtag_v);
+       DD(sl, "swim version   = 0x%x\n", sl->swim_v);
+       if (sl->jtag_v == 0)
+               DD(sl,
+                       "    notice: the firmware doesn't support a jtag/swd interface\n");
+       if (sl->swim_v == 0)
+               DD(sl,
+                       "    notice: the firmware doesn't support a swim interface\n");
+
+}
+
+static int stlink_mode(struct stlink *sl) {
+       if (sl->q_len <= 0)
+               return STLINK_DEV_UNKNOWN_MODE;
+
+       stlink_print_data(sl);
+
+       switch (sl->q_buf[0]) {
+       case STLINK_DEV_DFU_MODE:
+               DD(sl, "stlink mode: dfu\n");
+               return STLINK_DEV_DFU_MODE;
+       case STLINK_DEV_DEBUG_MODE:
+               DD(sl, "stlink mode: debug (jtag or swd)\n");
+               return STLINK_DEV_DEBUG_MODE;
+       case STLINK_DEV_MASS_MODE:
+               DD(sl, "stlink mode: mass\n");
+               return STLINK_DEV_MASS_MODE;
+       }
+       return STLINK_DEV_UNKNOWN_MODE;
+}
+
+static void stlink_stat(struct stlink *sl, char *txt) {
+       if (sl->q_len <= 0)
+               return;
+
+       stlink_print_data(sl);
+
+       switch (sl->q_buf[0]) {
+       case STLINK_OK:
+               DD(sl, "  %s: ok\n", txt);
+               return;
+       case STLINK_FALSE:
+               DD(sl, "  %s: false\n", txt);
+               return;
+       default:
+               DD(sl, "  %s: unknown\n", txt);
+       }
+}
+
+static void stlink_core_stat(struct stlink *sl) {
+       if (sl->q_len <= 0)
+               return;
+
+       stlink_print_data(sl);
+
+       switch (sl->q_buf[0]) {
+       case STLINK_CORE_RUNNING:
+               sl->core_stat = STLINK_CORE_RUNNING;
+               DD(sl, "  core status: running\n");
+               return;
+       case STLINK_CORE_HALTED:
+               sl->core_stat = STLINK_CORE_HALTED;
+               DD(sl, "  core status: halted\n");
+               return;
+       default:
+               sl->core_stat = STLINK_CORE_STAT_UNKNOWN;
+               fprintf(stderr, "  core status: unknown\n");
+       }
+}
+
+void stlink_version(struct stlink *sl) {
+       D(sl, "\n*** stlink_version ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[0] = STLINK_GET_VERSION;
+       sl->q_len = 6;
+       sl->q_addr = 0;
+       stlink_q(sl);
+       stlink_parse_version(sl);
+}
+
+// Get stlink mode:
+// STLINK_DEV_DFU_MODE || STLINK_DEV_MASS_MODE || STLINK_DEV_DEBUG_MODE
+// usb dfu             || usb mass             || jtag or swd
+int stlink_current_mode(struct stlink *sl) {
+       D(sl, "\n*** stlink_current_mode ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[0] = STLINK_GET_CURRENT_MODE;
+       sl->q_len = 2;
+       sl->q_addr = 0;
+       stlink_q(sl);
+       return stlink_mode(sl);
+}
+
+// Exit the mass mode and enter the swd debug mode.
+void stlink_enter_swd_mode(struct stlink *sl) {
+       D(sl, "\n*** stlink_enter_swd_mode ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_ENTER;
+       sl->cdb_cmd_blk[2] = STLINK_DEBUG_ENTER_SWD;
+       sl->q_len = 0; // >0 -> aboard
+       stlink_q(sl);
+}
+
+// Exit the mass mode and enter the jtag debug mode.
+// (jtag is disabled in the discovery's stlink firmware)
+void stlink_enter_jtag_mode(struct stlink *sl) {
+       D(sl, "\n*** stlink_enter_jtag_mode ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_ENTER;
+       sl->cdb_cmd_blk[2] = STLINK_DEBUG_ENTER_JTAG;
+       sl->q_len = 0;
+       stlink_q(sl);
+}
+
+// Exit the jtag or swd mode and enter the mass mode.
+void stlink_exit_debug_mode(struct stlink *sl) {
+       D(sl, "\n*** stlink_exit_debug_mode ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_EXIT;
+       sl->q_len = 0; // >0 -> aboard
+       stlink_q(sl);
+}
+
+// XXX kernel driver performs reset, the device temporally disappears
+static void stlink_exit_dfu_mode(struct stlink *sl) {
+       D(sl, "\n*** stlink_exit_dfu_mode ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[0] = STLINK_DFU_COMMAND;
+       sl->cdb_cmd_blk[1] = STLINK_DFU_EXIT;
+       sl->q_len = 0; // ??
+       stlink_q(sl);
+       /*
+        [135121.844564] sd 19:0:0:0: [sdb] Unhandled error code
+        [135121.844569] sd 19:0:0:0: [sdb] Result: hostbyte=DID_ERROR driverbyte=DRIVER_OK
+        [135121.844574] sd 19:0:0:0: [sdb] CDB: Read(10): 28 00 00 00 10 00 00 00 08 00
+        [135121.844584] end_request: I/O error, dev sdb, sector 4096
+        [135121.844590] Buffer I/O error on device sdb, logical block 512
+        [135130.122567] usb 6-1: reset full speed USB device using uhci_hcd and address 7
+        [135130.274551] usb 6-1: device firmware changed
+        [135130.274618] usb 6-1: USB disconnect, address 7
+        [135130.275186] VFS: busy inodes on changed media or resized disk sdb
+        [135130.275424] VFS: busy inodes on changed media or resized disk sdb
+        [135130.286758] VFS: busy inodes on changed media or resized disk sdb
+        [135130.292796] VFS: busy inodes on changed media or resized disk sdb
+        [135130.301481] VFS: busy inodes on changed media or resized disk sdb
+        [135130.304316] VFS: busy inodes on changed media or resized disk sdb
+        [135130.431113] usb 6-1: new full speed USB device using uhci_hcd and address 8
+        [135130.629444] usb-storage 6-1:1.0: Quirks match for vid 0483 pid 3744: 102a1
+        [135130.629492] scsi20 : usb-storage 6-1:1.0
+        [135131.625600] scsi 20:0:0:0: Direct-Access     STM32                          PQ: 0 ANSI: 0
+        [135131.627010] sd 20:0:0:0: Attached scsi generic sg2 type 0
+        [135131.633603] sd 20:0:0:0: [sdb] 64000 512-byte logical blocks: (32.7 MB/31.2 MiB)
+        [135131.633613] sd 20:0:0:0: [sdb] Assuming Write Enabled
+        [135131.633620] sd 20:0:0:0: [sdb] Assuming drive cache: write through
+        [135131.640584] sd 20:0:0:0: [sdb] Assuming Write Enabled
+        [135131.640592] sd 20:0:0:0: [sdb] Assuming drive cache: write through
+        [135131.640609]  sdb:
+        [135131.652634] sd 20:0:0:0: [sdb] Assuming Write Enabled
+        [135131.652639] sd 20:0:0:0: [sdb] Assuming drive cache: write through
+        [135131.652645] sd 20:0:0:0: [sdb] Attached SCSI removable disk
+        [135131.671536] sd 20:0:0:0: [sdb] Result: hostbyte=DID_OK driverbyte=DRIVER_SENSE
+        [135131.671548] sd 20:0:0:0: [sdb] Sense Key : Illegal Request [current]
+        [135131.671553] sd 20:0:0:0: [sdb] Add. Sense: Logical block address out of range
+        [135131.671560] sd 20:0:0:0: [sdb] CDB: Read(10): 28 00 00 00 f9 80 00 00 08 00
+        [135131.671570] end_request: I/O error, dev sdb, sector 63872
+        [135131.671575] Buffer I/O error on device sdb, logical block 7984
+        [135131.678527] sd 20:0:0:0: [sdb] Result: hostbyte=DID_OK driverbyte=DRIVER_SENSE
+        [135131.678532] sd 20:0:0:0: [sdb] Sense Key : Illegal Request [current]
+        [135131.678537] sd 20:0:0:0: [sdb] Add. Sense: Logical block address out of range
+        [135131.678542] sd 20:0:0:0: [sdb] CDB: Read(10): 28 00 00 00 f9 80 00 00 08 00
+        [135131.678551] end_request: I/O error, dev sdb, sector 63872
+        ...
+        [135131.853565] end_request: I/O error, dev sdb, sector 4096
+        */
+}
+
+void stlink_core_id(struct stlink *sl) {
+       D(sl, "\n*** stlink_core_id ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_READCOREID;
+       sl->q_len = 4;
+       sl->q_addr = 0;
+       stlink_q(sl);
+       sl->core_id = read_uint32(sl->q_buf, 0);
+       if (sl->verbose < 2)
+               return;
+       stlink_print_data(sl);
+       DD(sl, "core_id = 0x%08x\n", sl->core_id);
+}
+
+// Arm-core reset -> halted state.
+void stlink_reset(struct stlink *sl) {
+       D(sl, "\n*** stlink_reset ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_RESETSYS;
+       sl->q_len = 2;
+       sl->q_addr = 0;
+       stlink_q(sl);
+       stlink_stat(sl, "core reset");
+}
+
+// Arm-core status: halted or running.
+void stlink_status(struct stlink *sl) {
+       D(sl, "\n*** stlink_status ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_GETSTATUS;
+       sl->q_len = 2;
+       sl->q_addr = 0;
+       stlink_q(sl);
+       stlink_core_stat(sl);
+}
+
+// Force the core into the debug mode -> halted state.
+void stlink_force_debug(struct stlink *sl) {
+       D(sl, "\n*** stlink_force_debug ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_FORCEDEBUG;
+       sl->q_len = 2;
+       sl->q_addr = 0;
+       stlink_q(sl);
+       stlink_stat(sl, "force debug");
+}
+
+// Read all arm-core registers.
+void stlink_read_all_regs(struct stlink *sl) {
+       D(sl, "\n*** stlink_read_all_regs ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_READALLREGS;
+       sl->q_len = 84;
+       sl->q_addr = 0;
+       stlink_q(sl);
+       stlink_print_data(sl);
+
+       // 0-3 | 4-7 | ... | 60-63 | 64-67 | 68-71   | 72-75      | 76-79 | 80-83
+       // r0  | r1  | ... | r15   | xpsr  | main_sp | process_sp | rw    | rw2
+       for (int i = 0; i < 16; i++) {
+               sl->reg.r[i] = read_uint32(sl->q_buf, 4 * i);
+               if (sl->verbose > 1)
+                       DD(sl, "r%2d = 0x%08x\n", i, sl->reg.r[i]);
+       }
+       sl->reg.xpsr = read_uint32(sl->q_buf, 64);
+       sl->reg.main_sp = read_uint32(sl->q_buf, 68);
+       sl->reg.process_sp = read_uint32(sl->q_buf, 72);
+       sl->reg.rw = read_uint32(sl->q_buf, 76);
+       sl->reg.rw2 = read_uint32(sl->q_buf, 80);
+       if (sl->verbose < 2)
+               return;
+
+       DD(sl, "xpsr       = 0x%08x\n", sl->reg.xpsr);
+       DD(sl, "main_sp    = 0x%08x\n", sl->reg.main_sp);
+       DD(sl, "process_sp = 0x%08x\n", sl->reg.process_sp);
+       DD(sl, "rw         = 0x%08x\n", sl->reg.rw);
+       DD(sl, "rw2        = 0x%08x\n", sl->reg.rw2);
+}
+
+// Read an arm-core register, the index must be in the range 0..20.
+//  0  |  1  | ... |  15   |  16   |   17    |   18       |  19   |  20
+// r0  | r1  | ... | r15   | xpsr  | main_sp | process_sp | rw    | rw2
+void stlink_read_reg(struct stlink *sl, int r_idx) {
+       D(sl, "\n*** stlink_read_reg");
+       DD(sl, " (%d) ***\n", r_idx);
+
+       if (r_idx > 20 || r_idx < 0) {
+               fprintf(stderr, "Error: register index must be in [0..20]\n");
+               return;
+       }
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_READREG;
+       sl->cdb_cmd_blk[2] = r_idx;
+       sl->q_len = 4;
+       sl->q_addr = 0;
+       stlink_q(sl);
+       //  0  |  1  | ... |  15   |  16   |   17    |   18       |  19   |  20
+       // 0-3 | 4-7 | ... | 60-63 | 64-67 | 68-71   | 72-75      | 76-79 | 80-83
+       // r0  | r1  | ... | r15   | xpsr  | main_sp | process_sp | rw    | rw2
+       stlink_print_data(sl);
+
+       uint32_t r = read_uint32(sl->q_buf, 0);
+       DD(sl, "r_idx (%2d) = 0x%08x\n", r_idx, r);
+
+       switch (r_idx) {
+       case 16:
+               sl->reg.xpsr = r;
+               break;
+       case 17:
+               sl->reg.main_sp = r;
+               break;
+       case 18:
+               sl->reg.process_sp = r;
+               break;
+       case 19:
+               sl->reg.rw = r; //XXX ?(primask, basemask etc.)
+               break;
+       case 20:
+               sl->reg.rw2 = r; //XXX ?(primask, basemask etc.)
+               break;
+       default:
+               sl->reg.r[r_idx] = r;
+       }
+}
+
+// Write an arm-core register. Index:
+//  0  |  1  | ... |  15   |  16   |   17    |   18       |  19   |  20
+// r0  | r1  | ... | r15   | xpsr  | main_sp | process_sp | rw    | rw2
+void stlink_write_reg(struct stlink *sl, uint32_t reg, int idx) {
+       D(sl, "\n*** stlink_write_reg ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_WRITEREG;
+       //   2: reg index
+       // 3-6: reg content
+       sl->cdb_cmd_blk[2] = idx;
+       write_uint32(sl->cdb_cmd_blk + 3, reg);
+       sl->q_len = 2;
+       sl->q_addr = 0;
+       stlink_q(sl);
+       stlink_stat(sl, "write reg");
+}
+
+// Write a register of the debug module of the core.
+// XXX ?(atomic writes)
+// TODO test
+void stlink_write_dreg(struct stlink *sl, uint32_t reg, uint32_t addr) {
+       D(sl, "\n*** stlink_write_dreg ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_WRITEDEBUGREG;
+       // 2-5: address of reg of the debug module
+       // 6-9: reg content
+       write_uint32(sl->cdb_cmd_blk + 2, addr);
+       write_uint32(sl->cdb_cmd_blk + 6, reg);
+       sl->q_len = 2;
+       sl->q_addr = addr;
+       stlink_q(sl);
+       stlink_stat(sl, "write debug reg");
+}
+
+// Force the core exit the debug mode.
+void stlink_run(struct stlink *sl) {
+       D(sl, "\n*** stlink_run ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_RUNCORE;
+       sl->q_len = 2;
+       sl->q_addr = 0;
+       stlink_q(sl);
+       stlink_stat(sl, "run core");
+}
+
+// same as above with entrypoint.
+static unsigned int is_core_halted(struct stlink*);
+void stlink_run_at(struct stlink *sl, stm32_addr_t addr) {
+       stlink_write_reg(sl, addr, 15); /* pc register */
+
+       stlink_run(sl);
+
+       while (is_core_halted(sl) == 0)
+         usleep(3000000);
+}
+
+// Step the arm-core.
+void stlink_step(struct stlink *sl) {
+       D(sl, "\n*** stlink_step ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_STEPCORE;
+       sl->q_len = 2;
+       sl->q_addr = 0;
+       stlink_q(sl);
+       stlink_stat(sl, "step core");
+}
+
+// TODO test
+// see Cortex-M3 Technical Reference Manual
+void stlink_set_hw_bp(struct stlink *sl, int fp_nr, uint32_t addr, int fp) {
+       D(sl, "\n*** stlink_set_hw_bp ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_SETFP;
+       // 2:The number of the flash patch used to set the breakpoint
+       // 3-6: Address of the breakpoint (LSB)
+       // 7: FP_ALL (0x02) / FP_UPPER (0x01) / FP_LOWER (0x00)
+       sl->q_buf[2] = fp_nr;
+       write_uint32(sl->q_buf, addr);
+       sl->q_buf[7] = fp;
+
+       sl->q_len = 2;
+       stlink_q(sl);
+       stlink_stat(sl, "set flash breakpoint");
+}
+
+// TODO test
+void stlink_clr_hw_bp(struct stlink *sl, int fp_nr) {
+       D(sl, "\n*** stlink_clr_hw_bp ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_CLEARFP;
+       sl->cdb_cmd_blk[2] = fp_nr;
+
+       sl->q_len = 2;
+       stlink_q(sl);
+       stlink_stat(sl, "clear flash breakpoint");
+}
+
+// Read a "len" bytes to the sl->q_buf from the memory, max 6kB (6144 bytes)
+void stlink_read_mem32(struct stlink *sl, uint32_t addr, uint16_t len) {
+       D(sl, "\n*** stlink_read_mem32 ***\n");
+       if (len % 4 != 0) { // !!! never ever: fw gives just wrong values
+               fprintf(
+                       stderr,
+                       "Error: Data length doesn't have a 32 bit alignment: +%d byte.\n",
+                       len % 4);
+               return;
+       }
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_READMEM_32BIT;
+       // 2-5: addr
+       // 6-7: len
+       write_uint32(sl->cdb_cmd_blk + 2, addr);
+       write_uint16(sl->cdb_cmd_blk + 6, len);
+
+       // data_in 0-0x40-len
+       // !!! len _and_ q_len must be max 6k,
+       //     i.e. >1024 * 6 = 6144 -> aboard)
+       // !!! if len < q_len: 64*k, 1024*n, n=1..5  -> aboard
+       //     (broken residue issue)
+       sl->q_len = len;
+       sl->q_addr = addr;
+       stlink_q(sl);
+       stlink_print_data(sl);
+}
+
+// Write a "len" bytes from the sl->q_buf to the memory, max 64 Bytes.
+void stlink_write_mem8(struct stlink *sl, uint32_t addr, uint16_t len) {
+       D(sl, "\n*** stlink_write_mem8 ***\n");
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_WRITEMEM_8BIT;
+       // 2-5: addr
+       // 6-7: len (>0x40 (64) -> aboard)
+       write_uint32(sl->cdb_cmd_blk + 2, addr);
+       write_uint16(sl->cdb_cmd_blk + 6, len);
+
+       // data_out 0-len
+       sl->q_len = len;
+       sl->q_addr = addr;
+       sl->q_data_dir = Q_DATA_OUT;
+       stlink_q(sl);
+       stlink_print_data(sl);
+}
+
+// Write a "len" bytes from the sl->q_buf to the memory, max Q_BUF_LEN bytes.
+void stlink_write_mem32(struct stlink *sl, uint32_t addr, uint16_t len) {
+       D(sl, "\n*** stlink_write_mem32 ***\n");
+       if (len % 4 != 0) {
+               fprintf(
+                       stderr,
+                       "Error: Data length doesn't have a 32 bit alignment: +%d byte.\n",
+                       len % 4);
+               return;
+       }
+       clear_cdb(sl);
+       sl->cdb_cmd_blk[1] = STLINK_DEBUG_WRITEMEM_32BIT;
+       // 2-5: addr
+       // 6-7: len "unlimited"
+       write_uint32(sl->cdb_cmd_blk + 2, addr);
+       write_uint16(sl->cdb_cmd_blk + 6, len);
+
+       // data_out 0-0x40-...-len
+       sl->q_len = len;
+       sl->q_addr = addr;
+       sl->q_data_dir = Q_DATA_OUT;
+       stlink_q(sl);
+       stlink_print_data(sl);
+}
+
+/* FPEC flash controller interface, pm0063 manual
+ */
+
+#define FLASH_REGS_ADDR 0x40022000
+#define FLASH_REGS_SIZE 0x28
+
+#define FLASH_ACR (FLASH_REGS_ADDR + 0x00)
+#define FLASH_KEYR (FLASH_REGS_ADDR + 0x04)
+#define FLASH_SR (FLASH_REGS_ADDR + 0x0c)
+#define FLASH_CR (FLASH_REGS_ADDR + 0x10)
+#define FLASH_AR (FLASH_REGS_ADDR + 0x14)
+#define FLASH_OBR (FLASH_REGS_ADDR + 0x1c)
+#define FLASH_WRPR (FLASH_REGS_ADDR + 0x20)
+
+#define FLASH_RDPTR_KEY 0x00a5
+#define FLASH_KEY1 0x45670123
+#define FLASH_KEY2 0xcdef89ab
+
+#define FLASH_SR_BSY 0
+#define FLASH_SR_EOP 5
+
+#define FLASH_CR_PG 0
+#define FLASH_CR_PER 1
+#define FLASH_CR_MER 2
+#define FLASH_CR_STRT 6
+#define FLASH_CR_LOCK 7
+
+static uint32_t __attribute__((unused)) read_flash_rdp(struct stlink* sl)
+{
+  stlink_read_mem32(sl, FLASH_WRPR, sizeof(uint32_t));
+  return (*(uint32_t*)sl->q_buf) & 0xff;
+}
+
+static inline uint32_t read_flash_wrpr(struct stlink* sl)
+{
+  stlink_read_mem32(sl, FLASH_WRPR, sizeof(uint32_t));
+  return *(uint32_t*)sl->q_buf;
+}
+
+static inline uint32_t read_flash_obr(struct stlink* sl)
+{
+  stlink_read_mem32(sl, FLASH_OBR, sizeof(uint32_t));
+  return *(uint32_t*)sl->q_buf;
+}
+
+static inline uint32_t read_flash_cr(struct stlink* sl)
+{
+  stlink_read_mem32(sl, FLASH_CR, sizeof(uint32_t));
+  return *(uint32_t*)sl->q_buf;
+}
+
+static inline unsigned int is_flash_locked(struct stlink* sl)
+{
+  /* return non zero for true */
+  return read_flash_cr(sl) & (1 << FLASH_CR_LOCK);
+}
+
+static void unlock_flash(struct stlink* sl)
+{
+  /* the unlock sequence consists of 2 write cycles where
+     2 key values are written to the FLASH_KEYR register.
+     an invalid sequence results in a definitive lock of
+     the FPEC block until next reset.
+  */
+
+  write_uint32(sl->q_buf, FLASH_KEY1);
+  stlink_write_mem32(sl, FLASH_KEYR, sizeof(uint32_t));
+
+  write_uint32(sl->q_buf, FLASH_KEY2);
+  stlink_write_mem32(sl, FLASH_KEYR, sizeof(uint32_t));
+}
+
+static int unlock_flash_if(struct stlink* sl)
+{
+  /* unlock flash if already locked */
+
+  if (is_flash_locked(sl))
+  {
+    unlock_flash(sl);
+    if (is_flash_locked(sl))
+      return -1;
+  }
+
+  return 0;
+}
+
+static void lock_flash(struct stlink* sl)
+{
+  /* write to 1 only. reset by hw at unlock sequence */
+
+  const uint32_t n = read_flash_cr(sl) | (1 << FLASH_CR_LOCK);
+
+  write_uint32(sl->q_buf, n);
+  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
+}
+
+static void set_flash_cr_pg(struct stlink* sl)
+{
+  const uint32_t n = 1 << FLASH_CR_PG;
+  write_uint32(sl->q_buf, n);
+  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
+}
+
+static void __attribute__((unused)) clear_flash_cr_pg(struct stlink* sl)
+{
+  const uint32_t n = read_flash_cr(sl) & ~(1 << FLASH_CR_PG);
+  write_uint32(sl->q_buf, n);
+  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
+}
+
+static void set_flash_cr_per(struct stlink* sl)
+{
+  const uint32_t n = 1 << FLASH_CR_PER;
+  write_uint32(sl->q_buf, n);
+  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
+}
+
+static void __attribute__((unused)) clear_flash_cr_per(struct stlink* sl)
+{
+  const uint32_t n = read_flash_cr(sl) & ~(1 << FLASH_CR_PER);
+  write_uint32(sl->q_buf, n);
+  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
+}
+
+static void set_flash_cr_mer(struct stlink* sl)
+{
+  const uint32_t n = 1 << FLASH_CR_MER;
+  write_uint32(sl->q_buf, n);
+  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
+}
+
+static void __attribute__((unused)) clear_flash_cr_mer(struct stlink* sl)
+{
+  const uint32_t n = read_flash_cr(sl) & ~(1 << FLASH_CR_MER);
+  write_uint32(sl->q_buf, n);
+  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
+}
+
+static void set_flash_cr_strt(struct stlink* sl)
+{
+  /* assume come on the flash_cr_per path */
+  const uint32_t n = (1 << FLASH_CR_PER) | (1 << FLASH_CR_STRT);
+  write_uint32(sl->q_buf, n);
+  stlink_write_mem32(sl, FLASH_CR, sizeof(uint32_t));
+}
+
+static inline uint32_t read_flash_acr(struct stlink* sl)
+{
+  stlink_read_mem32(sl, FLASH_ACR, sizeof(uint32_t));
+  return *(uint32_t*)sl->q_buf;
+}
+
+static inline uint32_t read_flash_sr(struct stlink* sl)
+{
+  stlink_read_mem32(sl, FLASH_SR, sizeof(uint32_t));
+  return *(uint32_t*)sl->q_buf;
+}
+
+static inline unsigned int is_flash_busy(struct stlink* sl)
+{
+  return read_flash_sr(sl) & (1 << FLASH_SR_BSY);
+}
+
+static void wait_flash_busy(struct stlink* sl)
+{
+  /* todo: add some delays here */
+  while (is_flash_busy(sl))
+    ;
+}
+
+static inline unsigned int is_flash_eop(struct stlink* sl)
+{
+  return read_flash_sr(sl) & (1 << FLASH_SR_EOP);
+}
+
+static void __attribute__((unused)) clear_flash_sr_eop(struct stlink* sl)
+{
+  const uint32_t n = read_flash_sr(sl) & ~(1 << FLASH_SR_EOP);
+  write_uint32(sl->q_buf, n);
+  stlink_write_mem32(sl, FLASH_SR, sizeof(uint32_t));
+}
+
+static void __attribute__((unused)) wait_flash_eop(struct stlink* sl)
+{
+  /* todo: add some delays here */
+  while (is_flash_eop(sl) == 0)
+    ;
+}
+
+static inline void write_flash_ar(struct stlink* sl, uint32_t n)
+{
+  write_uint32(sl->q_buf, n);
+  stlink_write_mem32(sl, FLASH_AR, sizeof(uint32_t));
+}
+
+#if 0 /* todo */
+static void disable_flash_read_protection(struct stlink* sl)
+{
+  /* erase the option byte area */
+  /* rdp = 0x00a5; */
+  /* reset */
+}
+#endif /* todo */
+
+#if 0 /* not working */
+static int write_flash_mem16
+(struct stlink* sl, uint32_t addr, uint16_t val)
+{
+  /* half word writes */
+  if (addr % 2) return -1;
+
+  /* unlock if locked */
+  unlock_flash_if(sl);
+
+  /* set flash programming chosen bit */
+  set_flash_cr_pg(sl);
+
+  write_uint16(sl->q_buf, val);
+  stlink_write_mem16(sl, addr, 2);
+
+  /* wait for non business */
+  wait_flash_busy(sl);
+
+  lock_flash(sl);
+
+  /* check the programmed value back */
+  stlink_read_mem16(sl, addr, 2);
+  if (*(const uint16_t*)sl->q_buf != val)
+  {
+    /* values differ at i * sizeof(uint16_t) */
+    return -1;
+  }
+
+  /* success */
+  return 0;
+}
+#endif /* not working */
+
+static int erase_flash_page(struct stlink* sl, stm32_addr_t page)
+{
+  /* page an addr in the page to erase */
+
+  /* wait for ongoing op to finish */
+  wait_flash_busy(sl);
+
+  /* unlock if locked */
+  unlock_flash_if(sl);
+
+  /* set the page erase bit */
+  set_flash_cr_per(sl);
+
+  /* select the page to erase */
+  write_flash_ar(sl, page);
+
+  /* start erase operation, reset by hw with bsy bit */
+  set_flash_cr_strt(sl);
+
+  /* wait for completion */
+  wait_flash_busy(sl);
+
+  /* relock the flash */
+  lock_flash(sl);
+
+  /* todo: verify the erased page */
+
+  return 0;
+}
+
+static int __attribute__((unused)) erase_flash_mass(struct stlink* sl)
+{
+  /* wait for ongoing op to finish */
+  wait_flash_busy(sl);
+
+  /* unlock if locked */
+  unlock_flash_if(sl);
+
+  /* set the mass erase bit */
+  set_flash_cr_mer(sl);
+
+  /* start erase operation, reset by hw with bsy bit */
+  set_flash_cr_strt(sl);
+
+  /* wait for completion */
+  wait_flash_busy(sl);
+
+  /* relock the flash */
+  lock_flash(sl);
+
+  /* todo: verify the erased memory */
+
+  return 0;
+}
+
+static unsigned int is_core_halted(struct stlink* sl)
+{
+  /* return non zero if core is halted */
+  stlink_status(sl);
+  return sl->q_buf[0] == STLINK_CORE_HALTED;
+}
+
+static int write_loader_to_sram
+(struct stlink* sl, stm32_addr_t* addr, size_t* size)
+{
+  /* from openocd, contrib/loaders/flash/stm32.s */
+  static const uint8_t loader_code[] =
+  {
+    0x08, 0x4c,                        /* ldr  r4, STM32_FLASH_BASE */
+    0x1c, 0x44,                        /* add  r4, r3 */
+    /* write_half_word: */
+    0x01, 0x23,                        /* movs r3, #0x01 */
+    0x23, 0x61,                        /* str  r3, [r4, #STM32_FLASH_CR_OFFSET] */
+    0x30, 0xf8, 0x02, 0x3b,    /* ldrh r3, [r0], #0x02 */
+    0x21, 0xf8, 0x02, 0x3b,    /* strh r3, [r1], #0x02 */
+    /* busy: */
+    0xe3, 0x68,                        /* ldr  r3, [r4, #STM32_FLASH_SR_OFFSET] */
+    0x13, 0xf0, 0x01, 0x0f,    /* tst  r3, #0x01 */
+    0xfb, 0xd0,                        /* beq  busy */
+    0x13, 0xf0, 0x14, 0x0f,    /* tst  r3, #0x14 */
+    0x01, 0xd1,                        /* bne  exit */
+    0x01, 0x3a,                        /* subs r2, r2, #0x01 */
+    0xf0, 0xd1,                        /* bne  write_half_word */
+    /* exit: */
+    0x00, 0xbe,                        /* bkpt #0x00 */
+    0x00, 0x20, 0x02, 0x40,    /* STM32_FLASH_BASE: .word 0x40022000 */
+  };
+
+  memcpy(sl->q_buf, loader_code, sizeof(loader_code));
+  stlink_write_mem32(sl, sl->sram_base, sizeof(loader_code));
+
+  *addr = sl->sram_base;
+  *size = sizeof(loader_code);
+
+  /* success */
+  return 0;
+}
+
+typedef struct flash_loader
+{
+  stm32_addr_t loader_addr; /* loader sram adddr */
+  stm32_addr_t buf_addr; /* buffer sram address */
+} flash_loader_t;
+
+static int write_buffer_to_sram
+(struct stlink* sl, flash_loader_t* fl, const uint8_t* buf, size_t size)
+{
+  /* write the buffer right after the loader */
+  memcpy(sl->q_buf, buf, size);
+  stlink_write_mem8(sl, fl->buf_addr, size);
+  return 0;
+}
+
+static int init_flash_loader
+(struct stlink* sl, flash_loader_t* fl)
+{
+  size_t size;
+
+  /* allocate the loader in sram */
+  if (write_loader_to_sram(sl, &fl->loader_addr, &size) == -1)
+  {
+    fprintf(stderr, "write_loader_to_sram() == -1\n");
+    return -1;
+  }
+
+  /* allocate a one page buffer in sram right after loader */
+  fl->buf_addr = fl->loader_addr + size;
+
+  return 0;
+}
+
+static int run_flash_loader
+(struct stlink* sl, flash_loader_t* fl, stm32_addr_t target, const uint8_t* buf, size_t size)
+{
+  const size_t count = size / sizeof(uint16_t);
+
+  if (write_buffer_to_sram(sl, fl, buf, size) == -1)
+  {
+    fprintf(stderr, "write_buffer_to_sram() == -1\n");
+    return -1;
+  }
+
+  /* setup core */
+  stlink_write_reg(sl, fl->buf_addr, 0); /* source */
+  stlink_write_reg(sl, target, 1); /* target */
+  stlink_write_reg(sl, count, 2); /* count (16 bits half words) */
+  stlink_write_reg(sl, 0, 3); /* flash bank 0 (input) */
+  stlink_write_reg(sl, fl->loader_addr, 15); /* pc register */
+
+  /* unlock and set programming mode */
+  unlock_flash_if(sl);
+  set_flash_cr_pg(sl);
+
+  /* run loader */
+  stlink_run(sl);
+
+  while (is_core_halted(sl) == 0)
+    ;
+
+  lock_flash(sl);
+
+  /* not all bytes have been written */
+  stlink_read_reg(sl, 2);
+  if (sl->reg.r[2] != 0)
+  {
+    fprintf(stderr, "write error, count == %u\n", sl->reg.r[2]);
+    return -1;
+  }
+
+  return 0;
+}
+
+/* memory mapped file */
+
+typedef struct mapped_file
+{
+  uint8_t* base;
+  size_t len;
+} mapped_file_t;
+
+#define MAPPED_FILE_INITIALIZER { NULL, 0 }
+
+static int map_file(mapped_file_t* mf, const char* path)
+{
+  int error = -1;
+  struct stat st;
+
+  const int fd = open(path, O_RDONLY);
+  if (fd == -1)
+  {
+    fprintf(stderr, "open(%s) == -1\n", path);
+    return -1;
+  }
+
+  if (fstat(fd, &st) == -1)
+  {
+    fprintf(stderr, "fstat() == -1\n");
+    goto on_error;
+  }
+
+  mf->base = (uint8_t*)mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+  if (mf->base == MAP_FAILED)
+  {
+    fprintf(stderr, "mmap() == MAP_FAILED\n");
+    goto on_error;
+  }
+
+  mf->len = st.st_size;
+
+  /* success */
+  error = 0;
+
+ on_error:
+  close(fd);
+
+  return error;
+}
+
+static void unmap_file(mapped_file_t* mf)
+{
+  munmap((void*)mf->base, mf->len);
+  mf->base = (unsigned char*)MAP_FAILED;
+  mf->len = 0;
+}
+
+static int check_file
+(struct stlink* sl, mapped_file_t* mf, stm32_addr_t addr)
+{
+  size_t off;
+
+  for (off = 0; off < mf->len; off += sl->flash_pgsz)
+  {
+    size_t aligned_size;
+
+    /* adjust last page size */
+    size_t cmp_size = sl->flash_pgsz;
+    if ((off + sl->flash_pgsz) > mf->len)
+      cmp_size = mf->len - off;
+
+    aligned_size = cmp_size;
+    if (aligned_size & (4 - 1))
+      aligned_size = (cmp_size + 4) & ~(4 - 1);
+
+    stlink_read_mem32(sl, addr + off, aligned_size);
+
+    if (memcmp(sl->q_buf, mf->base + off, cmp_size))
+      return -1;
+  }
+
+  return 0;
+}
+
+static int stlink_fcheck_flash
+(struct stlink* sl, const char* path, stm32_addr_t addr)
+{
+  /* check the contents of path are at addr */
+
+  int res;
+  mapped_file_t mf = MAPPED_FILE_INITIALIZER;
+
+  if (map_file(&mf, path) == -1)
+    return -1;
+
+  res = check_file(sl, &mf, addr);
+
+  unmap_file(&mf);
+
+  return res;
+}
+
+static int stlink_fwrite_flash
+(struct stlink* sl, const char* path, stm32_addr_t addr)
+{
+  /* write the file in flash at addr */
+
+  int error = -1;
+  size_t off;
+  mapped_file_t mf = MAPPED_FILE_INITIALIZER;
+  flash_loader_t fl;
+
+  if (map_file(&mf, path) == -1)
+  {
+    fprintf(stderr, "map_file() == -1\n");
+    return -1;
+  }
+
+  /* check addr range is inside the flash */
+  if (addr < sl->flash_base)
+  {
+    fprintf(stderr, "addr too low\n");
+    goto on_error;
+  }
+  else if ((addr + mf.len) < addr)
+  {
+    fprintf(stderr, "addr overruns\n");
+    goto on_error;
+  }
+  else if ((addr + mf.len) > (sl->flash_base + sl->flash_size))
+  {
+    fprintf(stderr, "addr too high\n");
+    goto on_error;
+  }
+  else if ((addr & 1) || (mf.len & 1))
+  {
+    /* todo */
+    fprintf(stderr, "unaligned addr or size\n");
+    goto on_error;
+  }
+
+  /* erase each page. todo: mass erase faster? */
+  for (off = 0; off < mf.len; off += sl->flash_pgsz)
+  {
+    /* addr must be an addr inside the page */
+    if (erase_flash_page(sl, addr + off) == -1)
+    {
+      fprintf(stderr, "erase_flash_page(0x%x) == -1\n", addr + off);
+      goto on_error;
+    }
+  }
+
+  /* flash loader initialization */
+  if (init_flash_loader(sl, &fl) == -1)
+  {
+    fprintf(stderr, "init_flash_loader() == -1\n");
+    goto on_error;
+  }
+
+  /* write each page. above WRITE_BLOCK_SIZE fails? */
+#define WRITE_BLOCK_SIZE 0x40
+  for (off = 0; off < mf.len; off += WRITE_BLOCK_SIZE)
+  {
+    /* adjust last write size */
+    size_t size = WRITE_BLOCK_SIZE;
+    if ((off + WRITE_BLOCK_SIZE) > mf.len)
+      size = mf.len - off;
+
+    if (run_flash_loader(sl, &fl, addr + off, mf.base + off, size) == -1)
+    {
+      fprintf(stderr, "run_flash_loader(0x%x) == -1\n", addr + off);
+      goto on_error;
+    }
+  }
+
+  /* check the file ha been written */
+  if (check_file(sl, &mf, addr) == -1)
+  {
+    fprintf(stderr, "check_file() == -1\n");
+    goto on_error;
+  }
+
+  /* success */
+  error = 0;
+
+ on_error:
+  unmap_file(&mf);
+  return error;
+}
+
+static int stlink_fwrite_sram
+(struct stlink* sl, const char* path, stm32_addr_t addr)
+{
+  /* write the file in sram at addr */
+
+  int error = -1;
+  size_t off;
+  mapped_file_t mf = MAPPED_FILE_INITIALIZER;
+
+  if (map_file(&mf, path) == -1)
+  {
+    fprintf(stderr, "map_file() == -1\n");
+    return -1;
+  }
+
+  /* check addr range is inside the sram */
+  if (addr < sl->sram_base)
+  {
+    fprintf(stderr, "addr too low\n");
+    goto on_error;
+  }
+  else if ((addr + mf.len) < addr)
+  {
+    fprintf(stderr, "addr overruns\n");
+    goto on_error;
+  }
+  else if ((addr + mf.len) > (sl->sram_base + sl->sram_size))
+  {
+    fprintf(stderr, "addr too high\n");
+    goto on_error;
+  }
+  else if ((addr & 3) || (mf.len & 3))
+  {
+    /* todo */
+    fprintf(stderr, "unaligned addr or size\n");
+    goto on_error;
+  }
+
+  /* do the copy by 1k blocks */
+  for (off = 0; off < mf.len; off += 1024)
+  {
+    size_t size = 1024;
+    if ((off + size) > mf.len)
+      size = mf.len - off;
+
+    memcpy(sl->q_buf, mf.base + off, size);
+
+    /* round size if needed */
+    if (size & 3)
+      size += 2;
+
+    stlink_write_mem32(sl, addr + off, size);
+  }
+
+  /* check the file ha been written */
+  if (check_file(sl, &mf, addr) == -1)
+  {
+    fprintf(stderr, "check_file() == -1\n");
+    goto on_error;
+  }
+
+  /* success */
+  error = 0;
+
+ on_error:
+  unmap_file(&mf);
+  return error;
+}
+
+
+static int stlink_fread
+(struct stlink* sl, const char* path, stm32_addr_t addr, size_t size)
+{
+  /* read size bytes from addr to file */
+
+  int error = -1;
+  size_t off;
+
+  const int fd = open(path, O_RDWR | O_TRUNC | O_CREAT, 00700);
+  if (fd == -1)
+  {
+    fprintf(stderr, "open(%s) == -1\n", path);
+    return -1;
+  }
+
+  /* do the copy by 1k blocks */
+  for (off = 0; off < size; off += 1024)
+  {
+    size_t read_size = 1024;
+    if ((off + read_size) > size)
+      read_size = off + read_size;
+
+    /* round size if needed */
+    if (read_size & 3)
+      read_size = (read_size + 4) & ~(3);
+
+    stlink_read_mem32(sl, addr + off, read_size);
+
+    if (write(fd, sl->q_buf, read_size) != (ssize_t)read_size)
+    {
+      fprintf(stderr, "write() != read_size\n");
+      goto on_error;
+    }
+  }
+
+  /* success */
+  error = 0;
+
+ on_error:
+  close(fd);
+
+  return error;
+}
+
+// 1) open a sg device, switch the stlink from dfu to mass mode
+// 2) wait 5s until the kernel driver stops reseting the broken device
+// 3) reopen the device
+// 4) the device driver is now ready for a switch to jtag/swd mode
+// TODO thinking, better error handling, wait until the kernel driver stops reseting the plugged-in device
+struct stlink* stlink_quirk_open(const char *dev_name, const int verbose) {
+       struct stlink *sl = stlink_open(dev_name, verbose);
+       if (sl == NULL) {
+               fputs("Error: could not open stlink device\n", stderr);
+               return NULL;
+       }
+
+       stlink_version(sl);
+
+       if (sl->st_vid != USB_ST_VID || sl->stlink_pid != USB_STLINK_PID) {
+               fprintf(stderr, "Error: the device %s is not a stlink\n",
+                       dev_name);
+               fprintf(stderr, "       VID: got %04x expect %04x \n",
+                       sl->st_vid, USB_ST_VID);
+               fprintf(stderr, "       PID: got %04x expect %04x \n",
+                       sl->stlink_pid, USB_STLINK_PID);
+               return NULL;
+       }
+
+       D(sl, "\n*** stlink_force_open ***\n");
+       switch (stlink_current_mode(sl)) {
+       case STLINK_DEV_MASS_MODE:
+               return sl;
+       case STLINK_DEV_DEBUG_MODE:
+               // TODO go to mass?
+               return sl;
+       }
+       DD(sl, "\n*** switch the stlink to mass mode ***\n");
+       stlink_exit_dfu_mode(sl);
+       // exit the dfu mode -> the device is gone
+       DD(sl, "\n*** reopen the stlink device ***\n");
+       delay(1000);
+       stlink_close(sl);
+       delay(5000);
+
+       sl = stlink_open(dev_name, verbose);
+       if (sl == NULL) {
+               fputs("Error: could not open stlink device\n", stderr);
+               return NULL;
+       }
+       // re-query device info
+       stlink_version(sl);
+       return sl;
+}
+
+static void __attribute__((unused)) mark_buf(struct stlink *sl) {
+       clear_buf(sl);
+       sl->q_buf[0] = 0x12;
+       sl->q_buf[1] = 0x34;
+       sl->q_buf[2] = 0x56;
+       sl->q_buf[3] = 0x78;
+       sl->q_buf[4] = 0x90;
+       sl->q_buf[15] = 0x42;
+       sl->q_buf[16] = 0x43;
+       sl->q_buf[63] = 0x42;
+       sl->q_buf[64] = 0x43;
+       sl->q_buf[1024 * 6 - 1] = 0x42; //6kB
+       sl->q_buf[1024 * 8 - 1] = 0x42; //8kB
+}
+
+#if 0
+int main(int argc, char *argv[]) {
+       // set scpi lib debug level: 0 for no debug info, 10 for lots
+       const int scsi_verbose = 2;
+       char *dev_name;
+
+       switch (argc) {
+       case 1:
+               fputs(
+                       "\nUsage: stlink-access-test /dev/sg0, sg1, ...\n"
+                               "\n*** Notice: The stlink firmware violates the USB standard.\n"
+                               "*** If you plug-in the discovery's stlink, wait a several\n"
+                               "*** minutes to let the kernel driver swallow the broken device.\n"
+                               "*** Watch:\ntail -f /var/log/messages\n"
+                               "*** This command sequence can shorten the waiting time and fix some issues.\n"
+                               "*** Unplug the stlink and execute once as root:\n"
+                               "modprobe -r usb-storage && modprobe usb-storage quirks=483:3744:lrwsro\n\n",
+                       stderr);
+               return EXIT_FAILURE;
+       case 2:
+               dev_name = argv[1];
+               break;
+       default:
+               return EXIT_FAILURE;
+       }
+
+       fputs("*** stlink access test ***\n", stderr);
+       DD(sl, "Using sg_lib %s : scsi_pt %s\n", sg_lib_version(),
+               scsi_pt_version());
+
+       struct stlink *sl = stlink_force_open(dev_name, scsi_verbose);
+       if (sl == NULL)
+               return EXIT_FAILURE;
+
+       // we are in mass mode, go to swd
+       stlink_enter_swd_mode(sl);
+       stlink_current_mode(sl);
+       stlink_core_id(sl);
+       //----------------------------------------------------------------------
+
+       stlink_status(sl);
+       //stlink_force_debug(sl);
+       stlink_reset(sl);
+       stlink_status(sl);
+#if 0
+       // core system control block
+       stlink_read_mem32(sl, 0xe000ed00, 4);
+       DD(sl, "cpu id base register: SCB_CPUID = got 0x%08x expect 0x411fc231", read_uint32(sl->q_buf, 0));
+       // no MPU
+       stlink_read_mem32(sl, 0xe000ed90, 4);
+       DD(sl, "mpu type register: MPU_TYPER = got 0x%08x expect 0x0", read_uint32(sl->q_buf, 0));
+
+       stlink_read_mem32(sl, 0xe000edf0, 4);
+       DD(sl, "DHCSR = 0x%08x", read_uint32(sl->q_buf, 0));
+
+       stlink_read_mem32(sl, 0x4001100c, 4);
+       DD(sl, "GPIOC_ODR = 0x%08x", read_uint32(sl->q_buf, 0));
+#endif
+#if 0
+       // happy new year 2011: let blink all the leds
+       // see "RM0041 Reference manual - STM32F100xx advanced ARM-based 32-bit MCUs"
+
+#define GPIOC          0x40011000 // port C
+#define GPIOC_CRH      (GPIOC + 0x04) // port configuration register high
+#define GPIOC_ODR      (GPIOC + 0x0c) // port output data register
+#define LED_BLUE       (1<<8) // pin 8
+#define LED_GREEN      (1<<9) // pin 9
+       stlink_read_mem32(sl, GPIOC_CRH, 4);
+       uint32_t io_conf = read_uint32(sl->q_buf, 0);
+       DD(sl, "GPIOC_CRH = 0x%08x", io_conf);
+
+       // set: general purpose output push-pull, output mode, max speed 10 MHz.
+       write_uint32(sl->q_buf, 0x44444411);
+       stlink_write_mem32(sl, GPIOC_CRH, 4);
+
+       clear_buf(sl);
+       for (int i = 0; i < 100; i++) {
+               write_uint32(sl->q_buf, LED_BLUE | LED_GREEN);
+               stlink_write_mem32(sl, GPIOC_ODR, 4);
+               /* stlink_read_mem32(sl, 0x4001100c, 4); */
+               /* DD(sl, "GPIOC_ODR = 0x%08x", read_uint32(sl->q_buf, 0)); */
+               delay(100);
+
+               clear_buf(sl);
+               stlink_write_mem32(sl, GPIOC_ODR, 4); // PC lo
+               delay(100);
+       }
+       write_uint32(sl->q_buf, io_conf); // set old state
+
+#endif
+#if 0
+       // TODO rtfm: stlink doesn't have flash write routines
+       // writing to the flash area confuses the fw for the next read access
+
+       //stlink_read_mem32(sl, 0, 1024*6);
+       // flash 0x08000000 128kB
+       fputs("++++++++++ read a flash at 0x0800 0000\n", stderr);
+       stlink_read_mem32(sl, 0x08000000, 1024 * 6); //max 6kB
+       clear_buf(sl);
+       stlink_read_mem32(sl, 0x08000c00, 5);
+       stlink_read_mem32(sl, 0x08000c00, 4);
+       mark_buf(sl);
+       stlink_write_mem32(sl, 0x08000c00, 4);
+       stlink_read_mem32(sl, 0x08000c00, 256);
+       stlink_read_mem32(sl, 0x08000c00, 256);
+#endif
+#if 0
+       // sram 0x20000000 8kB
+       fputs("\n++++++++++ read/write 8bit, sram at 0x2000 0000 ++++++++++++++++\n\n", stderr);
+       clear_buf(sl);
+       stlink_write_mem8(sl, 0x20000000, 16);
+
+       mark_buf(sl);
+       stlink_write_mem8(sl, 0x20000000, 1);
+       stlink_write_mem8(sl, 0x20000001, 1);
+       stlink_write_mem8(sl, 0x2000000b, 3);
+       stlink_read_mem32(sl, 0x20000000, 16);
+#endif
+#if 0
+       // a not aligned mem32 access doesn't work indeed
+       fputs("\n++++++++++ read/write 32bit, sram at 0x2000 0000 ++++++++++++++++\n\n", stderr);
+       clear_buf(sl);
+       stlink_write_mem8(sl, 0x20000000, 32);
+
+       mark_buf(sl);
+       stlink_write_mem32(sl, 0x20000000, 1);
+       stlink_read_mem32(sl, 0x20000000, 16);
+       mark_buf(sl);
+       stlink_write_mem32(sl, 0x20000001, 1);
+       stlink_read_mem32(sl, 0x20000000, 16);
+       mark_buf(sl);
+       stlink_write_mem32(sl, 0x2000000b, 3);
+       stlink_read_mem32(sl, 0x20000000, 16);
+
+       mark_buf(sl);
+       stlink_write_mem32(sl, 0x20000000, 17);
+       stlink_read_mem32(sl, 0x20000000, 32);
+#endif
+#if 0
+       // sram 0x20000000 8kB
+       fputs("++++++++++ read/write 32bit, sram at 0x2000 0000 ++++++++++++\n", stderr);
+       mark_buf(sl);
+       stlink_write_mem8(sl, 0x20000000, 64);
+       stlink_read_mem32(sl, 0x20000000, 64);
+
+       mark_buf(sl);
+       stlink_write_mem32(sl, 0x20000000, 1024 * 8); //8kB
+       stlink_read_mem32(sl, 0x20000000, 1024 * 6);
+       stlink_read_mem32(sl, 0x20000000 + 1024 * 6, 1024 * 2);
+#endif
+#if 0
+       stlink_read_all_regs(sl);
+       stlink_step(sl);
+       fputs("++++++++++ write r0 = 0x12345678\n", stderr);
+       stlink_write_reg(sl, 0x12345678, 0);
+       stlink_read_reg(sl, 0);
+       stlink_read_all_regs(sl);
+#endif
+#if 0
+       stlink_run(sl);
+       stlink_status(sl);
+
+       stlink_force_debug(sl);
+       stlink_status(sl);
+#endif
+#if 1 /* read the system bootloader */
+       fputs("\n++++++++++ reading bootloader ++++++++++++++++\n\n", stderr);
+       stlink_fread(sl, "/tmp/barfoo", sl->sys_base, sl->sys_size);
+#endif
+#if 0 /* read the flash memory */
+       fputs("\n+++++++ read flash memory\n\n", stderr);
+       /* mark_buf(sl); */
+       stlink_read_mem32(sl, 0x08000000, 4);
+#endif
+#if 0 /* flash programming */
+       fputs("\n+++++++ program flash memory\n\n", stderr);
+       stlink_fwrite_flash(sl, "/tmp/foobar", 0x08000000);
+#endif
+#if 0 /* check file contents */
+       fputs("\n+++++++ check flash memory\n\n", stderr);
+       {
+         const int res = stlink_fcheck_flash(sl, "/tmp/foobar", 0x08000000);
+         printf("_____ stlink_fcheck_flash() == %d\n", res);
+       }
+#endif
+#if 0
+       fputs("\n+++++++ sram write and execute\n\n", stderr);
+       stlink_fwrite_sram(sl, "/tmp/foobar", sl->sram_base);
+       stlink_run_at(sl, sl->sram_base);
+#endif
+
+       stlink_run(sl);
+       stlink_status(sl);
+       //----------------------------------------------------------------------
+       // back to mass mode, just in case ...
+       stlink_exit_debug_mode(sl);
+       stlink_current_mode(sl);
+       stlink_close(sl);
+
+       //fflush(stderr); fflush(stdout);
+       return EXIT_SUCCESS;
+}
+#endif
diff --git a/src/stlink-hw.h b/src/stlink-hw.h
new file mode 100644 (file)
index 0000000..9ce085b
--- /dev/null
@@ -0,0 +1,158 @@
+#ifndef _STLINK_HW_H_
+#define _STLINK_HW_H_
+
+#include <stdint.h>
+
+// device access
+#define RDWR           0
+#define RO             1
+#define SG_TIMEOUT_SEC 1 // actually 1 is about 2 sec
+// Each CDB can be a total of 6, 10, 12, or 16 bytes, later version
+// of the SCSI standard also allow for variable-length CDBs (min. CDB is 6).
+// the stlink needs max. 10 bytes.
+#define CDB_6          6
+#define CDB_10         10
+#define CDB_12         12
+#define CDB_16         16
+
+#define CDB_SL         10
+
+// Query data flow direction.
+#define Q_DATA_OUT     0
+#define Q_DATA_IN      1
+
+// The SCSI Request Sense command is used to obtain sense data
+// (error information) from a target device.
+// http://en.wikipedia.org/wiki/SCSI_Request_Sense_Command
+#define SENSE_BUF_LEN          32
+
+// Max data transfer size.
+// 6kB = max mem32_read block, 8kB sram
+//#define Q_BUF_LEN    96
+#define Q_BUF_LEN      1024 * 100
+
+// st-link vendor cmd's
+#define USB_ST_VID                     0x0483
+#define USB_STLINK_PID                 0x3744
+
+// STLINK_DEBUG_RESETSYS, etc:
+#define STLINK_OK                      0x80
+#define STLINK_FALSE                   0x81
+#define STLINK_CORE_RUNNING            0x80
+#define STLINK_CORE_HALTED             0x81
+#define STLINK_CORE_STAT_UNKNOWN       -1
+
+#define STLINK_GET_VERSION             0xf1
+#define STLINK_GET_CURRENT_MODE        0xf5
+
+#define STLINK_DEBUG_COMMAND           0xF2
+#define STLINK_DFU_COMMAND             0xF3
+#define STLINK_DFU_EXIT                0x07
+
+// STLINK_GET_CURRENT_MODE
+#define STLINK_DEV_DFU_MODE            0x00
+#define STLINK_DEV_MASS_MODE           0x01
+#define STLINK_DEV_DEBUG_MODE          0x02
+#define STLINK_DEV_UNKNOWN_MODE        -1
+
+// jtag mode cmds
+#define STLINK_DEBUG_ENTER             0x20
+#define STLINK_DEBUG_EXIT              0x21
+#define STLINK_DEBUG_READCOREID        0x22
+#define STLINK_DEBUG_GETSTATUS         0x01
+#define STLINK_DEBUG_FORCEDEBUG        0x02
+#define STLINK_DEBUG_RESETSYS          0x03
+#define STLINK_DEBUG_READALLREGS       0x04
+#define STLINK_DEBUG_READREG           0x05
+#define STLINK_DEBUG_WRITEREG          0x06
+#define STLINK_DEBUG_READMEM_32BIT     0x07
+#define STLINK_DEBUG_WRITEMEM_32BIT    0x08
+#define STLINK_DEBUG_RUNCORE           0x09
+#define STLINK_DEBUG_STEPCORE          0x0a
+#define STLINK_DEBUG_SETFP             0x0b
+#define STLINK_DEBUG_WRITEMEM_8BIT     0x0d
+#define STLINK_DEBUG_CLEARFP           0x0e
+#define STLINK_DEBUG_WRITEDEBUGREG     0x0f
+#define STLINK_DEBUG_ENTER_SWD         0xa3
+#define STLINK_DEBUG_ENTER_JTAG        0x00
+
+typedef struct {
+       uint32_t r[16];
+       uint32_t xpsr;
+       uint32_t main_sp;
+       uint32_t process_sp;
+       uint32_t rw;
+       uint32_t rw2;
+} reg;
+
+typedef uint32_t stm32_addr_t;
+
+struct stlink {
+       int sg_fd;
+       int do_scsi_pt_err;
+       // sg layer verboseness: 0 for no debug info, 10 for lots
+       int verbose;
+
+       unsigned char cdb_cmd_blk[CDB_SL];
+
+       // Data transferred from or to device
+       unsigned char q_buf[Q_BUF_LEN];
+       int q_len;
+       int q_data_dir; // Q_DATA_IN, Q_DATA_OUT
+       // the start of the query data in the device memory space
+       uint32_t q_addr;
+
+       // Sense (error information) data
+       unsigned char sense_buf[SENSE_BUF_LEN];
+
+       uint32_t st_vid;
+       uint32_t stlink_pid;
+       uint32_t stlink_v;
+       uint32_t jtag_v;
+       uint32_t swim_v;
+       uint32_t core_id;
+
+       reg reg;
+       int core_stat;
+
+       /* medium density stm32 flash settings */
+#define STM32_FLASH_BASE 0x08000000
+#define STM32_FLASH_SIZE (128 * 1024)
+#define STM32_FLASH_PGSZ 1024
+       stm32_addr_t flash_base;
+       size_t flash_size;
+       size_t flash_pgsz;
+
+       /* in flash system memory */
+#define STM32_SYSTEM_BASE 0x1ffff000
+#define STM32_SYSTEM_SIZE (2 * 1024)
+       stm32_addr_t sys_base;
+       size_t sys_size;
+
+       /* sram settings */
+#define STM32_SRAM_BASE 0x20000000
+#define STM32_SRAM_SIZE (8 * 1024)
+       stm32_addr_t sram_base;
+       size_t sram_size;
+};
+
+struct stlink* stlink_quirk_open(const char *dev_name, const int verbose);
+int stlink_current_mode(struct stlink *sl);
+void stlink_enter_swd_mode(struct stlink *sl);
+void stlink_enter_jtag_mode(struct stlink *sl);
+void stlink_exit_debug_mode(struct stlink *sl);
+void stlink_core_id(struct stlink *sl);
+void stlink_status(struct stlink *sl);
+void stlink_force_debug(struct stlink *sl);
+void stlink_reset(struct stlink *sl);
+void stlink_run(struct stlink *sl);
+void stlink_step(struct stlink *sl);
+void stlink_read_all_regs(struct stlink *sl);
+void stlink_read_reg(struct stlink *sl, int r_idx);
+void stlink_write_reg(struct stlink *sl, uint32_t reg, int idx);
+void stlink_read_mem32(struct stlink *sl, uint32_t addr, uint16_t len);
+void stlink_write_mem8(struct stlink *sl, uint32_t addr, uint16_t len);
+void stlink_write_mem32(struct stlink *sl, uint32_t addr, uint16_t len);
+void stlink_close(struct stlink *sl);
+
+#endif