#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/param.h>
#include <usb.h>
#include "hexfile.h"

static const char rcsid[] = "$Id: fpga_load.c 1418 2006-09-06 20:19:39Z tzafrir $";

#define	ERR(fmt, arg...)	fprintf(stderr, "%s: ERROR: " fmt, progname, ## arg)
#define	INFO(fmt, arg...)	fprintf(stderr, "%s: " fmt, progname, ## arg)

static int	verbose = LOG_WARNING;
static char	*progname;

#define	MAX_HEX_LINES	2000
#define	PACKET_SIZE	512
#define	EEPROM_SIZE	16
#define	SERIAL_SIZE	8

enum fpga_load_packet_types {
	STATUS_REPLY	= 0x01,
	DATA_PACKET	= 0x01,
#ifdef	XORCOM_INTERNAL
	EEPROM_SET	= 0x04,
#endif
	EEPROM_GET	= 0x08,
	RENUMERATE	= 0x10,
	BAD_COMMAND	= 0xAA
};

struct myeeprom {
	uint8_t		source;
	uint16_t	vendor;
	uint16_t	product;
	uint8_t		release_major;
	uint8_t		release_minor;
	uint8_t		reserved;
	uint8_t		serial[SERIAL_SIZE];
} PACKED;

struct fpga_packet_header {
	struct {
		uint8_t		op;
	} PACKED header;
	union {
		struct {
			uint16_t	seq;
			uint8_t		status;
		} PACKED status_reply;
		struct {
			uint16_t	seq;
			uint8_t		reserved;
			uint8_t		data[ZERO_SIZE];
		} PACKED data_packet;
		struct {
			struct myeeprom		data;
		} PACKED eeprom_set;
		struct {
			struct myeeprom		data;
		} PACKED eeprom_get;
	} d;
} PACKED;

enum fpga_load_status {
	FW_FAIL_RESET	= 1,
	FW_FAIL_TRANS	= 2,
	FW_TRANS_OK	= 4,
	FW_CONFIG_DONE	= 8
};

int my_usb_device(struct usb_device *dev, usb_dev_handle *handle);

const char *load_status2str(enum fpga_load_status s)
{
	switch(s) {
		case FW_FAIL_RESET: return "FW_FAIL_RESET";
		case FW_FAIL_TRANS: return "FW_FAIL_TRANS";
		case FW_TRANS_OK: return "FW_TRANS_OK";
		case FW_CONFIG_DONE: return "FW_CONFIG_DONE";
		default: return "UNKNOWN";
	}
}

int path_of_dev(char buf[], unsigned int buflen, struct usb_device *dev)
{
	return snprintf(buf, buflen, "/proc/bus/usb/%s/%s", dev->bus->dirname, dev->filename);
}

struct usb_device *dev_of_path(const char *path)
{
	struct usb_bus		*bus;
	struct usb_device	*dev;
	char			dirname[PATH_MAX];
	char			filename[PATH_MAX];
	const char		prefix[] = "/proc/bus/usb/";
	const int		prefix_len = strlen(prefix);
	const char		*p;
	int			bnum;
	int			dnum;
	int			ret;

	assert(path != NULL);
	if(strncmp(prefix, path, prefix_len) != 0) {
		ERR("wrong path: '%s'\n", path);
		return NULL;
	}
	p = path + prefix_len;
	ret = sscanf(p, "%d/%d", &bnum, &dnum);
	if(ret != 2) {
		ERR("wrong path tail: '%s'\n", p);
		return NULL;
	}
	sprintf(dirname, "%03d", bnum);
	sprintf(filename, "%03d", dnum);
	for (bus = usb_busses; bus; bus = bus->next) {
		if(strcmp(bus->dirname, dirname) != 0)
			continue;
		for (dev = bus->devices; dev; dev = dev->next) {
			if(strcmp(dev->filename, filename) == 0)
				return dev;
		}
	}
	ERR("no usb device match '%s'\n", path);
	return NULL;
}

int get_usb_string(char *buf, unsigned int len, uint16_t item, usb_dev_handle *handle)
{
	char	tmp[BUFSIZ];
	int	ret;

	if (!item)
		return 0;
	ret = usb_get_string_simple(handle, item, tmp, BUFSIZ);
	if (ret <= 0)
		return ret;
	return snprintf(buf, len, "%s", tmp);
}

/* My device parameters */
#define	MY_INTERFACE	0
#define	MY_CONFIG	1
#define	MY_ENDPOINTS	4

#define	MY_EP_OUT	0x04
#define	MY_EP_IN	0x88

#define	TIMEOUT		5000

static const int my_endpoints[MY_ENDPOINTS] = {
	0x02,
	0x04,
	0x86,
	0x88
};

void usb_cleanup(usb_dev_handle *handle)
{
	if(usb_release_interface(handle, MY_INTERFACE) != 0) {
		ERR("Releasing interface: usb: %s\n", usb_strerror());
	}
	if(usb_close(handle) != 0) {
		ERR("Closing device: usb: %s\n", usb_strerror());
	}
}

void print_bcd_ver(const struct myeeprom *eeprom)
{
	/* In this case, print only the version. Also note that this 
	 * is an output, and sent to stdout
	 */
	printf("%d.%03d\n", eeprom->release_major, eeprom->release_minor);
	return;
}

void dump_eeprom(const struct myeeprom *eeprom)
{
	const uint8_t	*data = eeprom->serial;

	INFO("Source:  0x%02X\n", eeprom->source);
	INFO("Vendor:  0x%04X\n", eeprom->vendor);
	INFO("Product: 0x%04X\n", eeprom->product);
	INFO("Release: %d.%03d\n", eeprom->release_major, eeprom->release_minor);
	INFO("Data:    0x[%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X]\n",
			data[0], data[1], data[2], data[3],
			data[4], data[5], data[6], data[7]); 
}

void dump_packet(const char *buf, int len)
{
	int	i;

	for(i = 0; i < len; i++)
		INFO("dump: %2d> 0x%02X\n", i, (uint8_t)buf[i]);
}

#ifdef	XORCOM_INTERNAL
int eeprom_set(struct usb_dev_handle *handle, const struct myeeprom *eeprom)
{
	int				ret;
	int				len;
	char				buf[PACKET_SIZE];
	struct fpga_packet_header	*phead = (struct fpga_packet_header *)buf;

	if(verbose >= LOG_INFO)
		INFO("%s Start...\n", __FUNCTION__);
	assert(handle != NULL);
	phead->header.op = EEPROM_SET;
	memcpy(&phead->d.eeprom_set.data, eeprom, EEPROM_SIZE);
	len = sizeof(phead->d.eeprom_set) + sizeof(phead->header.op);
	if(verbose >= LOG_INFO) {
		INFO("%s write %d bytes\n", __FUNCTION__, len);
		dump_packet((char *)phead, len);
	}
	ret = usb_bulk_write(handle, MY_EP_OUT, (char *)phead, len, TIMEOUT);
	if(ret < 0) {
		ERR("usb: bulk_write failed (%d)\n", ret);
		return ret;
	} else if(ret != len) {
		ERR("usb: bulk_write short write (%d)\n", ret);
		return -EFAULT;
	}
	ret = usb_bulk_read(handle, MY_EP_IN, buf, sizeof(buf), TIMEOUT);
	if(ret < 0) {
		ERR("usb: bulk_read failed (%d)\n", ret);
		return ret;
	} else if(ret == 0)
		return 0;
	phead = (struct fpga_packet_header *)buf;
	if(phead->header.op == BAD_COMMAND) {
		ERR("BAD_COMMAND\n");
		return -EINVAL;
	} else if(phead->header.op != EEPROM_SET) {
		ERR("Got unexpected reply op=%d\n", phead->header.op);
		return -EINVAL;
	}
	if(verbose >= LOG_INFO) {
		INFO("%s read %d bytes\n", __FUNCTION__, ret);
		dump_packet(buf, ret);
	}
	return 0;
}
#endif

int eeprom_get(struct usb_dev_handle *handle, struct myeeprom *eeprom)
{
	int				ret;
	int				len;
	char				buf[PACKET_SIZE];
	struct fpga_packet_header	*phead = (struct fpga_packet_header *)buf;

	assert(handle != NULL);
	if(verbose >= LOG_INFO)
		INFO("%s Start...\n", __FUNCTION__);
	phead->header.op = EEPROM_GET;
	len = sizeof(phead->header.op);		/* warning: sending small packet */
	if(verbose >= LOG_INFO) {
		INFO("%s write %d bytes\n", __FUNCTION__, len);
		dump_packet(buf, len);
	}
	ret = usb_bulk_write(handle, MY_EP_OUT, (char *)phead, len, TIMEOUT);
	if(ret < 0) {
		ERR("usb: bulk_write failed (%d)\n", ret);
		return ret;
	} else if(ret != len) {
		ERR("usb: bulk_write short write (%d)\n", ret);
		return -EFAULT;
	}
	ret = usb_bulk_read(handle, MY_EP_IN, buf, sizeof(buf), TIMEOUT);
	if(ret < 0) {
		ERR("usb: bulk_read failed (%d)\n", ret);
		return ret;
	} else if(ret == 0)
		return 0;
	phead = (struct fpga_packet_header *)buf;
	if(phead->header.op == BAD_COMMAND) {
		ERR("BAD_COMMAND\n");
		return -EINVAL;
	} else if(phead->header.op != EEPROM_GET) {
		ERR("Got unexpected reply op=%d\n", phead->header.op);
		return -EINVAL;
	}
	if(verbose >= LOG_INFO) {
		INFO("%s read %d bytes\n", __FUNCTION__, ret);
		dump_packet(buf, ret);
	}
	memcpy(eeprom, &phead->d.eeprom_get.data, EEPROM_SIZE);
	return 0;
}

int send_hexline(struct usb_dev_handle *handle, struct hexline *hexline, int seq)
{
	int				ret;
	int				len;
	uint8_t				*data;
	char				buf[PACKET_SIZE];
	struct fpga_packet_header	*phead = (struct fpga_packet_header *)buf;
	enum fpga_load_status		status;

	assert(handle != NULL);
	assert(hexline != NULL);
	len = hexline->d.content.header.ll;	/* don't send checksum */
	data = hexline->d.content.tt_data.data;
	if(hexline->d.content.header.tt != TT_DATA) {
		ERR("Bad record %d type = %d\n", seq, hexline->d.content.header.tt);
		return -EINVAL;
	}
	phead->header.op = DATA_PACKET;
	phead->d.data_packet.seq = seq;
	phead->d.data_packet.reserved = 0x00;
	memcpy(phead->d.data_packet.data, data, len);
	len += sizeof(hexline->d.content.header);
	if(verbose >= LOG_INFO)
		INFO("%04d+\r", seq);
	ret = usb_bulk_write(handle, MY_EP_OUT, (char *)phead, len, TIMEOUT);
	if(ret < 0) {
		ERR("usb: bulk_write failed (%d)\n", ret);
		return ret;
	} else if(ret != len) {
		ERR("usb: bulk_write short write (%d)\n", ret);
		return -EFAULT;
	}
	if (verbose >= LOG_DEBUG)
		dump_packet((char*)phead, len);
	ret = usb_bulk_read(handle, MY_EP_IN, buf, sizeof(buf), TIMEOUT);
	if(ret < 0) {
		ERR("usb: bulk_read failed (%d)\n", ret);
		return ret;
	} else if(ret == 0)
		return 0;
	if(verbose >= LOG_INFO)
		INFO("%04d-\r", seq);
	phead = (struct fpga_packet_header *)buf;
	if(phead->header.op != STATUS_REPLY) {
		ERR("Got unexpected reply op=%d\n", phead->header.op);
		return -EINVAL;
	}
	status = (enum fpga_load_status)phead->d.status_reply.status;
	switch(status) {
		case FW_TRANS_OK:
		case FW_CONFIG_DONE:
			break;
		case FW_FAIL_RESET:
		case FW_FAIL_TRANS:
			ERR("status reply %s (%d)\n", load_status2str(status), status);
			if(verbose >= LOG_INFO)
				dump_packet(buf, ret);
			return -EPROTO;
		default:
			ERR("Unknown status reply %d\n", status);
			if(verbose >= LOG_INFO)
				dump_packet(buf, ret);
			return -EPROTO;
	}
	return 0;
}

//. returns > 0 - ok, the number of lines sent
//. returns < 0 - error number
int send_splited_hexline(struct usb_dev_handle *handle, struct hexline *hexline, int seq, uint8_t maxwidth)
{
	struct hexline *extraline;
	int linessent = 0;
	int allocsize;
	int extra_offset = 0;
	unsigned int this_line = 0;
	uint8_t bytesleft = 0;
	
	if(!hexline) {
		ERR("Bad record %d type = %d\n", seq, hexline->d.content.header.tt);
		return -EINVAL;
	}
	bytesleft = hexline->d.content.header.ll;
	// split the line into several lines
	while (bytesleft > 0) {
		int status;
		this_line = (bytesleft >= maxwidth) ? maxwidth : bytesleft;
		allocsize = sizeof(struct hexline) + this_line + 1;
		// generate the new line
		if((extraline = (struct hexline *)malloc(allocsize)) == NULL) {
			ERR("Not enough memory for spliting the lines\n" );
			return -EINVAL;
		}
		memset( extraline, 0, allocsize );
		extraline->d.content.header.ll		= this_line;
		extraline->d.content.header.offset	= hexline->d.content.header.offset + extra_offset;
		extraline->d.content.header.tt		= hexline->d.content.header.tt;
		memcpy( extraline->d.content.tt_data.data, hexline->d.content.tt_data.data+extra_offset, this_line);
		status = send_hexline( handle, extraline, seq+linessent );
		// cleanups
		free(extraline);
		extra_offset += this_line;
		bytesleft -= this_line;
		if (status)
			return status;
		linessent++;
	}
	return linessent;
}

int my_usb_device(struct usb_device *dev, usb_dev_handle *handle)
{
	struct usb_device_descriptor	*dev_desc;
	struct usb_config_descriptor	*config_desc;
	struct usb_interface		*interface;
	struct usb_interface_descriptor	*iface_desc;
	struct usb_endpoint_descriptor	*endpoint;
	char				iManufacturer[BUFSIZ];
	char				iProduct[BUFSIZ];
	int				ret;
	int				i;

	assert(dev != NULL);
	dev_desc = &dev->descriptor;
	config_desc = dev->config;
	interface = config_desc->interface;
	iface_desc = interface->altsetting;
	if(verbose >= LOG_INFO)
		INFO("Vendor:Product=%04X:%04X Class=%d (endpoints=%d)\n",
				dev_desc->idVendor, dev_desc->idProduct, dev_desc->bDeviceClass, iface_desc->bNumEndpoints);
	if(iface_desc->bInterfaceClass != 0xFF) {
		ERR("Wrong Interface class %d\n", iface_desc->bInterfaceClass);
		return -EINVAL;
	}
	if(iface_desc->bInterfaceNumber != MY_INTERFACE) {
		ERR("Wrong Interface number %d\n", iface_desc->bInterfaceNumber);
		return -EINVAL;
	}
	if(iface_desc->bNumEndpoints != MY_ENDPOINTS) {
		ERR("Wrong number of endpoints: %d\n", iface_desc->bNumEndpoints);
		return -EINVAL;
	}
	endpoint = iface_desc->endpoint;
	for(i = 0; i < iface_desc->bNumEndpoints; i++, endpoint++) {
		if(endpoint->bEndpointAddress != my_endpoints[i]) {
			ERR("Wrong endpoint %d: address = 0x%X\n", i, endpoint->bEndpointAddress);
			return -EINVAL;
		}
		if(endpoint->bEndpointAddress == MY_EP_OUT || endpoint->bEndpointAddress == MY_EP_IN) {
			if(endpoint->wMaxPacketSize > PACKET_SIZE) {
				ERR("Endpoint #%d wMaxPacketSize too large (%d)\n", i, endpoint->wMaxPacketSize);
				return -EINVAL;
			}
		}
	}
	if(usb_reset(handle) != 0) {
		ERR("Reseting device: usb: %s\n", usb_strerror());
	}
	if(usb_set_configuration(handle, MY_CONFIG) != 0) {
		ERR("usb: %s\n", usb_strerror());
		return -EINVAL;
	}
	if(usb_claim_interface(handle, MY_INTERFACE) != 0) {
		ERR("usb: %s\n", usb_strerror());
		return -EINVAL;
	}
	if(usb_resetep(handle, MY_EP_OUT) != 0) {
		ERR("usb: %s\n", usb_strerror());
		return -EINVAL;
	}
	if(usb_resetep(handle, MY_EP_IN) != 0) {
		ERR("usb: %s\n", usb_strerror());
		return -EINVAL;
	}
	ret = get_usb_string(iManufacturer, BUFSIZ, dev_desc->iManufacturer, handle);
	ret = get_usb_string(iProduct, BUFSIZ, dev_desc->iProduct, handle);
	if(verbose >= LOG_INFO)
		INFO("iManufacturer=%s iProduct=%s\n", iManufacturer, iProduct);
	return 0;
}

int renumerate_device(struct usb_dev_handle *handle)
{
	char				buf[PACKET_SIZE];
	struct fpga_packet_header	*phead = (struct fpga_packet_header *)buf;
	int				ret;

	assert(handle != NULL);
	if(verbose >= LOG_INFO)
		INFO("Renumerating\n");
	phead->header.op = RENUMERATE;
	ret = usb_bulk_write(handle, MY_EP_OUT, (char *)phead, 1, TIMEOUT);
	if(ret < 0) {
		ERR("usb: bulk_write failed (%d)\n", ret);
		return ret;
	} else if(ret != 1) {
		ERR("usb: bulk_write short write (%d)\n", ret);
		return -EFAULT;
	}
	return 0;
}

int fpga_load(struct usb_dev_handle *handle, const struct hexdata *hexdata)
{
	unsigned int	i;
	int		ret;
	int		finished = 0;
	
	assert(handle != NULL);
	if(verbose >= LOG_INFO)
		INFO("Start...\n");
		
	for(i = 0; i < hexdata->maxlines; i++) {
		struct hexline	*hexline = hexdata->lines[i];

		if(!hexline)
			break;
		if(finished) {
			ERR("Extra data after End Of Data Record (line %d)\n", i);
			return 0;
		}
		if(hexline->d.content.header.tt == TT_EOF) {
			INFO("End of data\n");
			finished = 1;
			continue;
		}
		if((ret = send_hexline(handle, hexline, i)) != 0) {
			perror("Failed sending hexline");
			return 0;
		}
	}
	if(verbose >= LOG_INFO)
		INFO("Finished...\n");
	return 1;
}

int fpga_load_usb1(struct usb_dev_handle *handle, const struct hexdata *hexdata)
{
	unsigned int	i,j=0;
	int		ret;
	int		finished = 0;
	
	assert(handle != NULL);
	if(verbose >= LOG_INFO)
		INFO("Start...\n");
		
	// i - is the line number
	// j - is the sequence number, on USB 2, i=j, but on
	//     USB 1 send_splited_hexline may increase the sequence
	//     number, as it needs 
	for(i = 0; i < hexdata->maxlines; i++) {
		struct hexline	*hexline = hexdata->lines[i];
		

		if(!hexline)
			break;
		if(finished) {
			ERR("Extra data after End Of Data Record (line %d)\n", i);
			return 0;
		}
		if(hexline->d.content.header.tt == TT_EOF) {
			INFO("End of data\n");
			finished = 1;
			continue;
		}
		
		if((ret = send_splited_hexline(handle, hexline, j, 60)) < 0) {
			perror("Failed sending hexline (splitting did not help)");
			return 0;
		}
		j += ret;
	}
	if(verbose >= LOG_INFO)
		INFO("Finished...\n");
	return 1;
}

#include <getopt.h>

void usage()
{
	fprintf(stderr, "Usage: %s -D /proc/bus/usb/<bus>/<dev> [options...]\n", progname);
	fprintf(stderr, "\tOptions:\n");
	fprintf(stderr, "\t\t[-b <binfile>]	# output to <binfile>\n");
	fprintf(stderr, "\t\t[-d]		# Get device version from eeprom\n");
	fprintf(stderr, "\t\t[-I <hexfile>]	# Input from <hexfile>\n");
	fprintf(stderr, "\t\t[-g]		# Get eeprom from device\n");
#ifdef XORCOM_INTERNAL
	fprintf(stderr, "\t\t[-C srC byte]	# Set Address sourCe (default: C0)\n");
	fprintf(stderr, "\t\t[-V vendorid]	# Set Vendor id on device\n");
	fprintf(stderr, "\t\t[-P productid]	# Set Product id on device\n");
	fprintf(stderr, "\t\t[-R release]	# Set Release. 2 dot separated decimals\n");
	fprintf(stderr, "\t\t[-S serial]	# Set Serial. 8 comma separated numbers\n");
#endif
	exit(1);
}

static void parse_report_func(int level, const char *msg, ...)
{
	va_list ap;

	va_start(ap, msg);
	if(level <= verbose)
		vfprintf(stderr, msg, ap);
	va_end(ap);
}

int hasUSB2( struct usb_device	*dev )
{
	if (dev->config->interface->altsetting->endpoint->wMaxPacketSize != 512)
		return 0;
	else
		return 1;
}

// usb_interface_descriptor->usb_endpoint_descriptor.wMaxPacketSize

int main(int argc, char *argv[])
{
	struct usb_device	*dev;
	usb_dev_handle		*handle;
	const char		*devpath = NULL;
	const char		*binfile = NULL;
	const char		*hexfile = NULL;
	struct hexdata		*hexdata = NULL;
	struct myeeprom		eeprom_buf;
	int			opt_read_eeprom = 0;
	int			opt_print_bcdver_only = 0;
#ifdef	XORCOM_INTERNAL
	int			opt_write_eeprom = 0;
	char			*vendor = NULL;
	char			*source = NULL;
	int			is_source_given = 0;
	char			*product = NULL;
	char			*release = NULL;
	char			*serial = NULL;
	uint8_t			serial_buf[SERIAL_SIZE];
	const char		options[] = "b:C:dD:ghI:vV:P:R:S:";
#else
	const char		options[] = "b:dD:ghI:v";
#endif
	int			ret = 0;

	progname = argv[0];
	assert(sizeof(struct fpga_packet_header) <= PACKET_SIZE);
	assert(sizeof(struct myeeprom) == EEPROM_SIZE);
	while (1) {
		int	c;

		c = getopt (argc, argv, options);
		if (c == -1)
			break;

		switch (c) {
			case 'D':
				devpath = optarg;
				break;
			case 'b':
				binfile = optarg;
				break;
			case 'd':
				opt_print_bcdver_only = 1;
				opt_read_eeprom = 1;
				break;
			case 'g':
				opt_read_eeprom = 1;
				break;
			case 'I':
				hexfile = optarg;
				break;
#ifdef	XORCOM_INTERNAL
			case 'V':
				vendor = optarg;
				break;
			case 'C':
				source = optarg;
				is_source_given = 1;
				break;
			case 'P':
				product = optarg;
				break;
			case 'R':
				release = optarg;
				break;
			case 'S':
				serial = optarg;
				{
					int		i;
					char		*p;
					unsigned long	val;

					p = strtok(serial, ",");
					for(i = 0; i < SERIAL_SIZE && p; i++) {
						val = strtoul(p, NULL, 0);
						if(val > 0xFF) {
							ERR("Value #%d for -S option is too large (%lu)\n", i+1, val);
							usage();
						}
						serial_buf[i] = val;
						p = strtok(NULL, ",");
					}
					if(i < SERIAL_SIZE) {
						ERR("got only %d values for -S option. Need %d\n", i, SERIAL_SIZE);
						usage();
					}
				}

				break;
#endif
			case 'v':
				verbose++;
				break;
			case 'h':
			default:
				ERR("Unknown option '%c'\n", c);
				usage();
		}
	}

	if (optind != argc) {
		usage();
	}
	if(hexfile) {
		parse_hexfile_set_reporting(parse_report_func);
		hexdata = parse_hexfile(hexfile, MAX_HEX_LINES);
		if(!hexdata) {
			ERR("Bailing out\n");
			exit(1);
		}
		if(binfile) {
			dump_binary(hexdata, binfile);
			return 0;
		}
	}
	if(!devpath) {
		ERR("Missing device path\n");
		usage();
	}
	if(verbose)
		INFO("Startup %s\n", devpath);

	usb_init();
	usb_find_busses();
	usb_find_devices();
	dev = dev_of_path(devpath);
	if(!dev) {
		ERR("Bailing out\n");
		exit(1);
	}
	handle = usb_open(dev);
	if(!handle) {
		ERR("Failed to open usb device '%s/%s': %s\n", dev->bus->dirname, dev->filename, usb_strerror());
		return -ENODEV;
	}
	if(my_usb_device(dev, handle)) {
		ERR("Foreign usb device '%s/%s'\n", dev->bus->dirname, dev->filename);
		ret = -ENODEV;
		goto dev_err;
	}
	
	if(hexdata) {
		int status;

		if (hasUSB2(dev))
			status = fpga_load(handle, hexdata);
		else {
			INFO("Warning: working on a low end USB1 backend\n");
			status = fpga_load_usb1(handle, hexdata);
		}

		if(!status) {
			ERR("FPGA loading failed\n");
			ret = -ENODEV;
			goto dev_err;
		}
		ret = renumerate_device(handle);
		if(ret < 0) {
			ERR("Renumeration failed: errno=%d\n", ret);
			goto dev_err;
		}
	}
#ifdef	XORCOM_INTERNAL
	if(vendor || product || release || serial || source )
		opt_read_eeprom = opt_write_eeprom = 1;
#endif
	if(opt_read_eeprom) {
		ret = eeprom_get(handle, &eeprom_buf);
		if(ret < 0) {
			ERR("Failed reading eeprom: %d\n", ret);
			goto dev_err;
		}
		if (opt_print_bcdver_only)
			print_bcd_ver(&eeprom_buf);
		else
			dump_eeprom(&eeprom_buf);
	}
#ifdef XORCOM_INTERNAL
	if(opt_write_eeprom) {
		// FF: address source is from device. C0: from eeprom
		if (is_source_given)
			eeprom_buf.source = strtoul(source, NULL, 0);
		else
			eeprom_buf.source = 0xC0;
		if(vendor)
			eeprom_buf.vendor = strtoul(vendor, NULL, 0);
		if(product)
			eeprom_buf.product = strtoul(product, NULL, 0);
		if(release) {
			int	release_major = 0;
			int	release_minor = 0;

			sscanf(release, "%d.%d", &release_major, &release_minor);
			eeprom_buf.release_major = release_major;
			eeprom_buf.release_minor = release_minor;
		}
		if(serial) {
			memcpy(eeprom_buf.serial, serial_buf, SERIAL_SIZE);
		}
		dump_eeprom(&eeprom_buf);
		ret = eeprom_set(handle, &eeprom_buf);
		if(ret < 0) {
			ERR("Failed writing eeprom: %d\n", ret);
			goto dev_err;
		}
	}
#endif
	if(verbose)
		INFO("Exiting\n");
dev_err:
	usb_cleanup(handle);
	return ret;
}
