diff --git a/Makefile b/Makefile index f33abcb..fe77854 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ ifndef DESTDIR endif BIN =stm8flash -OBJECTS =stlink.o stlinkv2.o espstlink.o main.o byte_utils.o ihex.o srec.o stm8.o libespstlink.o +OBJECTS =stlink.o stlinkv2.o espstlink.o rp2040swim.o main.o byte_utils.o ihex.o srec.o stm8.o libespstlink.o librp2040swim.o .PHONY: all clean install diff --git a/librp2040swim.c b/librp2040swim.c new file mode 100644 index 0000000..8071f93 --- /dev/null +++ b/librp2040swim.c @@ -0,0 +1,349 @@ +/** + * Low-level serial transport for ARDev1161/rp2040_swim programmer. + */ +#include "librp2040swim.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RPSW_MAGIC 0x53575052u +#define RPSW_VERSION 1u +#define RPSW_MAX_PAYLOAD 1024u + +#define RPSW_CMD_GET_VERSION 0x01u +#define RPSW_CMD_SET_PINS 0x02u +#define RPSW_CMD_SET_SPEED 0x03u +#define RPSW_CMD_ENTER_SWIM 0x04u +#define RPSW_CMD_RESET_TARGET 0x05u +#define RPSW_CMD_MEMORY_READ 0x08u +#define RPSW_CMD_MEMORY_WRITE 0x09u +#define RPSW_CMD_FLASH_ERASE 0x0au +#define RPSW_CMD_FLASH_WRITE_BLOCK 0x0bu +#define RPSW_CMD_GET_LAST_ERROR 0x0du +#define RPSW_CMD_RELEASE_TARGET 0x12u + +#define RPSW_STATUS_OK 0u + +static rp2040swim_error_t error; + +rp2040swim_error_t *rp2040swim_get_last_error(void) { + return &error; +} + +static void set_error(int code, int device_status, const char *fmt, ...) { + va_list ap; + memset(&error, 0, sizeof(error)); + error.code = code; + error.device_status = device_status; + va_start(ap, fmt); + vsnprintf(error.message, sizeof(error.message), fmt, ap); + va_end(ap); + fprintf(stderr, "%s\n", error.message); +} + +static uint16_t get_u16le(const uint8_t *p) { + return (uint16_t)p[0] | ((uint16_t)p[1] << 8); +} + +static uint32_t get_u32le(const uint8_t *p) { + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void put_u16le(uint8_t *p, uint16_t v) { + p[0] = (uint8_t)(v & 0xffu); + p[1] = (uint8_t)((v >> 8) & 0xffu); +} + +static void put_u32le(uint8_t *p, uint32_t v) { + p[0] = (uint8_t)(v & 0xffu); + p[1] = (uint8_t)((v >> 8) & 0xffu); + p[2] = (uint8_t)((v >> 16) & 0xffu); + p[3] = (uint8_t)((v >> 24) & 0xffu); +} + +static uint32_t crc32_update(uint32_t crc, const uint8_t *data, size_t len) { + crc = ~crc; + for (size_t i = 0; i < len; i++) { + crc ^= data[i]; + for (int bit = 0; bit < 8; bit++) { + crc = (crc >> 1) ^ (0xedb88320u & (uint32_t)-(int32_t)(crc & 1u)); + } + } + return ~crc; +} + +static bool read_exact_timeout(int fd, uint8_t *buf, size_t len, int timeout_ms) { + size_t done = 0; + while (done < len) { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + struct timeval tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + int r = select(fd + 1, &rfds, NULL, NULL, &tv); + if (r <= 0) { + return false; + } + + ssize_t n = read(fd, buf + done, len - done); + if (n <= 0) { + return false; + } + done += (size_t)n; + } + return true; +} + +static bool write_all(int fd, const uint8_t *buf, size_t len) { + size_t done = 0; + while (done < len) { + ssize_t n = write(fd, buf + done, len - done); + if (n <= 0) { + return false; + } + done += (size_t)n; + } + return true; +} + +rp2040swim_t *rp2040swim_open(const char *device) { + const char *path = device == NULL ? "/dev/ttyACM0" : device; + int fd = open(path, O_RDWR | O_NOCTTY); + if (fd < 0) { + perror("Couldn't open tty"); + return NULL; + } + + struct termios tty; + memset(&tty, 0, sizeof(tty)); + if (tcgetattr(fd, &tty) != 0) { + perror("tcgetattr failed"); + close(fd); + return NULL; + } + + cfsetospeed(&tty, (speed_t)B115200); + cfsetispeed(&tty, (speed_t)B115200); + cfmakeraw(&tty); + tty.c_cflag |= CREAD | CLOCAL; + tty.c_cc[VMIN] = 0; + tty.c_cc[VTIME] = 10; /* 1 second kernel read timeout; select below is stricter per read. */ + + tcflush(fd, TCIOFLUSH); + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + perror("tcsetattr failed"); + close(fd); + return NULL; + } + + rp2040swim_t *pgm = calloc(1, sizeof(*pgm)); + if (!pgm) { + close(fd); + return NULL; + } + pgm->fd = fd; + pgm->sequence = 1; + + /* Firmware reboots USB CDC sometimes; allow it to settle and discard stale bytes. */ + usleep(250000); + tcflush(fd, TCIOFLUSH); + return pgm; +} + +void rp2040swim_close(rp2040swim_t *pgm) { + if (!pgm) return; + close(pgm->fd); + free(pgm); +} + +static bool command(rp2040swim_t *pgm, uint8_t command_id, + const uint8_t *payload, uint16_t payload_len, + uint8_t *response, uint16_t *response_len) { + if (payload_len > RPSW_MAX_PAYLOAD) { + set_error(RP2040SWIM_ERROR_FRAME, -1, "payload too large"); + return false; + } + + uint8_t frame[10 + RPSW_MAX_PAYLOAD + 4]; + put_u32le(&frame[0], RPSW_MAGIC); + frame[4] = RPSW_VERSION; + frame[5] = command_id; + uint16_t seq = pgm->sequence++; + put_u16le(&frame[6], seq); + put_u16le(&frame[8], payload_len); + if (payload_len != 0 && payload != NULL) { + memcpy(&frame[10], payload, payload_len); + } + uint32_t crc = crc32_update(0, frame, 10u + payload_len); + put_u32le(&frame[10u + payload_len], crc); + + if (!write_all(pgm->fd, frame, 10u + payload_len + 4u)) { + set_error(RP2040SWIM_ERROR_IO, errno, "write failed: %s", strerror(errno)); + return false; + } + + uint8_t hdr[10]; + if (!read_exact_timeout(pgm->fd, hdr, sizeof(hdr), 15000)) { + set_error(RP2040SWIM_ERROR_IO, errno, "timeout waiting for response header"); + return false; + } + + if (get_u32le(&hdr[0]) != RPSW_MAGIC || hdr[4] != RPSW_VERSION) { + set_error(RP2040SWIM_ERROR_FRAME, -1, "bad response header"); + return false; + } + + uint8_t resp_cmd = hdr[5]; + uint16_t resp_seq = get_u16le(&hdr[6]); + uint16_t len = get_u16le(&hdr[8]); + if (resp_cmd != (uint8_t)(command_id | 0x80u) || resp_seq != seq || len < 2u || len > (RPSW_MAX_PAYLOAD + 2u)) { + set_error(RP2040SWIM_ERROR_FRAME, -1, "unexpected response cmd=0x%02x seq=%u len=%u", resp_cmd, resp_seq, len); + return false; + } + + uint8_t body_crc[RPSW_MAX_PAYLOAD + 2u + 4u]; + if (!read_exact_timeout(pgm->fd, body_crc, (size_t)len + 4u, 15000)) { + set_error(RP2040SWIM_ERROR_IO, errno, "timeout waiting for response payload"); + return false; + } + + uint32_t expected_crc = get_u32le(&body_crc[len]); + uint8_t tmp[10 + RPSW_MAX_PAYLOAD + 2u]; + memcpy(tmp, hdr, 10); + memcpy(&tmp[10], body_crc, len); + uint32_t actual_crc = crc32_update(0, tmp, 10u + len); + if (actual_crc != expected_crc) { + set_error(RP2040SWIM_ERROR_FRAME, -1, "bad response crc"); + return false; + } + + uint16_t status = get_u16le(&body_crc[0]); + uint16_t out_len = (uint16_t)(len - 2u); + if (status != RPSW_STATUS_OK) { + char detail[192] = {0}; + if (command_id != RPSW_CMD_GET_LAST_ERROR) { + uint16_t detail_len = sizeof(detail) - 1u; + if (command(pgm, RPSW_CMD_GET_LAST_ERROR, NULL, 0, (uint8_t *)detail, &detail_len)) { + detail[detail_len < sizeof(detail) ? detail_len : sizeof(detail) - 1u] = '\0'; + } + } + set_error(RP2040SWIM_ERROR_STATUS, status, "command 0x%02x failed: status=%u %s", command_id, status, detail); + return false; + } + + if (response_len != NULL) { + if (*response_len < out_len) { + set_error(RP2040SWIM_ERROR_FRAME, -1, "response buffer too small: have=%u need=%u", *response_len, out_len); + return false; + } + *response_len = out_len; + } + if (response != NULL && out_len != 0u) { + memcpy(response, &body_crc[2], out_len); + } + return true; +} + +bool rp2040swim_fetch_version(rp2040swim_t *pgm) { + uint8_t resp[64]; + uint16_t len = sizeof(resp); + if (!command(pgm, RPSW_CMD_GET_VERSION, NULL, 0, resp, &len)) { + return false; + } + if (len < 5u || resp[3] != RPSW_VERSION) { + set_error(RP2040SWIM_ERROR_VERSION, -1, "unsupported rp2040_swim protocol version"); + return false; + } + return true; +} + +bool rp2040swim_set_pins(rp2040swim_t *pgm, uint8_t swim_pin, uint8_t nrst_pin, bool pullup) { + uint8_t payload[3] = {swim_pin, nrst_pin, pullup ? 1u : 0u}; + uint16_t len = 0; + return command(pgm, RPSW_CMD_SET_PINS, payload, sizeof(payload), NULL, &len); +} + +bool rp2040swim_set_speed(rp2040swim_t *pgm, bool high_speed) { + uint8_t payload[1] = {high_speed ? 1u : 0u}; + uint16_t len = 0; + return command(pgm, RPSW_CMD_SET_SPEED, payload, sizeof(payload), NULL, &len); +} + +bool rp2040swim_enter_swim(rp2040swim_t *pgm) { + uint16_t len = 0; + return command(pgm, RPSW_CMD_ENTER_SWIM, NULL, 0, NULL, &len); +} + +bool rp2040swim_reset_target(rp2040swim_t *pgm) { + uint16_t len = 0; + return command(pgm, RPSW_CMD_RESET_TARGET, NULL, 0, NULL, &len); +} + +bool rp2040swim_release_target(rp2040swim_t *pgm) { + uint16_t len = 0; + return command(pgm, RPSW_CMD_RELEASE_TARGET, NULL, 0, NULL, &len); +} + +bool rp2040swim_read(rp2040swim_t *pgm, uint8_t *buffer, unsigned int addr, size_t size) { + uint8_t payload[6]; + put_u32le(&payload[0], addr); + put_u16le(&payload[4], (uint16_t)size); + uint16_t len = (uint16_t)size; + return command(pgm, RPSW_CMD_MEMORY_READ, payload, sizeof(payload), buffer, &len) && len == size; +} + +bool rp2040swim_write(rp2040swim_t *pgm, const uint8_t *buffer, unsigned int addr, size_t size) { + if (size > (RPSW_MAX_PAYLOAD - 6u)) { + set_error(RP2040SWIM_ERROR_FRAME, -1, "write too large"); + return false; + } + uint8_t payload[RPSW_MAX_PAYLOAD]; + put_u32le(&payload[0], addr); + put_u16le(&payload[4], (uint16_t)size); + if (size != 0u) { + memcpy(&payload[6], buffer, size); + } + uint16_t len = 0; + return command(pgm, RPSW_CMD_MEMORY_WRITE, payload, (uint16_t)(6u + size), NULL, &len); +} + +bool rp2040swim_flash_erase(rp2040swim_t *pgm, unsigned int addr, unsigned int length) { + uint8_t payload[8]; + put_u32le(&payload[0], addr); + put_u32le(&payload[4], length); + uint16_t len = 0; + return command(pgm, RPSW_CMD_FLASH_ERASE, payload, sizeof(payload), NULL, &len); +} + +bool rp2040swim_flash_write_block(rp2040swim_t *pgm, unsigned int addr, + const uint8_t *buffer, size_t size) { + if (size > (RPSW_MAX_PAYLOAD - 6u)) { + set_error(RP2040SWIM_ERROR_FRAME, -1, "flash write block too large"); + return false; + } + + uint8_t payload[RPSW_MAX_PAYLOAD]; + put_u32le(&payload[0], addr); + put_u16le(&payload[4], (uint16_t)size); + if (size != 0u) { + memcpy(&payload[6], buffer, size); + } + + uint16_t len = 0; + return command(pgm, RPSW_CMD_FLASH_WRITE_BLOCK, payload, (uint16_t)(6u + size), NULL, &len); +} diff --git a/librp2040swim.h b/librp2040swim.h new file mode 100644 index 0000000..dc19132 --- /dev/null +++ b/librp2040swim.h @@ -0,0 +1,44 @@ +/** + * Low-level serial transport for ARDev1161/rp2040_swim programmer. + */ +#ifndef __LIBRP2040SWIM_H +#define __LIBRP2040SWIM_H + +#include +#include +#include + +typedef struct _rp2040swim_t { + int fd; + uint16_t sequence; +} rp2040swim_t; + +typedef struct _rp2040swim_error_t { + int code; + char message[256]; + int device_status; +} rp2040swim_error_t; + +#define RP2040SWIM_ERROR_IO 1 +#define RP2040SWIM_ERROR_FRAME 2 +#define RP2040SWIM_ERROR_STATUS 3 +#define RP2040SWIM_ERROR_VERSION 4 + +rp2040swim_error_t *rp2040swim_get_last_error(void); + +rp2040swim_t *rp2040swim_open(const char *device); +void rp2040swim_close(rp2040swim_t *pgm); + +bool rp2040swim_fetch_version(rp2040swim_t *pgm); +bool rp2040swim_set_pins(rp2040swim_t *pgm, uint8_t swim_pin, uint8_t nrst_pin, bool pullup); +bool rp2040swim_set_speed(rp2040swim_t *pgm, bool high_speed); +bool rp2040swim_enter_swim(rp2040swim_t *pgm); +bool rp2040swim_reset_target(rp2040swim_t *pgm); +bool rp2040swim_release_target(rp2040swim_t *pgm); + +bool rp2040swim_read(rp2040swim_t *pgm, uint8_t *buffer, unsigned int addr, size_t size); +bool rp2040swim_write(rp2040swim_t *pgm, const uint8_t *buffer, unsigned int addr, size_t size); +bool rp2040swim_flash_erase(rp2040swim_t *pgm, unsigned int addr, unsigned int length); +bool rp2040swim_flash_write_block(rp2040swim_t *pgm, unsigned int addr, + const uint8_t *buffer, size_t size); +#endif diff --git a/main.c b/main.c index 80c9e43..f7e2f5e 100644 --- a/main.c +++ b/main.c @@ -13,6 +13,7 @@ #include "pgm.h" #include "espstlink.h" +#include "rp2040swim.h" #include "stlink.h" #include "stlinkv2.h" #include "stm8.h" @@ -92,6 +93,17 @@ programmer_t pgms[] = { espstlink_swim_read_range, espstlink_swim_write_range, }, + { + "rp2040swim", + RP2040_SWIM, + 0, + 0, + rp2040swim_pgm_open, + rp2040swim_pgm_close, + rp2040swim_srst, + rp2040swim_swim_read_range, + rp2040swim_swim_write_range, + }, { NULL }, }; @@ -119,7 +131,7 @@ void print_help_and_exit(const char *name, bool err) { } fprintf(stream, ")\n"); fprintf(stream, "\t-S serialno Specify programmer's serial number. If not given and more than one programmer is available, they'll be listed.\n"); - fprintf(stream, "\t-d port Specify the serial device for espstlink (default: /dev/ttyUSB0)\n"); + fprintf(stream, "\t-d port Specify the serial device for serial programmers (espstlink/rp2040swim; rp2040swim default: /dev/ttyACM0)\n"); fprintf(stream, "\t-p partno Specify STM8 device\n"); fprintf(stream, "\t-l List supported STM8 devices\n"); fprintf(stream, "\t-s memtype Specify memory type (flash, eeprom, ram, opt or explicit address)\n"); @@ -681,13 +693,30 @@ int main(int argc, char **argv) { /* flashing MCU */ int sent = pgm->write_range(pgm, part, buf, start, bytes_to_write, memtype); + + if (sent < bytes_to_write) { + fprintf(stderr, "FAILED\n"); + fprintf(stderr, "Requested %d bytes but wrote only %d.\n", + bytes_to_write, sent); + + fclose(f); + free(buf); + + if (pgm->close) { + pgm->close(pgm); + } + return EXIT_FAILURE; + } + if(pgm->reset) { // Restarting core (if applicable) pgm->reset(pgm); } + fprintf(stderr, "OK\n"); fprintf(stderr, "Bytes written: %d\n", sent); fclose(f); + free(buf); } else if (action == UNLOCK) { int sent; @@ -705,6 +734,21 @@ int main(int argc, char **argv) { } /* flashing MCU */ sent = pgm->write_range(pgm, part, buf, start, bytes_to_write, memtype); + + if (sent < bytes_to_write) { + fprintf(stderr, "FAILED\n"); + fprintf(stderr, "Requested %d option bytes but wrote only %d.\n", + bytes_to_write, sent); + + free(buf); + + if (pgm->close) { + pgm->close(pgm); + } + return EXIT_FAILURE; + } + + free(buf); } else if (part->read_out_protection_mode == ROP_STM8L) { unsigned char c = 0xAA; diff --git a/pgm.h b/pgm.h index 763ad6a..1106549 100644 --- a/pgm.h +++ b/pgm.h @@ -9,6 +9,8 @@ #include "stm8.h" #include "libespstlink.h" +#include "librp2040swim.h" +#include typedef enum { UNKNOWN, @@ -32,7 +34,8 @@ typedef enum { STLinkV2, STLinkV21, STLinkV3, - ESP_STLink + ESP_STLink, + RP2040_SWIM } programmer_type_t; typedef struct programmer_s { @@ -58,6 +61,10 @@ typedef struct programmer_s { /* Data for espstlink module. */ espstlink_t * espstlink; + + /* Data for rp2040swim module. */ + rp2040swim_t *rp2040swim; + bool rp2040swim_connected; const char *port; } programmer_t; diff --git a/rp2040swim.c b/rp2040swim.c new file mode 100644 index 0000000..b0d1ba4 --- /dev/null +++ b/rp2040swim.c @@ -0,0 +1,456 @@ +#include "rp2040swim.h" + +#include +#include +#include +#include +#include +#include + +#include "librp2040swim.h" + +#define DM_CSR2 0x7F99u +#define SWIM_CSR_ADDR 0x7F80u +#define SWIM_CSR_FINISH_VALUE 0x80u + +#define STM8_IAPSR_WR_PG_DIS 0x01u +#define STM8_IAPSR_PUL 0x02u +#define STM8_IAPSR_EOP 0x04u +#define STM8_IAPSR_DUL 0x08u + +#define RECONNECT_ATTEMPTS 7 + +static bool rp2040swim_use_high_speed(void) { + const char *hs = getenv("RP2040SWIM_HS"); + return (hs != NULL) && (hs[0] != '\0') && (strcmp(hs, "0") != 0); +} + +static int rp2040swim_read_byte(programmer_t *pgm, unsigned int addr) { + uint8_t byte = 0; + if (!rp2040swim_read(pgm->rp2040swim, &byte, addr, 1)) return -1; + return byte; +} + +static bool rp2040swim_write_byte(programmer_t *pgm, uint8_t byte, unsigned int addr) { + return rp2040swim_write(pgm->rp2040swim, &byte, addr, 1); +} + +static bool rp2040swim_stall(programmer_t *pgm, bool stall) { + int csr = rp2040swim_read_byte(pgm, DM_CSR2); + if (csr == -1) return false; + return rp2040swim_write_byte(pgm, stall ? (uint8_t)(csr | 8) : (uint8_t)(csr & ~8), DM_CSR2); +} + +static bool rp2040swim_reconnect(programmer_t *pgm) { + if (!rp2040swim_enter_swim(pgm->rp2040swim)) return false; + + if (rp2040swim_use_high_speed()) { + fprintf(stderr, "rp2040swim: experimental high-speed SWIM enabled\n"); + if (!rp2040swim_set_speed(pgm->rp2040swim, true)) { + return false; + } + } + return true; +} + + +static bool rp2040swim_ensure_connected(programmer_t *pgm) { + if (pgm->rp2040swim_connected) { + return true; + } + + if (!rp2040swim_reconnect(pgm)) { + pgm->rp2040swim_connected = false; + return false; + } + + pgm->rp2040swim_connected = true; + return true; +} + +static bool rp2040swim_ensure_connected_with_retries(programmer_t *pgm) { + for (unsigned int attempt = 0; attempt < RECONNECT_ATTEMPTS; attempt++) { + if (attempt != 0) { + fprintf(stderr, "rp2040swim: ENTER_SWIM retry %u/%u\n", + attempt, RECONNECT_ATTEMPTS); + usleep(50000); + } + + if (rp2040swim_ensure_connected(pgm)) { + return true; + } + + pgm->rp2040swim_connected = false; + (void)rp2040swim_reset_target(pgm->rp2040swim); + usleep(1000); + (void)rp2040swim_release_target(pgm->rp2040swim); + } + + return false; +} + +static bool rp2040swim_wait_iapsr_mask(programmer_t *pgm, + const stm8_device_t *device, + uint8_t mask, + unsigned int retries) { + while (retries-- > 0) { + int iapsr = rp2040swim_read_byte(pgm, device->regs.FLASH_IAPSR); + if (iapsr < 0) { + usleep(10000); + continue; + } + if ((iapsr & mask) == mask) { + return true; + } + usleep(10000); + } + + fprintf(stderr, "timeout waiting for FLASH_IAPSR mask 0x%02x\n", mask); + return false; +} + +static bool rp2040swim_prepare_for_flash(programmer_t *pgm, + const stm8_device_t *device, + const memtype_t memtype) { + if (!rp2040swim_ensure_connected(pgm)) return false; + + if (memtype == FLASH) { + if (!rp2040swim_write_byte(pgm, 0x56, device->regs.FLASH_PUKR)) return false; + if (!rp2040swim_write_byte(pgm, 0xae, device->regs.FLASH_PUKR)) return false; + if (!rp2040swim_wait_iapsr_mask(pgm, device, STM8_IAPSR_PUL, 20)) return false; + } + if (memtype == EEPROM || memtype == OPT) { + if (!rp2040swim_write_byte(pgm, 0xae, device->regs.FLASH_DUKR)) return false; + if (!rp2040swim_write_byte(pgm, 0x56, device->regs.FLASH_DUKR)) return false; + if (!rp2040swim_wait_iapsr_mask(pgm, device, STM8_IAPSR_DUL, 20)) return false; + } + + return true; +} + +static bool rp2040swim_wait_eop(programmer_t *pgm, const stm8_device_t *device, unsigned int retries) { + while (retries-- > 0) { + int iapsr = rp2040swim_read_byte(pgm, device->regs.FLASH_IAPSR); + if (iapsr < 0) { + usleep(10000); + continue; + } + if (iapsr & STM8_IAPSR_WR_PG_DIS) { + fprintf(stderr, "target page is write protected (UBC) or read-out protection is enabled\n"); + return false; + } + if (iapsr & STM8_IAPSR_EOP) { + return true; + } + usleep(10000); + } + return false; +} + +int rp2040swim_swim_read_range(programmer_t *pgm, const stm8_device_t *device, + unsigned char *buffer, unsigned int start, + unsigned int length) { + (void)device; + + unsigned int i = 0; + while (i < length) { + size_t current_size = length - i; + if (current_size > 1024) { + current_size = 1024; + } + + bool ok = false; + + for (unsigned int attempt = 0; attempt < RECONNECT_ATTEMPTS; attempt++) { + if (attempt != 0) { + fprintf(stderr, + "rp2040swim: MEMORY_READ retry %u/%u at 0x%06x len=%zu\n", + attempt, RECONNECT_ATTEMPTS, start + i, current_size); + } + + if (!rp2040swim_ensure_connected(pgm)) { + pgm->rp2040swim_connected = false; + usleep(20000); + continue; + } + + if (rp2040swim_read(pgm->rp2040swim, buffer + i, start + i, current_size)) { + ok = true; + break; + } + + pgm->rp2040swim_connected = false; + (void)rp2040swim_release_target(pgm->rp2040swim); + usleep(20000); + } + + if (!ok) { + return i; + } + + i += (unsigned int)current_size; + } + + return i; +} + +static bool rp2040swim_block_needs_erase(const unsigned char *current, + const unsigned char *desired, + unsigned int len) { + for (unsigned int i = 0; i < len; i++) { + if ((current[i] & (uint8_t)~desired[i]) != 0u) { + return true; + } + } + return false; +} + +int rp2040swim_swim_write_range(programmer_t *pgm, const stm8_device_t *device, + unsigned char *buffer, unsigned int start, + unsigned int length, const memtype_t memtype) { + unsigned int i = 0; + + /* + * With lazy-enter, open() no longer enters SWIM. + * write_range() must explicitly enter SWIM before any DM_CSR2 / memory access. + */ + if (!rp2040swim_ensure_connected_with_retries(pgm)) { + return 0; + } + + bool use_firmware_flash_path = false; + const char *fw_flash = getenv("RP2040SWIM_FW_FLASH"); + if (fw_flash != NULL && fw_flash[0] != '\0' && strcmp(fw_flash, "0") != 0) { + use_firmware_flash_path = true; + } + + /* + * Optional experimental firmware-side flash path. The default path below stays + * close to upstream stm8flash/espstlink: CR2/NCR2 + MEMORY_WRITE. + */ + if (memtype == FLASH && use_firmware_flash_path) { + unsigned int block_size = device->flash_block_size; + unsigned int rounded_size = ((length - 1) / block_size + 1) * block_size; + unsigned char *current = malloc(rounded_size); + unsigned char *desired = malloc(rounded_size); + unsigned int written = 0; + + if (!current || !desired) { + free(current); + free(desired); + return 0; + } + + if (rp2040swim_swim_read_range(pgm, device, current, start, rounded_size) != (int)rounded_size) { + free(current); + free(desired); + return 0; + } + + memcpy(desired, buffer, length); + if (rounded_size > length) { + memcpy(desired + length, current + length, rounded_size - length); + } + + if (!rp2040swim_prepare_for_flash(pgm, device, memtype)) { + free(current); + free(desired); + return 0; + } + + for (unsigned int off = 0; off < rounded_size; off += block_size) { + if (memcmp(current + off, desired + off, block_size) == 0) { + if (off < length) { + written = off + block_size; + if (written > length) written = length; + } + continue; + } + + if (rp2040swim_block_needs_erase(current + off, desired + off, block_size)) { + if (!rp2040swim_flash_erase(pgm->rp2040swim, start + off, block_size)) { + break; + } + } + if (!rp2040swim_flash_write_block(pgm->rp2040swim, start + off, desired + off, block_size)) { + break; + } + + if (off < length) { + written = off + block_size; + if (written > length) written = length; + } + } + + free(current); + free(desired); + + return written; + } + + if (memtype == OPT) { + if (!rp2040swim_prepare_for_flash(pgm, device, memtype)) { + return 0; + } + if (!rp2040swim_write_byte(pgm, 0x80, device->regs.FLASH_CR2)) return 0; + if (device->regs.FLASH_NCR2 != 0 && !rp2040swim_write_byte(pgm, 0x7f, device->regs.FLASH_NCR2)) return 0; + + for (i = 0; i < length; i++) { + if (!rp2040swim_write_byte(pgm, buffer[i], start + i)) return i; + usleep(6000); + if (!rp2040swim_wait_eop(pgm, device, 5)) return i; + } + } else { + unsigned int rounded_size = ((length - 1) / device->flash_block_size + 1) * device->flash_block_size; + unsigned char *current = malloc(rounded_size); + unsigned char *desired = malloc(rounded_size); + if (!current || !desired) { + free(current); + free(desired); + return 0; + } + + if (rp2040swim_swim_read_range(pgm, device, current, start, rounded_size) != (int)rounded_size) { + free(current); + free(desired); + return 0; + } + + memcpy(desired, buffer, length); + if (rounded_size > length) { + memcpy(desired + length, current + length, rounded_size - length); + } + + if ((memtype == FLASH || memtype == EEPROM) && + !rp2040swim_prepare_for_flash(pgm, device, memtype)) { + free(current); + free(desired); + return 0; + } + + for (i = 0; i < length; i += device->flash_block_size) { + if (memcmp(current + i, desired + i, device->flash_block_size) == 0) { + continue; + } + + uint8_t prgmode = 0x10; + if (memtype == FLASH || memtype == EEPROM) { + for (unsigned int j = 0; j < device->flash_block_size; j++) { + if (current[i + j]) { + prgmode = 0x01; + break; + } + } + + if (!rp2040swim_write_byte(pgm, prgmode, device->regs.FLASH_CR2)) break; + if (device->regs.FLASH_NCR2 != 0 && !rp2040swim_write_byte(pgm, (uint8_t)~prgmode, device->regs.FLASH_NCR2)) break; + } + + if (!rp2040swim_write(pgm->rp2040swim, desired + i, start + i, device->flash_block_size)) break; + + if (memtype == FLASH || memtype == EEPROM) { + usleep(prgmode == 0x10 ? 3000 : 6000); + if (!rp2040swim_wait_eop(pgm, device, 10)) break; + } + } + + free(current); + free(desired); + } + + if (i > length) { + i = length; + } + return i; +} + +static bool rp2040swim_clear_stall(programmer_t *pgm) { + for (unsigned attempt = 0; attempt < 3; attempt++) { + if (attempt != 0) { + usleep(5000); + } + + if (rp2040swim_stall(pgm, false)) { + return true; + } + } + + fprintf(stderr, "rp2040swim: warning: failed to clear STALL\n"); + return false; +} +void rp2040swim_srst(programmer_t *pgm) { + if (!pgm || !pgm->rp2040swim) return; + + /* + * Reset/run policy: + * + * Do not try to continue the old SWIM session after reset. + * Instead: + * - release old pins/session, + * - hardware reset the target, + * - enter SWIM again, + * - clear STALL, + * - release pins so user firmware can run. + * + * This is intentionally the old working flow. It is needed after -w, + * because stm8flash calls pgm->reset() after successful write. + */ + pgm->rp2040swim_connected = false; + + (void)rp2040swim_release_target(pgm->rp2040swim); + usleep(1000); + + (void)rp2040swim_reset_target(pgm->rp2040swim); + usleep(2000); + + if (!rp2040swim_ensure_connected_with_retries(pgm)) { + fprintf(stderr, + "rp2040swim: warning: failed to enter SWIM after reset; releasing lines only\n"); + (void)rp2040swim_release_target(pgm->rp2040swim); + return; + } + + (void)rp2040swim_clear_stall(pgm); + usleep(1000); + + pgm->rp2040swim_connected = false; + (void)rp2040swim_release_target(pgm->rp2040swim); +} + +bool rp2040swim_pgm_open(programmer_t *pgm) { + pgm->rp2040swim = rp2040swim_open(pgm->port); + if (pgm->rp2040swim == NULL) return false; + + pgm->rp2040swim_connected = false; + + if (!rp2040swim_fetch_version(pgm->rp2040swim) || + !rp2040swim_set_pins(pgm->rp2040swim, 2, 3, false)) + { + rp2040swim_close(pgm->rp2040swim); + pgm->rp2040swim = NULL; + pgm->rp2040swim_connected = false; + return false; + } + + (void)rp2040swim_release_target(pgm->rp2040swim); + + return true; +} + +void rp2040swim_pgm_close(programmer_t *pgm) { + if (!pgm) return; + + if (pgm->rp2040swim) { + if (pgm->rp2040swim_connected) { + (void)rp2040swim_clear_stall(pgm); + usleep(1000); + pgm->rp2040swim_connected = false; + } + + (void)rp2040swim_release_target(pgm->rp2040swim); + + rp2040swim_close(pgm->rp2040swim); + pgm->rp2040swim = NULL; + pgm->rp2040swim_connected = false; + } +} diff --git a/rp2040swim.h b/rp2040swim.h new file mode 100644 index 0000000..aa7e62a --- /dev/null +++ b/rp2040swim.h @@ -0,0 +1,17 @@ +#ifndef __RP2040SWIM_H +#define __RP2040SWIM_H + +#include +#include "pgm.h" + +bool rp2040swim_pgm_open(programmer_t *pgm); +void rp2040swim_pgm_close(programmer_t *pgm); +void rp2040swim_srst(programmer_t *pgm); +int rp2040swim_swim_read_range(programmer_t *pgm, const stm8_device_t *device, + unsigned char *buffer, unsigned int start, + unsigned int length); +int rp2040swim_swim_write_range(programmer_t *pgm, const stm8_device_t *device, + unsigned char *buffer, unsigned int start, + unsigned int length, const memtype_t memtype); + +#endif