diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f9ddb59d..7d8a2669 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -231,6 +231,8 @@ add_library(libavrdude serialadapter.c serialupdi.c serialupdi.h + serprog.c + serprog.h solaris_ecpp.h stk500.c stk500.h diff --git a/src/Makefile.am b/src/Makefile.am index f90b82c8..f67ac711 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -185,6 +185,8 @@ libavrdude_a_SOURCES = \ usbasp.h \ serialupdi.c \ serialupdi.h \ + serprog.c \ + serprog.h \ updi_constants.h \ updi_link.c \ updi_link.h \ diff --git a/src/avrdude.1 b/src/avrdude.1 index f5ed848d..3a859781 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -1988,6 +1988,15 @@ the CS line being managed outside the application. .It Ar help Show help menu and exit. .El +.It Ar serprog +.Bl -tag -offset indent -width indent +.It Ar cs +Sets the chip select (CS) to use on supported programmers. +Programmers supporting the 0x16 serprog command can have more than the default CS (0). +This option allows to choose these additional CSes (1, 2, ...) for programming the AVR. +.It Ar help +Show help menu and exit. +.El .El .Sh FILES .Bl -tag -offset indent -width /dev/ppi0XXX diff --git a/src/avrdude.conf.in b/src/avrdude.conf.in index 48d68769..296a64f9 100644 --- a/src/avrdude.conf.in +++ b/src/avrdude.conf.in @@ -1479,6 +1479,24 @@ programmer # serialupdi hvupdi_support = 1; ; +#------------------------------------------------------------ +# serprog +#------------------------------------------------------------ + +# The Serprog programmer implementation allows programming +# with programmers using the serprog protocol from flashrom. +# See https://flashrom.org/supported_hw/supported_prog/serprog/overview.html +# and https://flashrom.org/supported_hw/supported_prog/serprog/serprog-protocol.html +# for details. + +programmer # serprog + id = "serprog"; + desc = "Program via the Serprog protocol from Flashrom"; + type = "serprog"; + prog_modes = PM_ISP; + connection_type = serial; +; + #------------------------------------------------------------ # avrisp #------------------------------------------------------------ diff --git a/src/doc/avrdude.texi b/src/doc/avrdude.texi index 68486c9c..b9b9dacf 100644 --- a/src/doc/avrdude.texi +++ b/src/doc/avrdude.texi @@ -1645,6 +1645,19 @@ the CS line being managed outside the application. Show help menu and exit. @end table +@cindex Option @code{-x} serprog +@item serprog + +The serprog programmer type accepts the following extended parameter: +@table @code +@item @samp{cs} +Sets the chip select (CS) to use on supported programmers. +Programmers supporting the 0x16 serprog command can have more than the default CS (0). +This option allows to choose these additional CSes (1, 2, ...) for programming the AVR. +@item @samp{help} +Show help menu and exit. +@end table + @end table @page diff --git a/src/pgm_type.c b/src/pgm_type.c index 945f816f..56639eb8 100644 --- a/src/pgm_type.c +++ b/src/pgm_type.c @@ -49,6 +49,7 @@ #include "ppi.h" #include "serbb.h" #include "serialupdi.h" +#include "serprog.h" #include "stk500.h" #include "stk500generic.h" #include "stk500v2.h" @@ -100,6 +101,7 @@ const PROGRAMMER_TYPE programmers_types[] = { // Name(s) the programmers call th {"pickit2", pickit2_initpgm, pickit2_desc}, // "pickit2" {"serbb", serbb_initpgm, serbb_desc}, // "SERBB" {"serialupdi", serialupdi_initpgm, serialupdi_desc}, // "serialupdi" + {"serprog", serprog_initpgm, serprog_desc}, // "serprog" {"stk500", stk500_initpgm, stk500_desc}, // "STK500" {"stk500generic", stk500generic_initpgm, stk500generic_desc}, // "STK500GENERIC" {"stk500v2", stk500v2_initpgm, stk500v2_desc}, // "STK500V2" diff --git a/src/serprog.c b/src/serprog.c new file mode 100644 index 00000000..739790b2 --- /dev/null +++ b/src/serprog.c @@ -0,0 +1,519 @@ +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * Support for using serprog programmers to program over ISP. + * For information on serprog see: + * https://flashrom.org/supported_hw/supported_prog/serprog/index.html + * + * Copyright (C) 2024 Sydney Louisa Wilke + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Known limitations: + * - performance is suboptimal + * - connecting over TCP/IP to programmers is not implemented yet + */ + +#include "ac_cfg.h" + +#include "avrdude.h" +#include "libavrdude.h" + +#include "serprog.h" + +#include +#include + +#include +#include +#include +#include + +const char serprog_desc[] = "Program via the Serprog protocol from Flashrom"; + +// Private data for this programmer +struct pdata { + unsigned char cmd_bitmap[32]; + unsigned int cs; + uint32_t actual_frequency; +}; + +#define my (*(struct pdata *)(pgm->cookie)) + +// Serprog protocol specification + +// According to Serial Flasher Protocol Specification - version 1 +#define S_ACK 0x06 +#define S_NAK 0x15 +#define S_CMD_NOP 0x00 // No operation +#define S_CMD_Q_IFACE 0x01 // Query interface version +#define S_CMD_Q_CMDMAP 0x02 // Query supported commands bitmap +#define S_CMD_Q_PGMNAME 0x03 // Query programmer name +#define S_CMD_Q_SERBUF 0x04 // Query Serial Buffer Size +#define S_CMD_Q_BUSTYPE 0x05 // Query supported bustypes +#define S_CMD_Q_CHIPSIZE 0x06 // Query supported chipsize (2^n format) +#define S_CMD_Q_OPBUF 0x07 // Query operation buffer size +#define S_CMD_Q_WRNMAXLEN 0x08 // Query Write to opbuf: Write-N maximum length +#define S_CMD_R_BYTE 0x09 // Read a single byte +#define S_CMD_R_NBYTES 0x0A // Read n bytes +#define S_CMD_O_INIT 0x0B // Initialize operation buffer +#define S_CMD_O_WRITEB 0x0C // Write opbuf: Write byte with address +#define S_CMD_O_WRITEN 0x0D // Write to opbuf: Write-N +#define S_CMD_O_DELAY 0x0E // Write opbuf: udelay +#define S_CMD_O_EXEC 0x0F // Execute operation buffer +#define S_CMD_SYNCNOP 0x10 // Special no-operation that returns NAK+ACK +#define S_CMD_Q_RDNMAXLEN 0x11 // Query read-n maximum length +#define S_CMD_S_BUSTYPE 0x12 // Set used bustype(s). +#define S_CMD_O_SPIOP 0x13 // Perform SPI operation. +#define S_CMD_S_SPI_FREQ 0x14 // Set SPI clock frequency +#define S_CMD_S_PIN_STATE 0x15 // Enable/disable output drivers +#define S_CMD_S_SPI_CS 0x16 // Set SPI chip select to use +#define S_CMD_S_SPI_MODE 0x17 // Sets the SPI mode used by S_CMD_O_SPIOP +#define S_CMD_S_CS_MODE 0x18 // Sets the way the CS is controlled + +enum spi_mode { + SPI_MODE_HALF_DUPLEX = 0, + SPI_MODE_FULL_DUPLEX = 1, + SPI_MODE_MAX = SPI_MODE_FULL_DUPLEX, +}; + +enum cs_mode { + CS_MODE_AUTO = 0, + CS_MODE_SELECTED = 1, + CS_MODE_DESELECTED = 2, + CS_MODE_MAX = CS_MODE_DESELECTED, +}; + +// Little endian helper functions + +static uint16_t read_le16(const unsigned char *buf) { + return buf[0] | (buf[1] << 8); +} + +static uint32_t read_le32(const unsigned char *buf) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); +} + +static void write_le24(unsigned char *buf, uint32_t val) { + buf[0] = val; + buf[1] = val >> 8; + buf[2] = val >> 16; +} + +static void write_le32(unsigned char *buf, uint32_t val) { + buf[0] = val; + buf[1] = val >> 8; + buf[2] = val >> 16; + buf[3] = val >> 24; +} + +// Serprog communication functions + +static int perform_serprog_cmd_full(const PROGRAMMER *pgm, uint8_t cmd, + const unsigned char *params, int params_len, + const unsigned char *send_buf, int send_len, unsigned char *recv_buf, int recv_len) { + + unsigned char resp_status_code = 0; + + if(serial_send(&pgm->fd, &cmd, 1) < 0) + return -1; + if(params_len > 0) + if(serial_send(&pgm->fd, params, params_len) < 0) + return -1; + if(send_len > 0) + if(serial_send(&pgm->fd, send_buf, send_len) < 0) + return -1; + + if(serial_recv(&pgm->fd, &resp_status_code, 1) < 0 || serial_recv(&pgm->fd, recv_buf, recv_len) < 0) + return -1; + + return resp_status_code == S_ACK? 0: resp_status_code == S_NAK? 1: -1; +} + +static int perform_serprog_cmd(const PROGRAMMER *pgm, uint8_t cmd, + const unsigned char *params, int params_len, unsigned char *recv_buf, int recv_len) { + return perform_serprog_cmd_full(pgm, cmd, params, params_len, NULL, 0, recv_buf, recv_len); +} + +/** + * @brief Sends/receives a message to the AVR in full duplex mode + * @return -1 on failure, otherwise number of bytes sent/received + */ +static int serprog_spi_duplex(const PROGRAMMER *pgm, const unsigned char *tx, unsigned char *rx, int len) { + unsigned char params[6]; + + write_le24(params, len); + write_le24(params + 3, len); + if(perform_serprog_cmd_full(pgm, S_CMD_O_SPIOP, params, sizeof params, tx, len, rx, len) != 0) + return -1; + + return len; +} + +static bool is_serprog_cmd_supported(const unsigned char *cmd_bitmap, unsigned char cmd) { + return (cmd_bitmap[cmd / 8] >> (cmd % 8)) & 1; +} + +// Programmer lifecycle handlers + +static int serprog_open(PROGRAMMER *pgm, const char *port) { + union pinfo pinfo; + + pgm->port = port; + pinfo.serialinfo.baud = pgm->baudrate? pgm->baudrate: 115200; + pinfo.serialinfo.cflags = SERIAL_8N1; + if(serial_open(port, pinfo, &pgm->fd) == -1) + return -1; + + unsigned char buf[32]; + + // Sync + memset(buf, 0, sizeof buf); + if(perform_serprog_cmd(pgm, S_CMD_SYNCNOP, NULL, 0, buf, 1) != 1 || buf[0] != S_ACK) { + pmsg_error("cannot sync; is this a serprog programmer?\n"); + return -1; + } + + // Get command bitmap + memset(my.cmd_bitmap, 0, sizeof my.cmd_bitmap); + if(perform_serprog_cmd(pgm, S_CMD_Q_CMDMAP, NULL, 0, my.cmd_bitmap, 32) != 0) { + pmsg_error("cannot get list of supported serprog commands\n"); + return -1; + } + + // Get protocol version + memset(buf, 0, sizeof buf); + if(!is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_Q_IFACE) + || perform_serprog_cmd(pgm, S_CMD_Q_IFACE, NULL, 0, buf, 2) != 0) { + pmsg_error("cannot get serprog protocol version\n"); + return -1; + } + if(read_le16(buf) != 0x01) { + pmsg_error("unsupported serprog protocol version: %d\n", read_le16(buf)); + return -1; + } + + pmsg_info("serprog protocol version: %d\n", read_le16(buf)); + + // Get programmer name + if(is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_Q_PGMNAME)) { + memset(buf, 0, sizeof buf); + if(perform_serprog_cmd(pgm, S_CMD_Q_PGMNAME, NULL, 0, buf, 16) != 0) { + pmsg_error("cannot get programmer name\n"); + return -1; + } + pmsg_info("programmer name: %s\n", buf); + } + + // Check if required commands are supported + if(!is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_O_SPIOP)) { + pmsg_error("the %s programmer does not support SPI operations\n", pgmid); + return -1; + } + + if(!is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_S_CS_MODE)) { + pmsg_error("the %s programmer does not support setting the CS mode\n", pgmid); + return -1; + } + + if(!is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_S_SPI_MODE)) { + pmsg_error("the %s programmer does not support setting the SPI mode\n", pgmid); + return -1; + } + + if(my.cs > 0 && !is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_S_SPI_CS)) { + pmsg_error("the %s programmer does not support changing the CS\n", pgmid); + return -1; + } + + return 0; +} + +static void serprog_disable(const PROGRAMMER *pgm) { + unsigned char buf[32]; + + // Switch CS to auto + const unsigned char cs_mode = CS_MODE_AUTO; + + if(perform_serprog_cmd(pgm, S_CMD_S_CS_MODE, &cs_mode, 1, NULL, 0) != 0) { + pmsg_error("cannot reset the CS mode to auto\n"); + } + // Disable output + if(is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_S_PIN_STATE)) { + memset(buf, 0, sizeof buf); + buf[0] = 0; // Pin state disable + if(perform_serprog_cmd(pgm, S_CMD_S_PIN_STATE, buf, 1, NULL, 0) != 0) { + pmsg_error("cannot disable pin state\n"); + } + } + // Restore half duplex + memset(buf, 0, sizeof buf); + buf[0] = SPI_MODE_HALF_DUPLEX; + if(perform_serprog_cmd(pgm, S_CMD_S_SPI_MODE, buf, 1, NULL, 0) != 0) + pmsg_error("cannot reset SPI half duplex mode\n"); + + // Reset CS to CS_0 + if(is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_S_SPI_CS)) { + memset(buf, 0, sizeof buf); + buf[0] = 0; + if(perform_serprog_cmd(pgm, S_CMD_S_SPI_CS, buf, 1, NULL, 0) != 0) + pmsg_error("cannot reset CS to CS_0\n"); + } +} + +static void serprog_close(PROGRAMMER *pgm) { + serial_close(&pgm->fd); +} + +static int serprog_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res) { + return serprog_spi_duplex(pgm, cmd, res, 4); +} + +static int serprog_initialize(const PROGRAMMER *pgm, const AVRPART *part) { + if(part->prog_modes & PM_TPI) { + // We do not support TPI; this is a dedicated SPI thing + pmsg_error("the %s programmer does not support TPI\n", pgmid); + return -1; + } + + unsigned char buf[32]; + + // Set SPI clock frequency + if(is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_S_SPI_FREQ)) { + memset(buf, 0, sizeof buf); + uint32_t frequency = + pgm->bitclock > 0? 1/pgm->bitclock: + part->factory_fcpu > 0? part->factory_fcpu/4: + 250000; + write_le32(buf, frequency); + if(perform_serprog_cmd(pgm, S_CMD_S_SPI_FREQ, buf, 4, buf, 4) != 0) { + pmsg_error("cannot set SPI frequency %u Hz\n", (unsigned) frequency); + return -1; + } + my.actual_frequency = read_le32(buf); + } + + // Set active chip select + if(is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_S_SPI_CS)) { + memset(buf, 0, sizeof buf); + buf[0] = my.cs; + if(perform_serprog_cmd(pgm, S_CMD_S_SPI_CS, buf, 1, NULL, 0) != 0) { + pmsg_error("cannot change CS\n"); + return -1; + } + } + + // Set full duplex + memset(buf, 0, sizeof buf); + buf[0] = SPI_MODE_FULL_DUPLEX; + if(perform_serprog_cmd(pgm, S_CMD_S_SPI_MODE, buf, 1, NULL, 0) != 0) { + pmsg_error("cannot set SPI full duplex mode\n"); + return -1; + } + + // Set output + if(is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_S_PIN_STATE)) { + memset(buf, 0, sizeof buf); + buf[0] = 1; // Pin state enable + if(perform_serprog_cmd(pgm, S_CMD_S_PIN_STATE, buf, 1, NULL, 0) != 0) { + pmsg_error("cannot enable pin state\n"); + return -1; + } + } + + // Enable the CS/reset pin + const unsigned char cs_mode = CS_MODE_SELECTED; + + if(perform_serprog_cmd(pgm, S_CMD_S_CS_MODE, &cs_mode, 1, NULL, 0) != 0) { + pmsg_error("cannot enable the reset pin\n"); + return -1; + } + + int tries, ret; + + // Enable programming on the part + tries = 0; + do { + ret = pgm->program_enable(pgm, part); + if(ret == 0 || ret == -1) + break; + } while(tries++ < 65); + + if(ret) + pmsg_error("AVR device not responding\n"); + + return ret; +} + +/* used linuxspi.c as a template: + * Copyright (C) 2013 Kevin Cuzner + * Copyright (C) 2018 Ralf Ramsauer + */ +static int serprog_program_enable(const PROGRAMMER *pgm, const AVRPART *p) { + unsigned char cmd[4], res[4]; + + if(!p->op[AVR_OP_PGM_ENABLE]) { + pmsg_error("program enable instruction not defined for part %s\n", p->desc); + return -1; + } + + memset(cmd, 0, sizeof cmd); + avr_set_bits(p->op[AVR_OP_PGM_ENABLE], cmd); // Set the cmd + pgm->cmd(pgm, cmd, res); + + if(res[2] != cmd[1]) { + + /** From ATtiny441 datasheet: + * + * In some systems, the programmer cannot guarantee that SCK is held low + * during power-up. In this case, RESET must be given a positive pulse after + * SCK has been set to '0'. The duration of the pulse must be at least t RST + * plus two CPU clock cycles. See Table 25-5 on page 240 for definition of + * minimum pulse width on RESET pin, t RST + * 2. Wait for at least 20 ms and then enable serial programming by sending + * the Programming Enable serial instruction to the SDO pin + * 3. The serial programming instructions will not work if the communication + * is out of synchronization. When in sync, the second byte (0x53) will echo + * back when issuing the third byte of the Programming Enable instruction + * ... + * If the 0x53 did not echo back, give RESET a positive pulse and issue a + * new Programming Enable command + */ + + unsigned char cs_mode = CS_MODE_DESELECTED; + if(perform_serprog_cmd(pgm, S_CMD_S_CS_MODE, &cs_mode, 1, NULL, 0) != 0) + return -1; + + usleep(5); + cs_mode = CS_MODE_SELECTED; + if(perform_serprog_cmd(pgm, S_CMD_S_CS_MODE, &cs_mode, 1, NULL, 0) != 0) + return -1; + + usleep(20000); + + return -2; + } + + return 0; +} + +static int serprog_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) { + unsigned char cmd[4], res[4]; + + if(!p->op[AVR_OP_CHIP_ERASE]) { + pmsg_error("chip erase instruction not defined for part %s\n", p->desc); + return -1; + } + + memset(cmd, 0, sizeof cmd); + avr_set_bits(p->op[AVR_OP_CHIP_ERASE], cmd); + pgm->cmd(pgm, cmd, res); + usleep(p->chip_erase_delay); + pgm->initialize(pgm, p); + + return 0; +} + +static void serprog_display(const PROGRAMMER *pgm, const char *p) { +} + +static void serprog_enable(PROGRAMMER *pgm, const AVRPART *p) { +} + +static void serprog_setup(PROGRAMMER *pgm) { + pgm->cookie = mmt_malloc(sizeof(struct pdata)); +} + +static void serprog_teardown(PROGRAMMER *pgm) { + mmt_free(pgm->cookie); + pgm->cookie = NULL; +} + +static int serprog_set_sck_period(const PROGRAMMER *pgm, double v) { + if(!is_serprog_cmd_supported(my.cmd_bitmap, S_CMD_S_SPI_FREQ)) + return -1; + + unsigned char buf[8]; + + memset(buf, 0, sizeof buf); + write_le32(buf, v); + if(perform_serprog_cmd(pgm, S_CMD_S_SPI_FREQ, buf, 4, buf, 4) != 0) { + pmsg_error("cannot set SPI frequency\n"); + return -1; + } + my.actual_frequency = read_le32(buf); + return 0; +} + +static int serprog_get_sck_period(const PROGRAMMER *pgm, double *v) { + *v = my.actual_frequency; + return 0; +} + +static int serprog_parseextparams(const PROGRAMMER *pgm, const LISTID extparms) { + LNODEID ln; + const char *extended_param; + int rv = 0; + + for(ln = lfirst(extparms); ln; ln = lnext(ln)) { + extended_param = ldata(ln); + + if(str_starts(extended_param, "cs=")) { + unsigned int cs; + + if(sscanf(extended_param, "cs=%u", &cs) != 1) { + pmsg_error("invalid chip select '%s'\n", extended_param); + rv = -1; + } + my.cs = cs; + continue; + } + + if(str_eq(extended_param, "help")) { + msg_error("%s -c %s extended options:\n", progname, pgmid); + msg_error(" -xcs=cs_num Sets the chip select (CS) to use on supported programmers\n"); + msg_error(" -xhelp Show this help menu and exit\n"); + return LIBAVRDUDE_EXIT; + } + + pmsg_error("invalid extended parameter '%s'\n", extended_param); + rv = -1; + } + + return rv; +} + +void serprog_initpgm(PROGRAMMER *pgm) { + strcpy(pgm->type, "serprog"); + + // Required fields + pgm->initialize = serprog_initialize; + pgm->display = serprog_display; + pgm->enable = serprog_enable; + pgm->disable = serprog_disable; + pgm->program_enable = serprog_program_enable; + pgm->chip_erase = serprog_chip_erase; + pgm->cmd = serprog_cmd; + pgm->open = serprog_open; + pgm->close = serprog_close; + pgm->read_byte = avr_read_byte_default; + pgm->write_byte = avr_write_byte_default; + + // Optional fields + pgm->setup = serprog_setup; + pgm->teardown = serprog_teardown; + pgm->parseextparams = serprog_parseextparams; + pgm->get_sck_period = serprog_get_sck_period; + pgm->set_sck_period = serprog_set_sck_period; +} diff --git a/src/serprog.h b/src/serprog.h new file mode 100644 index 00000000..587f66f8 --- /dev/null +++ b/src/serprog.h @@ -0,0 +1,35 @@ + +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * + * Copyright (C) 2024 Sydney Louisa Wilke + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef serprog_h +#define serprog_h + +#ifdef __cplusplus +extern "C" { +#endif + +extern const char serprog_desc[]; +void serprog_initpgm(PROGRAMMER *pgm); + +#ifdef __cplusplus +} +#endif +#endif // serprog_h