Files
avrdude/src/serprog.c
2024-07-29 21:35:31 +02:00

524 lines
16 KiB
C

/*
* 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 <git@funkeleinhorn.com>
*
* 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 <fcntl.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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 <kevin@kevincuzner.com>
* Copyright (C) 2018 Ralf Ramsauer <ralf@vmexit.de>
*/
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) {
int rv = 0;
bool help = false;
for(LNODEID ln = lfirst(extparms); ln; ln = lnext(ln)) {
const char *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 in -x %s\n", extended_param);
rv = -1;
break;
}
my.cs = cs;
continue;
}
if(str_eq(extended_param, "help")) {
help = true;
rv = LIBAVRDUDE_EXIT;
}
if(!help) {
pmsg_error("invalid extended parameter -x %s\n", extended_param);
rv = -1;
}
msg_error("%s -c %s extended options:\n", progname, pgmid);
msg_error(" -x cs=<n> Sets the chip select line to CS<n> for supported programmers\n");
msg_error(" -x help Show this help menu and exit\n");
return rv;
}
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;
}