/*
 * Written by Oron Peled <oron@actcom.co.il>
 * Copyright (C) 2006-2008, Xorcom
 *
 * All rights reserved.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#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 7471 2009-11-04 16:36:21Z tzafrir $";

#define	ERR(fmt, arg...) do { \
				if(verbose >= LOG_ERR) \
					fprintf(stderr, "%s: ERROR (%d): " fmt, \
						progname, __LINE__, ## arg); \
			} while(0);
#define	INFO(fmt, arg...) do { \
				if(verbose >= LOG_INFO) \
					fprintf(stderr, "%s: " fmt, \
						progname, ## arg); \
			} while(0);
#define	DBG(fmt, arg...) do { \
				if(verbose >= LOG_DEBUG) \
					fprintf(stderr, "%s: DBG: " fmt, \
						progname, ## arg); \
			} while(0);

static int	verbose = LOG_WARNING;
static char	*progname;
static int	disconnected = 0;

#define	MAX_HEX_LINES	10000
#define	PACKET_SIZE	512
#define	EEPROM_SIZE	16
#define	LABEL_SIZE	8
#define	TIMEOUT		5000


/* My device parameters */
#define	MY_EP_OUT	0x04
#define	MY_EP_IN	0x88

#define	FPGA_EP_OUT	0x02
#define	FPGA_EP_IN	0x86

/* USB firmware types */
#define	USB_11xx	0
#define	USB_FIRMWARE_II	1

#define	TYPE_ENTRY(t,ni,n,ne,out,in,...)	\
	[t] = {					\
		.type_code = (t),		\
		.num_interfaces = (ni),		\
		.my_interface_num = (n),	\
		.num_endpoints = (ne),		\
		.my_ep_in = (in),		\
		.my_ep_out = (out),		\
		.name = #t,			\
		.endpoints = { __VA_ARGS__ },	\
		}

static const struct astribank_type {
	int	type_code;
	int	num_interfaces;
	int	my_interface_num;
	int	num_endpoints;
	int	my_ep_out;
	int	my_ep_in;
	char	*name;
	int	endpoints[4];	/* for matching */
} astribank_types[] = {
	TYPE_ENTRY(USB_11xx,		1, 0, 4, MY_EP_OUT, MY_EP_IN,
		FPGA_EP_OUT,
		MY_EP_OUT,
		FPGA_EP_IN,
		MY_EP_IN),
	TYPE_ENTRY(USB_FIRMWARE_II,	2, 1, 2, MY_EP_OUT, MY_EP_IN,
		MY_EP_OUT,
		MY_EP_IN),
};
#undef TYPE_ENTRY

enum fpga_load_packet_types {
	PT_STATUS_REPLY	= 0x01,
	PT_DATA_PACKET	= 0x01,
#ifdef	XORCOM_INTERNAL
	PT_EEPROM_SET	= 0x04,
#endif
	PT_EEPROM_GET	= 0x08,
	PT_RENUMERATE	= 0x10,
	PT_RESET	= 0x20,
	PT_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		label[LABEL_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
};

struct my_usb_device {
	struct usb_device	*dev;
	usb_dev_handle		*handle;
	int			my_interface_num;
	int			my_ep_out;
	int			my_ep_in;
	char			iManufacturer[BUFSIZ];
	char			iProduct[BUFSIZ];
	char			iSerialNumber[BUFSIZ];
	char			iInterface[BUFSIZ];
	int			is_usb2;
	struct myeeprom		eeprom;
	const struct astribank_type	*abtype;
};

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";
	}
}

/* return 1 if:
 * - str has a number
 * - It is larger than 0
 * - It equals num
 */
int num_matches(int num, const char* str) {
	int str_val = atoi(str);
	if (str_val <= 0)
		return 0;
	return (str_val == num);
}

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		*p;
	int			bnum;
	int			dnum;
	int			ret;

	assert(path != NULL);
	if(access(path, F_OK) < 0) {
		perror(path);
		return NULL;
	}
	/* Find last '/' */
	if((p = (const char *)memrchr(path, '/', strlen(path))) == NULL) {
		ERR("Missing a '/' in %s\n", path);
		return NULL;
	}
	/* Get the device number */
	ret = sscanf(p + 1, "%d", &dnum);
	if(ret != 1) {
		ERR("Path tail is not a device number: '%s'\n", p);
		return NULL;
	}
	/* Search for a '/' before that */
	p = (const char *)memrchr(path, '/', p - path);
	if(p == NULL)
		p = path;		/* Relative path */
	else
		p++;			/* skip '/' */
	/* Get the bus number */
	ret = sscanf(p, "%d", &bnum);
	if(ret != 1) {
		ERR("Path tail is not a bus number: '%s'\n", p);
		return NULL;
	}
	sprintf(dirname, "%03d", bnum);
	sprintf(filename, "%03d", dnum);
	for (bus = usb_busses; bus; bus = bus->next) {
		if (! num_matches(bnum, bus->dirname))
		//if(strcmp(bus->dirname, dirname) != 0)
			continue;
		for (dev = bus->devices; dev; dev = dev->next) {
			//if(strcmp(dev->filename, filename) == 0)
			if (num_matches(dnum, dev->filename))
				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);
}

void my_usb_device_cleanup(struct my_usb_device *mydev)
{
	assert(mydev != NULL);
	if(!mydev->handle) {
		return;	/* Nothing to do */
	}
	if(!disconnected) {
		if(usb_release_interface(mydev->handle, mydev->abtype->my_interface_num) != 0) {
			ERR("Releasing interface: usb: %s\n", usb_strerror());
		}
	}
	if(usb_close(mydev->handle) != 0) {
		ERR("Closing device: usb: %s\n", usb_strerror());
	}
	disconnected = 1;
	mydev->handle = NULL;
}

static void show_device_info(const struct my_usb_device *mydev)
{
	const struct myeeprom	*eeprom;
	uint8_t		data[LABEL_SIZE + 1];

	assert(mydev != NULL);
	eeprom = &mydev->eeprom;
	memset(data, 0, LABEL_SIZE + 1);
	memcpy(data, eeprom->label, LABEL_SIZE);
	printf("USB    Firmware Type: [%s]\n", mydev->abtype->name);
	printf("USB    iManufacturer: [%s]\n", mydev->iManufacturer);
	printf("USB    iProduct:      [%s]\n", mydev->iProduct);
	printf("USB    iSerialNumber: [%s]\n", mydev->iSerialNumber);
	printf("EEPROM Source:        0x%02X\n", eeprom->source);
	printf("EEPROM Vendor:        0x%04X\n", eeprom->vendor);
	printf("EEPROM Product:       0x%04X\n", eeprom->product);
	printf("EEPROM Release:       %d.%03d\n", eeprom->release_major, eeprom->release_minor);
	printf("EEPROM Label:        HEX(%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X) [%s]\n",
			data[0], data[1], data[2], data[3],
			data[4], data[5], data[6], data[7], data); 
}

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

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

int send_usb(const char *msg, struct my_usb_device *mydev, struct fpga_packet_header *phead, int len, int timeout)
{
	char	*p = (char *)phead;
	int	ret;

	if(verbose >= LOG_DEBUG)
		dump_packet(msg, p, len);
	if(mydev->my_ep_out & USB_ENDPOINT_IN) {
		ERR("send_usb called with an input endpoint 0x%x\n", mydev->my_ep_out);
		return -EINVAL;
	}
	ret = usb_bulk_write(mydev->handle, mydev->my_ep_out, p, len, timeout);
	if(ret < 0) {
		/*
		 * If the device was gone, it may be the
		 * result of renumeration. Ignore it.
		 */
		if(ret != -ENODEV) {
			ERR("bulk_write to endpoint 0x%x failed: %s\n", mydev->my_ep_out, usb_strerror());
			dump_packet("send_usb[ERR]", p, len);
		} else {
			disconnected = 1;
			my_usb_device_cleanup(mydev);
		}
		return ret;
	} else if(ret != len) {
		ERR("bulk_write to endpoint 0x%x short write: %s\n", mydev->my_ep_out, usb_strerror());
		dump_packet("send_usb[ERR]", p, len);
		return -EFAULT;
	}
	return ret;
}

int recv_usb(const char *msg, struct my_usb_device *mydev, char *buf, size_t len, int timeout)
{
	int	ret;

	if(mydev->my_ep_in & USB_ENDPOINT_OUT) {
		ERR("recv_usb called with an output endpoint 0x%x\n", mydev->my_ep_in);
		return -EINVAL;
	}
	ret = usb_bulk_read(mydev->handle, mydev->my_ep_in, buf, len, timeout);
	if(ret < 0) {
		ERR("bulk_read from endpoint 0x%x failed: %s\n", mydev->my_ep_in, usb_strerror());
		return ret;
	}
	if(verbose >= LOG_DEBUG)
		dump_packet(msg, buf, ret);
	return ret;
}

int flush_read(struct my_usb_device *mydev)
{
	char		tmpbuf[BUFSIZ];
	int		ret;

	memset(tmpbuf, 0, BUFSIZ);
	ret = recv_usb("flush_read", mydev, tmpbuf, sizeof(tmpbuf), TIMEOUT);
	if(ret < 0 && ret != -ETIMEDOUT) {
		ERR("ret=%d\n", ret);
		return ret;
	} else if(ret > 0) {
		DBG("Got %d bytes:\n", ret);
		dump_packet(__FUNCTION__, tmpbuf, ret);
	}
	return 0;
}

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

	DBG("%s Start...\n", __FUNCTION__);
	assert(mydev != NULL);
	phead->header.op = PT_EEPROM_SET;
	memcpy(&phead->d.eeprom_set.data, eeprom, EEPROM_SIZE);
	len = sizeof(phead->d.eeprom_set) + sizeof(phead->header.op);
	ret = send_usb("eeprom_set[W]", mydev, phead, len, TIMEOUT);
	if(ret < 0)
		return ret;
	ret = recv_usb("eeprom_set[R]", mydev, buf, sizeof(buf), TIMEOUT);
	if(ret <= 0)
		return ret;
	phead = (struct fpga_packet_header *)buf;
	if(phead->header.op == PT_BAD_COMMAND) {
		ERR("Firmware rejected PT_EEPROM_SET command\n");
		return -EINVAL;
	} else if(phead->header.op != PT_EEPROM_SET) {
		ERR("Got unexpected reply op=%d\n", phead->header.op);
		return -EINVAL;
	}
	return 0;
}
#endif

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

	assert(mydev != NULL);
	eeprom = &mydev->eeprom;
	DBG("%s Start...\n", __FUNCTION__);
	phead->header.op = PT_EEPROM_GET;
	len = sizeof(phead->header.op);		/* warning: sending small packet */
	ret = send_usb("eeprom_get[W]", mydev, phead, len, TIMEOUT);
	if(ret < 0)
		return ret;
	ret = recv_usb("eeprom_get[R]", mydev, buf, sizeof(buf), TIMEOUT);
	if(ret <= 0)
		return ret;
	phead = (struct fpga_packet_header *)buf;
	if(phead->header.op == PT_BAD_COMMAND) {
		ERR("PT_BAD_COMMAND\n");
		return -EINVAL;
	} else if(phead->header.op != PT_EEPROM_GET) {
		ERR("Got unexpected reply op=%d\n", phead->header.op);
		return -EINVAL;
	}
	memcpy(eeprom, &phead->d.eeprom_get.data, EEPROM_SIZE);
	return 0;
}

int send_hexline(struct my_usb_device *mydev, 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(mydev != NULL);
	assert(hexline != NULL);
	if(hexline->d.content.header.tt != TT_DATA) {
		DBG("Non data record %d type = %d\n", seq, hexline->d.content.header.tt);
		return 0;
	}
	len = hexline->d.content.header.ll;	/* don't send checksum */
	data = hexline->d.content.tt_data.data;
	phead->header.op = PT_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);
	DBG("%04d+\r", seq);
	ret = send_usb("hexline[W]", mydev, phead, len, TIMEOUT);
	if(ret < 0)
		return ret;
	ret = recv_usb("hexline[R]", mydev, buf, sizeof(buf), TIMEOUT);
	if(ret <= 0)
		return ret;
	DBG("%04d-\r", seq);
	phead = (struct fpga_packet_header *)buf;
	if(phead->header.op != PT_STATUS_REPLY) {
		ERR("Got unexpected reply op=%d\n", phead->header.op);
		dump_packet("hexline[ERR]", buf, ret);
		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);
			dump_packet("hexline[ERR]", buf, ret);
			return -EPROTO;
		default:
			ERR("Unknown status reply %d\n", status);
			dump_packet("hexline[ERR]", buf, ret);
			return -EPROTO;
	}
	return 0;
}

//. returns > 0 - ok, the number of lines sent
//. returns < 0 - error number
int send_splited_hexline(struct my_usb_device *mydev, 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;
	
	assert(mydev != NULL);
	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(mydev, extraline, seq+linessent );
		// cleanups
		free(extraline);
		extra_offset += this_line;
		bytesleft -= this_line;
		if (status)
			return status;
		linessent++;
	}
	return linessent;
}

int match_usb_device_identity(const struct usb_config_descriptor *config_desc,
	const struct astribank_type *ab)
{
	struct usb_interface		*interface;
	struct usb_interface_descriptor	*iface_desc;

	if(config_desc->bNumInterfaces <= ab->my_interface_num)
		return 0;
	interface = &config_desc->interface[ab->my_interface_num];
	iface_desc = interface->altsetting;
	
	return	iface_desc->bInterfaceClass == 0xFF &&
		iface_desc->bInterfaceNumber == ab->my_interface_num &&
		iface_desc->bNumEndpoints == ab->num_endpoints;
}

const struct astribank_type *my_usb_device_identify(const char devpath[], struct my_usb_device *mydev)
{
	struct usb_device_descriptor	*dev_desc;
	struct usb_config_descriptor	*config_desc;
	int				i;

	assert(mydev != NULL);
	usb_init();
	usb_find_busses();
	usb_find_devices();
	mydev->dev = dev_of_path(devpath);
	if(!mydev->dev) {
		ERR("Bailing out\n");
		return 0;
	}
	dev_desc = &mydev->dev->descriptor;
	config_desc = mydev->dev->config;
	for(i = 0; i < sizeof(astribank_types)/sizeof(astribank_types[0]); i++) {
		if(match_usb_device_identity(config_desc, &astribank_types[i])) {
			DBG("Identified[%d]: interfaces=%d endpoints=%d: \"%s\"\n",
				i,
				astribank_types[i].num_interfaces,
				astribank_types[i].num_endpoints,
				astribank_types[i].name);
			return &astribank_types[i];
		}
	}
	return NULL;
}

int my_usb_device_init(const char devpath[], struct my_usb_device *mydev, const struct astribank_type *abtype)
{
	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;
	int				ret;
	int				i;

	assert(mydev != NULL);
	usb_init();
	usb_find_busses();
	usb_find_devices();
	mydev->dev = dev_of_path(devpath);
	if(!mydev->dev) {
		ERR("Bailing out\n");
		return 0;
	}
	mydev->handle = usb_open(mydev->dev);
	if(!mydev->handle) {
		ERR("Failed to open usb device '%s/%s': %s\n", mydev->dev->bus->dirname, mydev->dev->filename, usb_strerror());
		return 0;
	}
	if(usb_claim_interface(mydev->handle, abtype->my_interface_num) != 0) {
		ERR("usb_claim_interface: %s\n", usb_strerror());
		return 0;
	}
	dev_desc = &mydev->dev->descriptor;
	config_desc = mydev->dev->config;
	if (!config_desc) {
		ERR("usb interface without a configuration\n");
		return 0;
	}
	interface = &config_desc->interface[abtype->my_interface_num];
	iface_desc = interface->altsetting;
	endpoint = iface_desc->endpoint;
	mydev->is_usb2 = (endpoint->wMaxPacketSize == 512);
	for(i = 0; i < iface_desc->bNumEndpoints; i++, endpoint++) {
		if(endpoint->bEndpointAddress != abtype->endpoints[i]) {
			ERR("Wrong endpoint 0x%X (at index %d)\n", endpoint->bEndpointAddress, i);
			return 0;
		}
		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 0;
			}
		}
	}
	mydev->abtype = abtype;
	mydev->my_ep_in = abtype->my_ep_in;
	mydev->my_ep_out = abtype->my_ep_out;
	ret = get_usb_string(mydev->iManufacturer, BUFSIZ, dev_desc->iManufacturer, mydev->handle);
	ret = get_usb_string(mydev->iProduct, BUFSIZ, dev_desc->iProduct, mydev->handle);
	ret = get_usb_string(mydev->iSerialNumber, BUFSIZ, dev_desc->iSerialNumber, mydev->handle);
	ret = get_usb_string(mydev->iInterface, BUFSIZ, iface_desc->iInterface, mydev->handle);
	INFO("ID=%04X:%04X Manufacturer=[%s] Product=[%s] SerialNumber=[%s] Interface=[%s]\n",
		dev_desc->idVendor,
		dev_desc->idProduct,
		mydev->iManufacturer,
		mydev->iProduct,
		mydev->iSerialNumber,
		mydev->iInterface);
	if(usb_clear_halt(mydev->handle, mydev->my_ep_out) != 0) {
		ERR("Clearing output endpoint: %s\n", usb_strerror());
		return 0;
	}
	if(usb_clear_halt(mydev->handle, mydev->my_ep_in) != 0) {
		ERR("Clearing input endpoint: %s\n", usb_strerror());
		return 0;
	}
	if(flush_read(mydev) < 0) {
		ERR("flush_read failed\n");
		return 0;
	}
	return 1;
}

int renumerate_device(struct my_usb_device *mydev, enum fpga_load_packet_types pt)
{
	char				buf[PACKET_SIZE];
	struct fpga_packet_header	*phead = (struct fpga_packet_header *)buf;
	int				ret;

	assert(mydev != NULL);
	DBG("Renumerating with 0x%X\n", pt);
	phead->header.op = pt;
	ret = send_usb("renumerate[W]", mydev, phead, 1, TIMEOUT);
	if(ret < 0 && ret != -ENODEV)
			return ret;
#if 0
	/*
	 * FIXME: we count on our USB firmware to reset the device... should we?
	 */
	ret = usb_reset(mydev->handle);
	if(ret < 0) {
		ERR("usb_reset: %s\n", usb_strerror());
		return -ENODEV;
	}
#endif
	return 0;
}

/*
 * Returns: true on success, false on failure
 */
int fpga_load(struct my_usb_device *mydev, const struct hexdata *hexdata)
{
	unsigned int	i;
	unsigned int	j = 0;
	int		ret;
	int		finished = 0;
	const char	*v = hexdata->version_info;
	
	v = (v[0]) ? v : "Unknown";
	assert(mydev != NULL);
	INFO("FPGA_LOAD (version %s)\n", v);
	/*
	 * 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) {
			DBG("End of data\n");
			finished = 1;
			continue;
		}
		if(mydev->is_usb2) {
			if((ret = send_hexline(mydev, hexline, i)) != 0) {
				perror("Failed sending hexline");
				return 0;
			}
		} else {
			if((ret = send_splited_hexline(mydev, hexline, j, 60)) < 0) {
				perror("Failed sending hexline (splitting did not help)");
				return 0;
			}
			j += ret;
		}
	}
	DBG("Finished...\n");
	return 1;
}

#include <getopt.h>

void usage()
{
	fprintf(stderr, "Usage: %s -D {/proc/bus/usb|/dev/bus/usb}/<bus>/<dev> [options...]\n", progname);
	fprintf(stderr, "\tOptions:\n");
	fprintf(stderr, "\t\t[-r]		# Reset the device\n");
	fprintf(stderr, "\t\t[-b <binfile>]	# Output to <binfile>\n");
	fprintf(stderr, "\t\t[-I <hexfile>]	# Input from <hexfile>\n");
	fprintf(stderr, "\t\t[-H <hexfile>]	# Output to <hexfile> ('-' is stdout)\n");
	fprintf(stderr, "\t\t[-i]		# Show hexfile information\n");
	fprintf(stderr, "\t\t[-g]		# Get eeprom from device\n");
	fprintf(stderr, "\t\t[-v]		# Increase verbosity\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[-L label]		# Set label.\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);
}

#ifdef	XORCOM_INTERNAL
static void eeprom_fill(struct myeeprom *myeeprom,
	const char vendor[],
	const char product[],
	const char release[],
	const char label[],
	const char source[])
{
	// FF: address source is from device. C0: from eeprom
	if (source)
		myeeprom->source = strtoul(source, NULL, 0);
	else
		myeeprom->source = 0xC0;
	if(vendor)
		myeeprom->vendor = strtoul(vendor, NULL, 0);
	if(product)
		myeeprom->product = strtoul(product, NULL, 0);
	if(release) {
		int	release_major = 0;
		int	release_minor = 0;

		sscanf(release, "%d.%d", &release_major, &release_minor);
		myeeprom->release_major = release_major;
		myeeprom->release_minor = release_minor;
	}
	if(label) {
		/* padding */
		memset(myeeprom->label, 0, LABEL_SIZE);
		memcpy(myeeprom->label, label, strlen(label));
	}
}
#endif

int main(int argc, char *argv[])
{
	const struct astribank_type	*abtype;
	struct my_usb_device	mydev;
	const char		*devpath = NULL;
	const char		*binfile = NULL;
	const char		*inhexfile = NULL;
	const char		*outhexfile = NULL;
	struct hexdata		*hexdata = NULL;
	int			opt_reset = 0;
	int			opt_info = 0;
	int			opt_read_eeprom = 0;
	int			opt_output_width = 0;
	int			output_is_set = 0;
#ifdef	XORCOM_INTERNAL
	int			opt_write_eeprom = 0;
	char			*vendor = NULL;
	char			*source = NULL;
	char			*product = NULL;
	char			*release = NULL;
	char			*label = NULL;
	const char		options[] = "rib:D:ghH:I:vw:C:V:P:R:S:";
#else
	const char		options[] = "rib:D:ghH:I:vw:";
#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;
				if(output_is_set++) {
					ERR("Cannot set -D. Another output option is already selected\n");
					return 1;
				}
				break;
			case 'r':
				opt_reset = 1;
				break;
			case 'i':
				opt_info = 1;
				break;
			case 'b':
				binfile = optarg;
				if(output_is_set++) {
					ERR("Cannot set -b. Another output option is already selected\n");
					return 1;
				}
				break;
			case 'g':
				opt_read_eeprom = 1;
				break;
			case 'H':
				outhexfile = optarg;
				if(output_is_set++) {
					ERR("Cannot set -H. Another output option is already selected\n");
					return 1;
				}
				break;
			case 'I':
				inhexfile = optarg;
				break;
#ifdef	XORCOM_INTERNAL
			case 'V':
				vendor = optarg;
				break;
			case 'C':
				source = optarg;
				break;
			case 'P':
				product = optarg;
				break;
			case 'R':
				release = optarg;
				break;
			case 'S':
				label = optarg;
				{
					const char	GOOD_CHARS[] =
						"abcdefghijklmnopqrstuvwxyz"
						"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
						"0123456789"
						"-_.";
					int	len = strlen(label);
					int	goodlen = strspn(label, GOOD_CHARS);

					if(len > LABEL_SIZE) {
						ERR("Label too long (%d > %d)\n", len, LABEL_SIZE);
						usage();
					}
					if(goodlen != len) {
						ERR("Bad character in label number (pos=%d)\n", goodlen);
						usage();
					}
				}
				break;
#endif
			case 'w':
				opt_output_width = strtoul(optarg, NULL, 0);
				break;
			case 'v':
				verbose++;
				break;
			case 'h':
			default:
				ERR("Unknown option '%c'\n", c);
				usage();
		}
	}

	if (optind != argc) {
		usage();
	}
	if(inhexfile) {
#ifdef	XORCOM_INTERNAL
		if(vendor || product || release || label || source ) {
			ERR("The -I option is exclusive of -[VPRSC]\n");
			return 1;
		}
#endif
		parse_hexfile_set_reporting(parse_report_func);
		hexdata = parse_hexfile(inhexfile, MAX_HEX_LINES);
		if(!hexdata) {
			ERR("Bailing out\n");
			exit(1);
		}
		if(opt_info) {
			printf("%s: Version=%s Checksum=%d\n",
					inhexfile, hexdata->version_info,
					bsd_checksum(hexdata));
		}
		if(binfile) {
			dump_binary(hexdata, binfile);
			return 0;
		}
		if(outhexfile) {
			if(opt_output_width)
				dump_hexfile2(hexdata, outhexfile, opt_output_width);
			else
				dump_hexfile(hexdata, outhexfile);
			return 0;
		}
	}
#ifdef	XORCOM_INTERNAL
	else if(vendor || product || release || label || source ) {
		if(outhexfile) {
			FILE	*fp;

			if(strcmp(outhexfile, "-") == 0)
				fp = stdout;
			else if((fp = fopen(outhexfile, "w")) == NULL) {
				perror(outhexfile);
				return 1;
			}
			memset(&mydev.eeprom, 0, sizeof(struct myeeprom));
			eeprom_fill(&mydev.eeprom, vendor, product, release, label, source);
			gen_hexline((uint8_t *)&mydev.eeprom, 0, sizeof(mydev.eeprom), fp);
			gen_hexline(NULL, 0, 0, fp);	/* EOF */
			return 0;
		}
	}
#endif
	if(!devpath) {
		ERR("Missing device path\n");
		usage();
	}
	DBG("Startup %s\n", devpath);
	if((abtype = my_usb_device_identify(devpath, &mydev)) == NULL) {
		ERR("Bad device. Does not match our types.\n");
		usage();
	}
	INFO("FIRMWARE: %s (type=%d)\n", abtype->name, abtype->type_code);
	if(!my_usb_device_init(devpath, &mydev, abtype)) {
		ERR("Failed to initialize USB device '%s'\n", devpath);
		ret = -ENODEV;
		goto dev_err;
	}
	ret = eeprom_get(&mydev);
	if(ret < 0) {
		ERR("Failed reading eeprom\n");
		goto dev_err;
	}
#ifdef	XORCOM_INTERNAL
	if(vendor || product || release || label || source ) {
		eeprom_fill(&mydev.eeprom, vendor, product, release, label, source);
		opt_write_eeprom = 1;
		opt_read_eeprom = 1;
	}
#endif
	if(opt_read_eeprom) {
		show_device_info(&mydev);
	}
	if(hexdata) {
		if (!mydev.is_usb2)
			INFO("Warning: working on a low end USB1 backend\n");
		if(!fpga_load(&mydev, hexdata)) {
			ERR("FPGA loading failed\n");
			ret = -ENODEV;
			goto dev_err;
		}
		ret = renumerate_device(&mydev, PT_RENUMERATE);
		if(ret < 0) {
			ERR("Renumeration failed: errno=%d\n", ret);
			goto dev_err;
		}
	}
#ifdef XORCOM_INTERNAL
	else if(opt_write_eeprom) {
		if(abtype->type_code == USB_FIRMWARE_II) {
			ERR("No EEPROM burning command in %s. Use fxload for that\n",
				abtype->name);
			goto dev_err;
		}
		ret = eeprom_set(&mydev, &mydev.eeprom);
		if(ret < 0) {
			ERR("Failed writing eeprom: %s\n", strerror(-ret));
			goto dev_err;
		}
		printf("------- RESULTS -------\n");
		show_device_info(&mydev);
	}
#endif
	if(opt_reset) {
		DBG("Reseting to default\n");
		ret = renumerate_device(&mydev, PT_RESET);
		if(ret < 0) {
			ERR("Renumeration to default failed: errno=%d\n", ret);
			goto dev_err;
		}
	}
	DBG("Exiting\n");
dev_err:
	my_usb_device_cleanup(&mydev);
	return ret;
}
