815 lines
21 KiB
C
815 lines
21 KiB
C
/*
|
|
* ft232r_prog.c by Mark Lord. Copyright 2010-2013.
|
|
*
|
|
* This is a Linux command-line alternative to the FTDI MProg/FTProg utilities.
|
|
* It is known to work only for FT232R chips at this time.
|
|
*
|
|
* 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, 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; see the file LICENSE.txt. If not, write to
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <ftdi.h>
|
|
|
|
#define MYVERSION "1.24"
|
|
|
|
static struct ftdi_context ftdi;
|
|
static int verbose = 0;
|
|
static const char *save_path = NULL, *restore_path = NULL;
|
|
|
|
enum cbus_mode {
|
|
cbus_txden = 0,
|
|
cbus_pwren = 1,
|
|
cbus_rxled = 2,
|
|
cbus_txled = 3,
|
|
cbus_txrxled = 4,
|
|
cbus_sleep = 5,
|
|
cbus_clk48 = 6,
|
|
cbus_clk24 = 7,
|
|
cbus_clk12 = 8,
|
|
cbus_clk6 = 9,
|
|
cbus_io = 10,
|
|
cbus_wr = 11,
|
|
cbus_rd = 12,
|
|
cbus_rxf = 13,
|
|
};
|
|
|
|
static const char *cbus_mode_strings[] = {
|
|
"TxDEN",
|
|
"PwrEn",
|
|
"RxLED",
|
|
"TxLED",
|
|
"TxRxLED",
|
|
"Sleep",
|
|
"Clk48",
|
|
"Clk24",
|
|
"Clk12",
|
|
"Clk6",
|
|
"IO",
|
|
"WR",
|
|
"RD",
|
|
"RxF",
|
|
NULL
|
|
};
|
|
|
|
enum arg_type {
|
|
arg_help,
|
|
arg_dump,
|
|
arg_verbose,
|
|
arg_save,
|
|
arg_restore,
|
|
arg_cbus0,
|
|
arg_cbus1,
|
|
arg_cbus2,
|
|
arg_cbus3,
|
|
arg_cbus4,
|
|
arg_manufacturer,
|
|
arg_product,
|
|
arg_old_serno,
|
|
arg_new_serno,
|
|
arg_self_powered,
|
|
arg_max_bus_power,
|
|
arg_high_current_io,
|
|
arg_suspend_pull_down,
|
|
arg_old_vid,
|
|
arg_old_pid,
|
|
arg_new_vid,
|
|
arg_new_pid,
|
|
arg_invert_txd,
|
|
arg_invert_rxd,
|
|
arg_invert_rts,
|
|
arg_invert_cts,
|
|
arg_invert_dtr,
|
|
arg_invert_dsr,
|
|
arg_invert_dcd,
|
|
arg_invert_ri,
|
|
};
|
|
|
|
static const char *arg_type_strings[] = {
|
|
"--help",
|
|
"--dump",
|
|
"--verbose",
|
|
"--save",
|
|
"--restore",
|
|
"--cbus0",
|
|
"--cbus1",
|
|
"--cbus2",
|
|
"--cbus3",
|
|
"--cbus4",
|
|
"--manufacturer",
|
|
"--product",
|
|
"--old-serial-number",
|
|
"--new-serial-number",
|
|
"--self-powered",
|
|
"--max-bus-power",
|
|
"--high-current-io",
|
|
"--suspend-pull-down",
|
|
"--old-vid",
|
|
"--old-pid",
|
|
"--new-vid",
|
|
"--new-pid",
|
|
"--invert_txd",
|
|
"--invert_rxd",
|
|
"--invert_rts",
|
|
"--invert_cts",
|
|
"--invert_dtr",
|
|
"--invert_dsr",
|
|
"--invert_dcd",
|
|
"--invert_ri",
|
|
NULL
|
|
};
|
|
|
|
static const char *arg_type_help[] = {
|
|
" # (show this help text)",
|
|
" # (dump eeprom settings to stdout))",
|
|
"# (show debug info and raw eeprom contents)",
|
|
" # (save original eeprom contents to file)",
|
|
"# (restore initial eeprom contents from file)",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
" <string> # (new USB manufacturer string)",
|
|
" <string> # (new USB product name string)",
|
|
"<string> # (current serial number of device to be reprogrammed)",
|
|
"<string> # (new USB serial number string)",
|
|
" [on|off] # (self powered)",
|
|
" <number> # (max bus current in milli-amperes)",
|
|
" [on|off] # (enable high [6mA @ 5V] drive current on CBUS pins)",
|
|
"[on|off] # (force I/O pins into logic low state on suspend)",
|
|
" <number> # (current vendor id of device to be reprogrammed, eg. 0x0403)",
|
|
" <number> # (current product id of device to be reprogrammed, eg. 0x6001)",
|
|
" <number> # (new/custom vendor id to be programmed)",
|
|
" <number> # (new/custom product id be programmed)",
|
|
" Inverts the current value of TXD",
|
|
" Inverts the current value of RXD",
|
|
" Inverts the current value of RTS",
|
|
" Inverts the current value of CTS",
|
|
" Inverts the current value of DTR",
|
|
" Inverts the current value of DSR",
|
|
" Inverts the current value of DCD",
|
|
" Inverts the current value of RI",
|
|
};
|
|
|
|
static const char *bool_strings[] = {
|
|
"off",
|
|
"on",
|
|
"0",
|
|
"1",
|
|
"no",
|
|
"yes",
|
|
"disable",
|
|
"enable",
|
|
};
|
|
|
|
struct eeprom_fields {
|
|
unsigned char byte01;
|
|
unsigned char high_current_io; /* bool */
|
|
unsigned char load_d2xx_driver; /* bool */
|
|
unsigned char txd_inverted; /* bool */
|
|
unsigned char rxd_inverted; /* bool */
|
|
unsigned char rts_inverted; /* bool */
|
|
unsigned char cts_inverted; /* bool */
|
|
unsigned char dtr_inverted; /* bool */
|
|
unsigned char dsr_inverted; /* bool */
|
|
unsigned char dcd_inverted; /* bool */
|
|
unsigned char ri_inverted; /* bool */
|
|
unsigned char pnp_enabled; /* bool */
|
|
enum cbus_mode cbus[5];
|
|
unsigned char extras[112]; /* extra, undefined fields */
|
|
struct ftdi_eeprom libftdi; /* stuff known to libftdi */
|
|
unsigned char BM_type_chip; /* from libftdi-0.18, missing in 0.19 */
|
|
|
|
/* These are not actually eeprom values; here for convenience */
|
|
unsigned short old_vid;
|
|
unsigned short old_pid;
|
|
const char *old_serno;
|
|
unsigned short new_vid;
|
|
unsigned short new_pid;
|
|
};
|
|
|
|
static void dumpmem (const char *msg, void *addr, int len)
|
|
{
|
|
char *data = addr, hex[3 * 16 + 1], ascii[17];
|
|
unsigned int i, offset = 0;
|
|
|
|
if (msg)
|
|
printf("%s:\n", msg);
|
|
for (i = 0; i < len;) {
|
|
unsigned int i16 = i % 16;
|
|
unsigned char c = data[i];
|
|
sprintf(hex + (3 * i16), " %02x", c);
|
|
ascii[i16] = (c < ' ' || c > '~') ? '.' : c;
|
|
if (++i == len || i16 == 15) {
|
|
ascii[i16 + 1] = '\0';
|
|
for (; i16 != 15; ++i16)
|
|
strcat(hex, " ");
|
|
printf("%04x:%s %s\n", offset, hex, ascii);
|
|
offset = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
static unsigned short calc_crc (void *addr, int len)
|
|
{
|
|
unsigned int i;
|
|
unsigned short crc = 0xaaaa;
|
|
unsigned char *d8 = addr;
|
|
|
|
for (i = 0; i < len - 2; i += 2) {
|
|
crc ^= d8[i] | (d8[i+1] << 8);
|
|
crc = (crc << 1) | (crc >> 15);
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
static void do_deinit (void)
|
|
{
|
|
ftdi_deinit(&ftdi);
|
|
}
|
|
|
|
static void do_close (void)
|
|
{
|
|
ftdi_usb_close(&ftdi);
|
|
}
|
|
|
|
static unsigned short verify_crc (void *addr, int len)
|
|
{
|
|
unsigned short crc = calc_crc(addr, len);
|
|
unsigned char *d8 = addr;
|
|
unsigned short actual = d8[len-2] | (d8[len-1] << 8);
|
|
|
|
if (crc != actual) {
|
|
fprintf(stderr, "Bad CRC: crc=0x%04x, actual=0x%04x\n", crc, actual);
|
|
exit(EINVAL);
|
|
}
|
|
if (verbose) printf("CRC: Okay (0x%04x)\n", crc);
|
|
return crc;
|
|
}
|
|
|
|
static unsigned short update_crc (void *addr, int len)
|
|
{
|
|
unsigned short crc = calc_crc(addr, len);
|
|
unsigned char *d8 = addr;
|
|
|
|
d8[len-2] = crc;
|
|
d8[len-1] = crc >> 8;
|
|
return crc;
|
|
}
|
|
|
|
static int match_arg (const char *arg, const char **possibles)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; possibles[i]; ++i) {
|
|
if (0 == strcasecmp(possibles[i], arg))
|
|
return i;
|
|
}
|
|
fprintf(stderr, "unrecognized arg: \"%s\"\n", arg);
|
|
exit(EINVAL);
|
|
return -1; /* never reached */
|
|
}
|
|
|
|
static unsigned long unsigned_val (const char *arg, unsigned long max)
|
|
{
|
|
unsigned long val;
|
|
|
|
errno = 0;
|
|
val = strtoul(arg, NULL, 0);
|
|
if (errno || val > max) {
|
|
fprintf(stderr, "%s: bad value (max=0x%lx)\n", arg, max);
|
|
exit(EINVAL);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static void ee_dump (struct eeprom_fields *ee)
|
|
{
|
|
unsigned int c;
|
|
|
|
printf(" eeprom_size = %d\n", ee->libftdi.size);
|
|
printf(" vendor_id = 0x%04x\n", ee->libftdi.vendor_id);
|
|
printf(" product_id = 0x%04x\n", ee->libftdi.product_id);
|
|
printf(" self_powered = %d\n", ee->libftdi.self_powered);
|
|
printf(" remote_wakeup = %d\n", ee->libftdi.remote_wakeup);
|
|
printf("suspend_pull_downs = %d\n", ee->libftdi.suspend_pull_downs);
|
|
printf(" max_bus_power = %d mA\n", 2 * ee->libftdi.max_power);
|
|
printf(" manufacturer = %s\n", ee->libftdi.manufacturer);
|
|
printf(" product = %s\n", ee->libftdi.product);
|
|
printf(" serialnum = %s\n", ee->libftdi.serial);
|
|
printf(" high_current_io = %u\n", ee->high_current_io);
|
|
printf(" load_d2xx_driver = %u\n", ee->load_d2xx_driver);
|
|
printf(" txd_inverted = %u\n", ee->txd_inverted);
|
|
printf(" rxd_inverted = %u\n", ee->rxd_inverted);
|
|
printf(" rts_inverted = %u\n", ee->rts_inverted);
|
|
printf(" cts_inverted = %u\n", ee->cts_inverted);
|
|
printf(" dtr_inverted = %u\n", ee->dtr_inverted);
|
|
printf(" dsr_inverted = %u\n", ee->dsr_inverted);
|
|
printf(" dcd_inverted = %u\n", ee->dcd_inverted);
|
|
printf(" ri_inverted = %u\n", ee->ri_inverted);
|
|
|
|
for (c = 0; c < 5; ++c)
|
|
printf(" cbus[%u] = %s\n", c, cbus_mode_strings[ee->cbus[c]]);
|
|
|
|
if (verbose) {
|
|
/* These fields are non-applicable for FT232R devices */
|
|
printf(" usb_version = %d\n", ee->libftdi.usb_version);
|
|
printf(" use_serialnum = %d (n/a)\n", ee->libftdi.use_serial);
|
|
printf("change_usb_version = %d (n/a)\n", ee->libftdi.change_usb_version);
|
|
printf(" pnp_enabled = %u (n/a)\n", ee->pnp_enabled);
|
|
printf(" BM_type_chip = 0x%02x (n/a)\n", ee->BM_type_chip);
|
|
printf(" in_is_isochronous = %d (n/a)\n", ee->libftdi.in_is_isochronous);
|
|
printf("out_is_isochronous = %d (n/a)\n", ee->libftdi.out_is_isochronous);
|
|
}
|
|
};
|
|
|
|
static unsigned int calc_extras_offset (unsigned char *eeprom)
|
|
{
|
|
unsigned int str1 = (eeprom[0x0e] & 0x7f) + eeprom[0x0f];
|
|
unsigned int str2 = (eeprom[0x10] & 0x7f) + eeprom[0x11];
|
|
unsigned int str3 = (eeprom[0x12] & 0x7f) + eeprom[0x13];
|
|
unsigned int offset;
|
|
|
|
if (str3 > str2)
|
|
offset = (str3 > str1) ? str3 : str1;
|
|
else
|
|
offset = (str2 > str1) ? str2 : str1;
|
|
return offset;
|
|
}
|
|
|
|
static unsigned int encode_string (void *eeprom, int desc, int offset, char *s)
|
|
{
|
|
unsigned char c, *u8 = eeprom, slen = (strlen(s) + 1) * 2;
|
|
|
|
if (!s || !*s)
|
|
return offset;
|
|
u8[desc + 0] = offset | 0x80; /* offset of string */
|
|
u8[desc + 1] = slen; /* length */
|
|
|
|
u8[offset++] = slen; /* length */
|
|
u8[offset++] = 0x03; /* "type" == string */
|
|
while ((c = *s++)) {
|
|
u8[offset++] = c;
|
|
u8[offset++] = 0;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
static void ft232r_eprom_build (struct eeprom_fields *ee, unsigned char *eeprom)
|
|
{
|
|
unsigned int len = ee->libftdi.size;
|
|
int offset = 0x18;
|
|
|
|
memset(eeprom, 0, len);
|
|
if (strlen(ee->libftdi.serial) > 16) {
|
|
fprintf(stderr, "Serial number string exceeds limit of 16 chars, aborting.\n");
|
|
exit(EINVAL);
|
|
}
|
|
if ((strlen(ee->libftdi.manufacturer) + strlen(ee->libftdi.product) + strlen(ee->libftdi.serial)) > 46) {
|
|
fprintf(stderr, "Total string sizes exceed limit of 46 chars, aborting.\n");
|
|
exit(EINVAL);
|
|
}
|
|
offset = encode_string(eeprom, 0x0e, offset, ee->libftdi.manufacturer);
|
|
offset = encode_string(eeprom, 0x10, offset, ee->libftdi.product);
|
|
offset = encode_string(eeprom, 0x12, offset, ee->libftdi.serial);
|
|
eeprom[0x02] = ee->libftdi.vendor_id;
|
|
eeprom[0x03] = ee->libftdi.vendor_id >> 8;
|
|
eeprom[0x04] = ee->libftdi.product_id;
|
|
eeprom[0x05] = ee->libftdi.product_id >> 8;
|
|
eeprom[0x07] = ee->BM_type_chip;
|
|
eeprom[0x08] = 0x80;
|
|
if (ee->libftdi.remote_wakeup)
|
|
eeprom[0x08] |= 0x20;
|
|
if (ee->libftdi.self_powered)
|
|
eeprom[0x08] |= 0x40;
|
|
eeprom[0x09] = ee->libftdi.max_power;
|
|
if (ee->libftdi.in_is_isochronous)
|
|
eeprom[0x0a] |= 0x01;
|
|
if (ee->libftdi.out_is_isochronous)
|
|
eeprom[0x0a] |= 0x02;
|
|
if (ee->libftdi.suspend_pull_downs)
|
|
eeprom[0x0a] |= 0x04;
|
|
if (ee->libftdi.use_serial)
|
|
eeprom[0x0a] |= 0x08;
|
|
if (ee->libftdi.change_usb_version)
|
|
eeprom[0x0a] |= 0x10;
|
|
eeprom[0x0c] = ee->libftdi.usb_version;
|
|
eeprom[0x0d] = ee->libftdi.usb_version >> 8;
|
|
}
|
|
|
|
/*
|
|
* There are some undefined "extra features" bytes after the strings.
|
|
* So blindly preserve them from the original eeprom image.
|
|
*/
|
|
static void ee_encode_extras (unsigned char *eeprom, int len, struct eeprom_fields *ee)
|
|
{
|
|
unsigned int extras_offset = calc_extras_offset(eeprom);
|
|
|
|
memcpy(eeprom + extras_offset, ee->extras, len - extras_offset - 2);
|
|
if (ee->pnp_enabled)
|
|
eeprom[extras_offset + 2] |= 1;
|
|
else
|
|
eeprom[extras_offset + 2] &= ~1;
|
|
}
|
|
|
|
static unsigned short ee_encode (unsigned char *eeprom, int len, struct eeprom_fields *ee)
|
|
{
|
|
int ret;
|
|
|
|
memset(eeprom, 0, len);
|
|
ee->libftdi.size = len;
|
|
|
|
if (ee->new_vid)
|
|
ee->libftdi.vendor_id = ee->new_vid;
|
|
if (ee->new_pid)
|
|
ee->libftdi.product_id = ee->new_pid;
|
|
|
|
/* Unfortunately, ftdi_eeprom_build() is buggy and puts things in the wrong places */
|
|
if (0) {
|
|
ret = ftdi_eeprom_build(&ee->libftdi, eeprom);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "ftdi_eeprom_build() failed, ret=%d\n", ret);
|
|
exit(EINVAL);
|
|
}
|
|
printf("ftdi_eeprom_build() ret=%d\n", ret);
|
|
} else {
|
|
ft232r_eprom_build(ee, eeprom);
|
|
}
|
|
eeprom[1] = ee->byte01;
|
|
if (ee->high_current_io)
|
|
eeprom[0x00] |= 0x04;
|
|
if (ee->load_d2xx_driver)
|
|
eeprom[0x00] |= 0x08;
|
|
if (ee->txd_inverted)
|
|
eeprom[0x0b] |= 0x01;
|
|
if (ee->rxd_inverted)
|
|
eeprom[0x0b] |= 0x02;
|
|
if (ee->rts_inverted)
|
|
eeprom[0x0b] |= 0x04;
|
|
if (ee->cts_inverted)
|
|
eeprom[0x0b] |= 0x08;
|
|
if (ee->dtr_inverted)
|
|
eeprom[0x0b] |= 0x10;
|
|
if (ee->dsr_inverted)
|
|
eeprom[0x0b] |= 0x20;
|
|
if (ee->dcd_inverted)
|
|
eeprom[0x0b] |= 0x40;
|
|
if (ee->ri_inverted)
|
|
eeprom[0x0b] |= 0x80;
|
|
eeprom[0x14] = (ee->cbus[1] << 4) | ee->cbus[0];
|
|
eeprom[0x15] = (ee->cbus[3] << 4) | ee->cbus[2];
|
|
eeprom[0x16] = ee->cbus[4];
|
|
ee_encode_extras(eeprom, len, ee);
|
|
return update_crc(eeprom, len);
|
|
}
|
|
|
|
/*
|
|
* There are some undefined "extra features" bytes after the strings.
|
|
* So blindly preserve them from the original eeprom image.
|
|
*/
|
|
static void ee_decode_extras (unsigned char *eeprom, int len, struct eeprom_fields *ee)
|
|
{
|
|
unsigned int extras_offset = calc_extras_offset(eeprom);
|
|
|
|
memcpy(ee->extras, eeprom + extras_offset, len - extras_offset - 2);
|
|
ee->pnp_enabled = eeprom[extras_offset + 2] & 0x01;
|
|
}
|
|
|
|
static void ee_decode (unsigned char *eeprom, int len, struct eeprom_fields *ee)
|
|
{
|
|
memset(ee, 0, sizeof(*ee));
|
|
if (eeprom[0] & 0x04)
|
|
ee->high_current_io = 1;
|
|
if (eeprom[0x00] & 0x08)
|
|
ee->load_d2xx_driver = 1;
|
|
ee->byte01 = eeprom[0x01];
|
|
if (eeprom[0x0b] & 0x01)
|
|
ee->txd_inverted = 1;
|
|
if (eeprom[0x0b] & 0x02)
|
|
ee->rxd_inverted = 1;
|
|
if (eeprom[0x0b] & 0x04)
|
|
ee->rts_inverted = 1;
|
|
if (eeprom[0x0b] & 0x08)
|
|
ee->cts_inverted = 1;
|
|
if (eeprom[0x0b] & 0x10)
|
|
ee->dtr_inverted = 1;
|
|
if (eeprom[0x0b] & 0x20)
|
|
ee->dsr_inverted = 1;
|
|
if (eeprom[0x0b] & 0x40)
|
|
ee->dcd_inverted = 1;
|
|
if (eeprom[0x0b] & 0x80)
|
|
ee->ri_inverted = 1;
|
|
ee->cbus[0] = eeprom[0x14] & 0xf;
|
|
ee->cbus[1] = eeprom[0x14] >> 4;
|
|
ee->cbus[2] = eeprom[0x15] & 0xf;
|
|
ee->cbus[3] = eeprom[0x15] >> 4;
|
|
ee->cbus[4] = eeprom[0x16] & 0xf;
|
|
ee_decode_extras(eeprom, len, ee);
|
|
|
|
/* Use libftdi to decode the remaining fields, which it knows about */
|
|
if (ftdi_eeprom_decode(&ee->libftdi, eeprom, len)) {
|
|
fprintf(stderr, "ftdi_eeprom_decode() failed\n");
|
|
exit(EINVAL);
|
|
}
|
|
ee->BM_type_chip = eeprom[0x07]; /* buggy ftdi_eeprom_decode() */
|
|
if (eeprom[0x0a] & 0x10) /* more buggy ftdi_eeprom_decode() */
|
|
ee->libftdi.change_usb_version = 1;
|
|
else
|
|
ee->libftdi.change_usb_version = 0;
|
|
ee->libftdi.usb_version = (eeprom[0x0d] << 8) | eeprom[0x0c];;
|
|
}
|
|
|
|
static const char *myname;
|
|
|
|
static void show_help (FILE *fp)
|
|
{
|
|
int i;
|
|
|
|
fprintf(fp, "\nUsage: %s [<arg> <val>]..\n", myname);
|
|
fprintf(fp, "\nwhere <arg> must be any of:\n");
|
|
|
|
for (i = 0; arg_type_strings[i]; ++i) {
|
|
const char *val = arg_type_help[i];
|
|
fprintf(fp, " %s", arg_type_strings[i]);
|
|
if (val) {
|
|
if (*val) {
|
|
fprintf(fp, " %s", val);
|
|
} else { /* cbus args */
|
|
int j;
|
|
fprintf(fp, " [");
|
|
for (j = 0; cbus_mode_strings[j];) {
|
|
fprintf(fp, "%s", cbus_mode_strings[j]);
|
|
if (cbus_mode_strings[++j])
|
|
fprintf(fp, "|");
|
|
}
|
|
fprintf(fp, "]");
|
|
}
|
|
}
|
|
fputc('\n', fp);
|
|
}
|
|
fputc('\n', fp);
|
|
}
|
|
|
|
static unsigned short ee_read_and_verify (void *eeprom, int len)
|
|
{
|
|
if (ftdi_read_eeprom(&ftdi, eeprom)) {
|
|
fprintf(stderr, "ftdi_read_eeprom() failed: %s\n", ftdi_get_error_string(&ftdi));
|
|
exit(EIO);
|
|
}
|
|
return verify_crc(eeprom, len);
|
|
}
|
|
|
|
static void process_args (int argc, char *argv[], struct eeprom_fields *ee)
|
|
{
|
|
int i;
|
|
|
|
for (i = 1; i < argc;) {
|
|
int arg;
|
|
arg = match_arg(argv[i++], arg_type_strings);
|
|
switch (arg) {
|
|
case arg_help:
|
|
show_help(stdout);
|
|
exit(1);
|
|
case arg_dump:
|
|
continue;
|
|
case arg_verbose:
|
|
verbose = 1;
|
|
continue;
|
|
case arg_invert_txd:
|
|
ee->txd_inverted = !ee->txd_inverted;
|
|
continue;
|
|
case arg_invert_rxd:
|
|
ee->rxd_inverted = !ee->rxd_inverted;
|
|
continue;
|
|
case arg_invert_rts:
|
|
ee->rts_inverted = !ee->rts_inverted;
|
|
continue;
|
|
case arg_invert_cts:
|
|
ee->cts_inverted = !ee->cts_inverted;
|
|
continue;
|
|
case arg_invert_dtr:
|
|
ee->dtr_inverted = !ee->dtr_inverted;
|
|
continue;
|
|
case arg_invert_dsr:
|
|
ee->dsr_inverted = !ee->dsr_inverted;
|
|
continue;
|
|
case arg_invert_dcd:
|
|
ee->dcd_inverted = !ee->dcd_inverted;
|
|
continue;
|
|
case arg_invert_ri:
|
|
ee->ri_inverted = !ee->ri_inverted;
|
|
continue;
|
|
}
|
|
if (i == argc) {
|
|
fprintf(stderr, "%s: missing %s value\n", argv[i-2], argv[i-1]);
|
|
exit(EINVAL);
|
|
}
|
|
switch (arg) {
|
|
case arg_save:
|
|
save_path = argv[i++];
|
|
break;
|
|
case arg_restore:
|
|
restore_path = argv[i++];
|
|
break;
|
|
case arg_cbus0:
|
|
case arg_cbus1:
|
|
case arg_cbus2:
|
|
case arg_cbus3:
|
|
case arg_cbus4:
|
|
ee->cbus[arg - arg_cbus0] = match_arg(argv[i++], cbus_mode_strings);
|
|
break;
|
|
case arg_manufacturer:
|
|
ee->libftdi.manufacturer = argv[i++];
|
|
break;
|
|
case arg_product:
|
|
ee->libftdi.product = argv[i++];
|
|
break;
|
|
case arg_new_serno:
|
|
ee->libftdi.serial = argv[i++];
|
|
break;
|
|
case arg_high_current_io:
|
|
ee->high_current_io = match_arg(argv[i++], bool_strings) & 1;
|
|
break;
|
|
case arg_self_powered:
|
|
ee->libftdi.self_powered = match_arg(argv[i++], bool_strings) & 1;
|
|
break;
|
|
case arg_max_bus_power:
|
|
ee->libftdi.max_power = unsigned_val(argv[i++], 0x1ff) / 2;
|
|
break;
|
|
case arg_suspend_pull_down:
|
|
ee->libftdi.suspend_pull_downs = unsigned_val(argv[i++], 0xff);
|
|
break;
|
|
case arg_old_vid:
|
|
ee->old_vid = unsigned_val(argv[i++], 0xffff);
|
|
break;
|
|
case arg_old_pid:
|
|
ee->old_pid = unsigned_val(argv[i++], 0xffff);
|
|
break;
|
|
case arg_old_serno:
|
|
ee->old_serno = argv[i++];
|
|
break;
|
|
case arg_new_vid:
|
|
ee->new_vid = unsigned_val(argv[i++], 0xffff);
|
|
break;
|
|
case arg_new_pid:
|
|
ee->new_pid = unsigned_val(argv[i++], 0xffff);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "bad args\n");
|
|
exit(EINVAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void save_eeprom_to_file (const char *path, void *eeprom, int len)
|
|
{
|
|
int count, fd = open(path, O_CREAT|O_WRONLY|O_TRUNC, 0644);
|
|
|
|
if (fd == -1) {
|
|
int err = errno;
|
|
perror(path);
|
|
exit(err);
|
|
}
|
|
count = write(fd, eeprom, len);
|
|
if (count < 0) {
|
|
int err = errno;
|
|
perror(path);
|
|
exit(err);
|
|
}
|
|
close(fd);
|
|
if (count != len) {
|
|
fprintf(stderr, "%s: wrong size, wrote %d/%d bytes\n", path, count, len);
|
|
exit(EINVAL);
|
|
}
|
|
printf("%s: wrote %d bytes\n", path, count);
|
|
}
|
|
|
|
static void restore_eeprom_from_file (const char *path, void *eeprom, int len, int max)
|
|
{
|
|
int count, fd = open(path, O_RDONLY);
|
|
|
|
if (fd == -1) {
|
|
int err = errno;
|
|
perror(path);
|
|
exit(err);
|
|
}
|
|
count = read(fd, eeprom, max);
|
|
if (count < 0) {
|
|
int err = errno;
|
|
perror(path);
|
|
exit(err);
|
|
}
|
|
close(fd);
|
|
if (count != len ) {
|
|
fprintf(stderr, "%s: wrong size, read %d/%d bytes\n", path, count, len);
|
|
exit(EINVAL);
|
|
}
|
|
printf("%s: read %d bytes\n", path, count);
|
|
verify_crc(eeprom, len);
|
|
}
|
|
|
|
int main (int argc, char *argv[])
|
|
{
|
|
const char *slash;
|
|
unsigned char old[256] = {0,}, new[256] = {0,};
|
|
unsigned short new_crc;
|
|
struct eeprom_fields ee;
|
|
unsigned int len = 128;
|
|
|
|
myname = argv[0];
|
|
slash = strrchr(myname, '/');
|
|
if (slash)
|
|
myname = slash + 1;
|
|
|
|
printf("\n%s: version %s, by Mark Lord.\n", myname, MYVERSION);
|
|
if (argc < 2) {
|
|
show_help(stdout);
|
|
exit(0);
|
|
}
|
|
|
|
ftdi_init(&ftdi);
|
|
atexit(&do_deinit);
|
|
|
|
memset(&ee, 0, sizeof(ee));
|
|
ee.old_vid = 0x0403;; /* default; override with --old_vid arg */
|
|
ee.old_pid = 0x6001; /* default; override with --old_pid arg */
|
|
process_args(argc, argv, &ee); /* handle --help and --old-* args */
|
|
|
|
if (ftdi_usb_open_desc(&ftdi, ee.old_vid, ee.old_pid, NULL, ee.old_serno)) {
|
|
fprintf(stderr, "ftdi_usb_open() failed for %04x:%04x:%s %s\n",
|
|
ee.old_vid, ee.old_pid, ee.old_serno ? ee.old_serno : "", ftdi_get_error_string(&ftdi));
|
|
exit(ENODEV);
|
|
}
|
|
atexit(&do_close);
|
|
|
|
/* First, read the original eeprom from the device */
|
|
(void) ee_read_and_verify(old, len);
|
|
if (verbose) dumpmem("existing eeprom", old, len);
|
|
|
|
/* Save old contents to a file, if requested (--save) */
|
|
if (save_path)
|
|
save_eeprom_to_file(save_path, old, len);
|
|
|
|
/* Restore contents from a file, if requested (--restore) */
|
|
if (restore_path) {
|
|
restore_eeprom_from_file(restore_path, new, len, sizeof(new));
|
|
if (verbose) dumpmem(restore_path, new, len);
|
|
/* Decode file contents into ee struct */
|
|
ee_decode(new, len, &ee);
|
|
} else {
|
|
/* Decode eeprom contents into ee struct */
|
|
ee_decode(old, len, &ee);
|
|
|
|
/* Reencode without any changes, to ensure we can reconstruct the original eeprom from ee */
|
|
new_crc = ee_encode(new, len, &ee);
|
|
if (memcmp(old, new, len)) {
|
|
if (verbose) dumpmem("reconstructed eeprom", new, len);
|
|
fprintf(stderr, "eeprom reconstruction self-test failed, aborting.\n");
|
|
exit(EINVAL);
|
|
}
|
|
}
|
|
|
|
/* process args, and dump new settings */
|
|
process_args(argc, argv, &ee); /* Handle value-change args */
|
|
ee_dump(&ee);
|
|
|
|
/* Build new eeprom image */
|
|
new_crc = ee_encode(new, len, &ee);
|
|
|
|
/* If different from original, then write it back to the device */
|
|
if (0 == memcmp(old, new, len)) {
|
|
printf("No change from existing eeprom contents.\n");
|
|
} else {
|
|
if (verbose) dumpmem("new eeprom", new, len);
|
|
printf("Rewriting eeprom with new contents.\n");
|
|
if (ftdi_write_eeprom(&ftdi, new)) {
|
|
fprintf(stderr, "ftdi_write_eeprom() failed: %s\n", ftdi_get_error_string(&ftdi));
|
|
exit(EIO);
|
|
}
|
|
/* Read it back again, and check for differences */
|
|
if (ee_read_and_verify(new, len) != new_crc) {
|
|
fprintf(stderr, "Readback test failed, results may be botched\n");
|
|
exit(EINVAL);
|
|
}
|
|
ftdi_usb_reset(&ftdi); /* reset the device to force it to load the new settings */
|
|
}
|
|
return 0;
|
|
}
|