multiboot_tool/butterfly_prog.c

790 lines
22 KiB
C

/***************************************************************************
* Copyright (C) 01/2020 by Olaf Rempel *
* razzor@kopf-tisch.de *
* *
* 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; version 2 of the License, *
* *
* 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. *
***************************************************************************/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/time.h>
#include "chipinfo_avr.h"
#include "multiboot.h"
#include "optarg.h"
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define SERIAL_BAUDRATE B115200
#define SERIAL_TIMEOUT 1000
#define WRITE_SIZE_EEPROM 16
struct multiboot_ops butterfly_ops;
typedef struct bfly_privdata_s
{
char * p_device;
struct termios oldtio;
int fd;
uint8_t twi_address;
uint8_t chip_erase;
uint8_t stay_in_bootloader;
uint16_t buffersize;
uint16_t flashsize;
uint16_t eepromsize;
uint8_t progmode_active;
} bfly_privdata_t;
static struct option bfly_optargs[] =
{
{ "address", 1, 0, 'a' }, /* [ -a <address ] */
{ "device", 1, 0, 'd' }, /* -d <device> */
{ "erase", 0, 0, 'e' }, /* [ -e ] */
{ "stay", 0, 0, 's' }, /* [ -s ] */
};
/* *************************************************************************
* bfly_optarg_cb
* ************************************************************************* */
static int bfly_optarg_cb(int val, const char *arg, void *privdata)
{
bfly_privdata_t * p_priv;
p_priv = (bfly_privdata_t *)privdata;
switch (val)
{
case 'a': /* address */
{
char *endptr;
p_priv->twi_address = strtol(arg, &endptr, 16);
if ((*endptr != '\0') ||
(p_priv->twi_address < 0x01) ||
(p_priv->twi_address > 0x7F)
)
{
fprintf(stderr, "invalid address: '%s'\n", arg);
return -1;
}
}
break;
case 'd': /* device */
if (p_priv->p_device != NULL)
{
fprintf(stderr, "invalid device: '%s'\n", arg);
return -1;
}
p_priv->p_device = strdup(optarg);
if (p_priv->p_device == NULL)
{
perror("strdup()");
return -1;
}
break;
case 'e': /* chip erase */
p_priv->chip_erase = 1;
break;
case 's': /* stay in bootloader */
p_priv->stay_in_bootloader = 1;
break;
case 'h':
case '?': /* error */
fprintf(stderr, "Usage: butterfly_prog [options]\n"
" -a <address> - optional: twi address for twiboot bridge mode\n"
" -d <device> - selects butterfly serial device\n"
" -e - executes a chip erase\n"
" -s - stay in bootloader afterwards\n"
" -r <flash|eeprom>:<file> - reads flash/eeprom to file (.bin | .hex | -)\n"
" -w <flash|eeprom>:<file> - write flash/eeprom from file (.bin | .hex)\n"
" -n - disable verify after write\n"
" -p <0|1|2> - progress bar mode\n"
"\n"
"Example: butterfly_prog -d /dev/ttyUSB0 -w flash:code.hex\n"
"\n");
return -1;
default:
return 1;
}
return 0;
} /* bfly_optarg_cb */
/* *************************************************************************
* butterfly_alloc
* ************************************************************************* */
static struct multiboot * butterfly_alloc(void)
{
struct multiboot * mboot = malloc(sizeof(struct multiboot));
if (mboot == NULL)
{
return NULL;
}
memset(mboot, 0x00, sizeof(struct multiboot));
mboot->ops= &butterfly_ops;
bfly_privdata_t * p_priv = malloc(sizeof(bfly_privdata_t));
if (p_priv == NULL)
{
free(mboot);
return NULL;
}
memset(p_priv, 0x00, sizeof(bfly_privdata_t));
optarg_register(bfly_optargs, ARRAY_SIZE(bfly_optargs),
bfly_optarg_cb, (void *)p_priv);
mboot->privdata = p_priv;
return mboot;
} /* butterfly_alloc */
/* *************************************************************************
* butterfly_free
* ************************************************************************* */
static void butterfly_free(struct multiboot * p_mboot)
{
bfly_privdata_t * p_priv = (bfly_privdata_t *)p_mboot->privdata;
if (p_priv->p_device != NULL)
{
free(p_priv->p_device);
}
free(p_priv);
free(p_mboot);
} /* butterfly_free */
/* *************************************************************************
* butterfly_get_memtype
* ************************************************************************* */
static int butterfly_get_memtype(struct multiboot * p_mboot,
const char * p_memname)
{
/* unused parameter */
(void)p_mboot;
if (strcmp(p_memname, "flash") == 0)
{
return 'F';
}
else if (strcmp(p_memname, "eeprom") == 0)
{
return 'E';
}
return -1;
} /* butterfly_get_memtype */
/* *************************************************************************
* butterfly_get_memsize
* ************************************************************************* */
static uint32_t butterfly_get_memsize(struct multiboot * p_mboot,
int memtype)
{
bfly_privdata_t * p_priv = (bfly_privdata_t *)p_mboot->privdata;
if (!p_priv->progmode_active)
{
return 0;
}
switch (memtype)
{
case 'F':
return p_priv->flashsize;
case 'E':
return p_priv->eepromsize;
default:
return 0;
}
} /* butterfly_get_memsize */
/* *************************************************************************
* butterfly_close_device
* ************************************************************************* */
static void butterfly_close_device(bfly_privdata_t * p_priv)
{
tcsetattr(p_priv->fd, TCSANOW, &p_priv->oldtio);
close(p_priv->fd);
} /* butterfly_close_device */
/* *************************************************************************
* butterlfy_open_device
* ************************************************************************* */
static int butterfly_open_device(bfly_privdata_t * p_priv)
{
p_priv->fd = open(p_priv->p_device, O_RDWR | O_NOCTTY | O_CLOEXEC);
if (p_priv->fd < 0)
{
perror("open()");
return -1;
}
if (tcgetattr(p_priv->fd, &p_priv->oldtio) < 0)
{
perror("tcgetattr(oldtio)");
close(p_priv->fd);
return -1;
}
struct termios newtio;
memset(&newtio, 0, sizeof(newtio));
newtio.c_iflag = IGNBRK;
newtio.c_cflag = (CS8 | CREAD | CLOCAL);
cfsetispeed(&newtio, SERIAL_BAUDRATE);
cfsetospeed(&newtio, SERIAL_BAUDRATE);
newtio.c_cc[VMIN] = 1;
newtio.c_cc[VTIME] = 0;
int err = tcsetattr(p_priv->fd, TCSANOW, &newtio);
if (err < 0)
{
perror("tcsetattr(newtio)");
close(p_priv->fd);
return -1;
}
/* needed for some slow USB2Serial adapters */
usleep(200000);
return 0;
} /* butterlfy_open_device */
/* *************************************************************************
* butterfly_serial_read
* ************************************************************************* */
static int butterfly_serial_read(int fd, void * data, int size,
unsigned int timeout_ms)
{
int pos = 0;
while (1)
{
fd_set fdset;
struct timeval timeout;
struct timeval * p_timeout = NULL;
FD_ZERO(&fdset);
FD_SET(fd, &fdset);
if (timeout_ms != 0)
{
p_timeout = &timeout;
timeout.tv_sec = timeout_ms / 1000;
timeout.tv_usec = (timeout_ms % 1000) * 1000;
}
int ret = select(fd +1, &fdset, NULL, NULL, p_timeout);
if (ret == -1)
{
perror("select");
return -1;
}
else if (ret == 0)
{
break;
}
else if (FD_ISSET(fd, &fdset))
{
int len = read(fd, data + pos, size - pos);
if (len < 0)
{
return -1;
}
else
{
pos += len;
if (pos == size)
{
break;
}
}
}
}
return pos;
} /* butterfly_serial_read */
/* *************************************************************************
* butterfly_expect_cr
* ************************************************************************* */
static int butterfly_expect_cr(bfly_privdata_t * p_priv)
{
uint8_t buffer[1];
int result;
result = butterfly_serial_read(p_priv->fd, buffer, sizeof(buffer),
SERIAL_TIMEOUT);
if ((result == sizeof(buffer)) &&
(buffer[0] == '\r')
)
{
return 0;
}
return -1;
} /* butterfly_expect_cr */
/* *************************************************************************
* butterfly_enter_progmode
* ************************************************************************* */
static int butterfly_enter_progmode(bfly_privdata_t * p_priv)
{
if (p_priv->twi_address == 0x00)
{
(void)write(p_priv->fd, "P", 1);
}
else
{
uint8_t cmd[2] = { 'I' , p_priv->twi_address };
(void)write(p_priv->fd, cmd, 2);
}
return butterfly_expect_cr(p_priv);
} /* butterfly_enter_progmode */
/* *************************************************************************
* butterfly_leave_progmode
* ************************************************************************* */
static int butterfly_leave_progmode(bfly_privdata_t * p_priv)
{
if (p_priv->stay_in_bootloader)
{
/* Leave programming mode */
(void)write(p_priv->fd, "L", 1);
}
else
{
/* Exit Bootloader */
(void)write(p_priv->fd, "E", 1);
}
return butterfly_expect_cr(p_priv);
} /* butterfly_leave_progmode */
/* *************************************************************************
* butterfly_get_signature
* ************************************************************************* */
static int butterfly_get_signature(bfly_privdata_t * p_priv,
uint8_t * p_signature)
{
int result;
uint8_t buffer[3];
(void)write(p_priv->fd, "s", 1);
result = butterfly_serial_read(p_priv->fd, buffer, sizeof(buffer),
SERIAL_TIMEOUT);
if (result == 3)
{
p_signature[0] = buffer[2];
p_signature[1] = buffer[1];
p_signature[2] = buffer[0];
return 0;
}
return -1;
} /* butterfly_get_signature */
/* *************************************************************************
* butterfly_get_buffersize
* ************************************************************************* */
static int butterfly_get_buffersize(bfly_privdata_t * p_priv,
uint16_t * p_buffersize)
{
int result;
uint8_t buffer[3];
(void)write(p_priv->fd, "b", 1);
result = butterfly_serial_read(p_priv->fd, buffer, sizeof(buffer),
SERIAL_TIMEOUT);
if (result == sizeof(buffer))
{
if (buffer[0] == 'Y')
{
*p_buffersize = (buffer[1] << 8) | buffer[2];
}
else
{
*p_buffersize = 0;
}
}
return (result != sizeof(buffer));
} /* butterfly_get_buffersize */
/* *************************************************************************
* butterfly_chiperase
* ************************************************************************* */
static int butterfly_chiperase(bfly_privdata_t * p_priv)
{
(void)write(p_priv->fd, "e", 1);
return butterfly_expect_cr(p_priv);
} /* butterfly_chiperase */
/* *************************************************************************
* butterfly_set_address
* ************************************************************************* */
static int butterfly_set_address(bfly_privdata_t * p_priv, uint16_t pos)
{
uint8_t buffer[1];
int result;
(void)write(p_priv->fd, "a", 1);
result = butterfly_serial_read(p_priv->fd, buffer, sizeof(buffer),
SERIAL_TIMEOUT);
if ((result == 1) &&
(buffer[0] == 'Y')
)
{
/* convert to word address */
pos >>= 1;
uint8_t cmd[3] = { 'A', pos >> 8, pos & 0xFF };
(void)write(p_priv->fd, cmd, sizeof(cmd));
result = butterfly_expect_cr(p_priv);
}
return result;
} /* butterfly_set_address */
/* *************************************************************************
* butterfly_read_data
* ************************************************************************* */
static int butterfly_read_data(bfly_privdata_t * p_priv,
uint8_t * p_data, uint16_t size,
uint8_t memtype)
{
int result;
uint8_t cmd[4] = { 'g', size >> 8, size & 0xFF, memtype };
(void)write(p_priv->fd, cmd, 4);
result = butterfly_serial_read(p_priv->fd, p_data, size,
SERIAL_TIMEOUT);
return (result != size);
} /* butterfly_read_data */
/* *************************************************************************
* butterfly_write_data
* ************************************************************************* */
static int butterfly_write_data(bfly_privdata_t * p_priv,
const uint8_t * p_data, uint16_t size,
uint8_t memtype)
{
uint8_t cmd[4] = { 'B', size >> 8, size & 0xFF, memtype };
(void)write(p_priv->fd, cmd, 4);
(void)write(p_priv->fd, p_data, size);
return butterfly_expect_cr(p_priv);
} /* butterfly_write_data */
/* *************************************************************************
* butterfly_close
* ************************************************************************* */
static int butterfly_close(struct multiboot * p_mboot)
{
bfly_privdata_t * p_priv = (bfly_privdata_t *)p_mboot->privdata;
if (p_priv->progmode_active)
{
butterfly_leave_progmode(p_priv);
}
butterfly_close_device(p_priv);
return 0;
} /* butterfly_close */
/* *************************************************************************
* butterfly_open
* ************************************************************************* */
static int butterfly_open(struct multiboot * p_mboot)
{
bfly_privdata_t * p_priv = (bfly_privdata_t *)p_mboot->privdata;
if (p_priv->p_device == NULL)
{
fprintf(stderr, "abort: no device given\n");
return -1;
}
if (butterfly_open_device(p_priv) < 0)
{
return -1;
}
if (butterfly_enter_progmode(p_priv) != 0)
{
fprintf(stderr, "failed to enter progmode\n");
butterfly_close_device(p_priv);
return -1;
}
p_priv->progmode_active = 1;
uint8_t signature[3];
if (butterfly_get_signature(p_priv, signature) != 0)
{
fprintf(stderr, "failed to get signature\n");
butterfly_close_device(p_priv);
return -1;
}
const avr_chipinfo_t * p_chipinfo;
p_chipinfo = chipinfo_get_by_signature(signature);
if (p_chipinfo == NULL)
{
fprintf(stderr, "failed to identify chip signature [0x%02x 0x%02x 0x%02x]\n",
signature[0], signature[1], signature[2]);
butterfly_close_device(p_priv);
return -1;
}
p_priv->flashsize = p_chipinfo->flashsize;
p_priv->eepromsize = p_chipinfo->eepromsize;
if (butterfly_get_buffersize(p_priv, &p_priv->buffersize) != 0)
{
fprintf(stderr, "failed to get buffersize\n");
butterfly_close_device(p_priv);
return -1;
}
if (p_priv->twi_address != 0x00)
{
printf("twi address : 0x%02x\n",
p_priv->twi_address);
}
printf("device : %-16s (sig: 0x%02x 0x%02x 0x%02x)\n",
p_chipinfo->name, p_chipinfo->sig[0],
p_chipinfo->sig[1], p_chipinfo->sig[2]);
printf("flash size : 0x%04x / %5d\n",
p_chipinfo->flashsize, p_chipinfo->flashsize);
printf("eeprom size : 0x%04x / %5d\n",
p_chipinfo->eepromsize, p_chipinfo->eepromsize);
if (p_priv->chip_erase)
{
if (butterfly_chiperase(p_priv) != 0)
{
fprintf(stderr, "failed to chip erase\n");
butterfly_close_device(p_priv);
return -1;
}
printf("chip erased : OK\n");
}
return 0;
} /* butterfly_open */
/* *************************************************************************
* butterfly_read
* ************************************************************************* */
static int butterfly_read(struct multiboot * p_mboot,
struct databuf * p_dbuf,
int memtype)
{
bfly_privdata_t * p_priv = (bfly_privdata_t *)p_mboot->privdata;
char * p_progress_msg = (memtype == 'F') ? "reading flash" : "reading eeprom";
uint16_t pos = 0;
uint16_t size = (memtype == 'F') ? p_priv->flashsize :
p_priv->eepromsize;
if (butterfly_set_address(p_priv, pos) < 0)
{
fprintf(stderr, "failed to set address\n");
return -1;
}
while (pos < size)
{
p_mboot->progress_cb(p_progress_msg, pos, size);
uint16_t len = MIN(p_priv->buffersize, size - pos);
if (butterfly_read_data(p_priv, p_dbuf->data + pos, len, memtype))
{
p_mboot->progress_cb(p_progress_msg, -1, -1);
return -1;
}
pos += len;
}
p_dbuf->length = pos;
p_mboot->progress_cb(p_progress_msg, pos, size);
return 0;
} /* butterfly_read */
/* *************************************************************************
* butterfly_write
* ************************************************************************* */
static int butterfly_write(struct multiboot * p_mboot,
struct databuf * p_dbuf,
int memtype)
{
bfly_privdata_t * p_priv = (bfly_privdata_t *)p_mboot->privdata;
char * p_progress_msg = (memtype == 'F') ? "writing flash" : "writing eeprom";
uint16_t pos = 0;
if (butterfly_set_address(p_priv, pos) < 0)
{
fprintf(stderr, "failed to set address\n");
return -1;
}
while (pos < p_dbuf->length)
{
p_mboot->progress_cb(p_progress_msg, pos, p_dbuf->length);
uint16_t len = (memtype == 'F') ? p_priv->buffersize : WRITE_SIZE_EEPROM;
len = MIN(len, p_dbuf->length - pos);
if (butterfly_write_data(p_priv, p_dbuf->data + pos, len, memtype))
{
p_mboot->progress_cb(p_progress_msg, -1, -1);
return -1;
}
pos += len;
}
p_mboot->progress_cb(p_progress_msg, pos, p_dbuf->length);
return 0;
} /* butterfly_write */
/* *************************************************************************
* butterfly_verify
* ************************************************************************* */
static int butterfly_verify(struct multiboot * p_mboot,
struct databuf * p_dbuf,
int memtype)
{
bfly_privdata_t * p_priv = (bfly_privdata_t *)p_mboot->privdata;
char * p_progress_msg = (memtype == 'F') ? "verifing flash" : "verifing eeprom";
uint16_t pos = 0;
uint8_t comp[256];
if (butterfly_set_address(p_priv, pos) < 0)
{
fprintf(stderr, "failed to set address\n");
return -1;
}
while (pos < p_dbuf->length)
{
p_mboot->progress_cb(p_progress_msg, pos, p_dbuf->length);
uint16_t len = MIN(p_priv->buffersize, p_dbuf->length - pos);
if (butterfly_read_data(p_priv, comp, len, memtype))
{
p_mboot->progress_cb(p_progress_msg, -1, -1);
return -1;
}
if (memcmp(comp, p_dbuf->data + pos, len) != 0x00)
{
p_mboot->progress_cb(p_progress_msg, -1, -1);
fprintf(stderr, "verify failed at pos 0x%04x!!\n", pos);
return -1;
}
pos += len;
}
p_dbuf->length = pos;
p_mboot->progress_cb(p_progress_msg, pos, p_dbuf->length);
return 0;
} /* butterfly_verify */
struct multiboot_ops butterfly_ops =
{
.exec_name = "butterfly_prog",
.alloc = butterfly_alloc,
.free = butterfly_free,
.get_memtype = butterfly_get_memtype,
.get_memsize = butterfly_get_memsize,
.open = butterfly_open,
.close = butterfly_close,
.read = butterfly_read,
.write = butterfly_write,
.verify = butterfly_verify,
};