/*
 * libusb_wrap.c
 * $Id: libusb_wrap.c,v 1.25 2017/12/14 17:49:14 rousseau Exp $
 * USB access routines using the libusb library
 *
 * Created by Ludovic Rousseau on Sep 27 2002
 * Copyright (c) 2002 Ludovic Rousseau
 * License: See file COPYING.GPL
 */

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <usb.h>

#include "gempc_ifdhandler.h"
#include "Config.h"
#include "GCdebug.h"
#include "GCUtils.h"
#include "GemCore.h"
#include "libusb_wrap.h"


#define USB_INEP 0
#define USB_OUTEP 1

/* read timeout
 * we must wait enough so that the card can finish its calculation */
#define USB_READ_TIMEOUT 60000	/* 1 minute timeout */

/* write timeout
 * we don't have to wait a long time since the card was doing nothing */
#define USB_WRITE_TIMEOUT 5000	/* 5 seconds timeout */

typedef struct
{
	int kMyVendorID;
	int kMyProductID;
} _usbID;

/*
 * Add an entry to match your reader.
 */
static _usbID UsbIDs[] =
{
	{ 0x08E6, 0x0430 }, /* GemPC 430 */
	{ 0x08E6, 0x0432 },	/* GemPC 432 */
	{ 0x08E6, 0x0435 }	/* GemPC 435 */
};

/* used to store string %s/%s (dirname/filename) like:
 * (/proc/bus/usb/) 001/002 (Linux)
 * /dev/usb0//dev/ (FreeBSD)
 * /dev/usb0//dev/ugen0 (OpenBSD)
 */
#define BUS_DEVICE_STRSIZE 32

typedef struct
{
	usb_dev_handle *handle;
	struct usb_device *dev;
	int interface;

	/*
	 * Endpoints
	 */
	int bulk_in;
	int bulk_out;

} _usbDevice;

#if (PCSCLITE_MAX_READERS-16)
#error Edit this file and set the number of initialiser to PCSCLITE_MAX_READERS (default was 16 but it has changed)
#endif
static _usbDevice usbDevice[PCSCLITE_MAX_READERS] = {
	[ 0 ... (PCSCLITE_MAX_READERS-1) ] = { NULL, NULL, 0, 0, 0 }
};

/*****************************************************************************
 *
 *					OpenUSB
 *
 ****************************************************************************/
status_t OpenUSB(DWORD lun, LPSTR device)
{
	static struct usb_bus *busses = NULL;
	int id, id_number;
	int reader = LunToReaderIndex(lun);
	struct usb_bus *bus;
	struct usb_dev_handle *dev_handle;
	int device_vendor, device_product;

	DEBUG_COMM3("Lun: %lX, Device: %s", lun, device);

	/* device name specified */
	if (device)
	{
		if (strncmp("usb:", device, 4) != 0)
		{
			DEBUG_CRITICAL2("device name does not start with \"usb:\": %s",
				device);
			return STATUS_UNSUCCESSFUL;
		}

		if (sscanf(device, "usb:%x/%x", &device_vendor, &device_product) != 2)
		{
			DEBUG_CRITICAL2("device name can't be parsed: %s", device);
			return STATUS_UNSUCCESSFUL;
		}
	}

	if (busses == NULL)
		usb_init();

	usb_find_busses();
	usb_find_devices();

	busses = usb_get_busses();

	if (busses == NULL)
	{
		DEBUG_CRITICAL("No USB busses found");
		return STATUS_UNSUCCESSFUL;
	}

	/* is the lun already used? */
	if (usbDevice[reader].handle != NULL)
	{
		DEBUG_CRITICAL2("USB driver with lun %lX already in use", lun);
		return STATUS_UNSUCCESSFUL;
	}

	/* find any devide corresponding to a UsbIDs entry */
	id_number = sizeof(UsbIDs)/sizeof(UsbIDs[0]);

	/* for any supported reader */
	for (id=0; id<id_number; id++)
	{
		/* the device was specified but is not the one we are trying to find */
		if (device
			&& (UsbIDs[id].kMyVendorID != device_vendor
			|| UsbIDs[id].kMyProductID != device_product))
			continue;

		/* on any USB buses */
		for (bus = busses; bus; bus = bus->next)
		{
			struct usb_device *dev;

			/* any device on this bus */
			for (dev = bus->devices; dev; dev = dev->next)
			{
				if (dev->descriptor.idVendor == UsbIDs[id].kMyVendorID
					&& dev->descriptor.idProduct == UsbIDs[id].kMyProductID)
				{
					int r, already_used;
					int interface;

					/* is it already opened? */
					already_used = FALSE;

					for (r=0; r<PCSCLITE_MAX_READERS; r++)
					{
						if (usbDevice[r].dev)
						{
							DEBUG_COMM3("Checking device %s/%s",
								bus->dirname, dev->filename);
							if (strcmp(usbDevice[r].dev->bus->dirname, bus->dirname) == 0 && strcmp(usbDevice[r].dev->filename, dev->filename) == 0)
								already_used = TRUE;
						}
					}

					/* this reader is already managed by us */
					if (already_used)
					{
						DEBUG_INFO3("USB device %s/%s already in use. Checking next one.",
							bus->dirname, dev->filename);

						continue;
					}

					DEBUG_COMM3("Trying to open USB device: %s/%s",
						bus->dirname, dev->filename);

					dev_handle = usb_open(dev);
					if (dev_handle == NULL)
					{
						DEBUG_CRITICAL4("Can't usb_open(%s/%s): %s",
							bus->dirname, dev->filename, strerror(errno));
						continue;
					}

					/* now we found a free reader and we try to use it */
					if (dev->config == NULL)
					{
						DEBUG_CRITICAL3("No dev->config found for %s/%s",
							bus->dirname, dev->filename);
						return STATUS_UNSUCCESSFUL;
					}

					interface = dev->config->interface->altsetting->bInterfaceNumber;
					if (usb_claim_interface(dev_handle, interface) < 0)
					{
						DEBUG_CRITICAL4("Can't claim interface %s/%s: %s",
							bus->dirname, dev->filename, strerror(errno));
						return STATUS_UNSUCCESSFUL;
					}

					DEBUG_COMM3("Using USB device: %s/%s",
						bus->dirname, dev->filename);

					/* store device information */
					usbDevice[reader].handle = dev_handle;
					usbDevice[reader].dev = dev;
					usbDevice[reader].interface = interface;

					usbDevice[reader].bulk_in = usbDevice[reader].dev->config->interface->altsetting->endpoint[USB_INEP].bEndpointAddress;
					usbDevice[reader].bulk_out = usbDevice[reader].dev->config->interface->altsetting->endpoint[USB_OUTEP].bEndpointAddress;

					goto end;
				}
			}
		}
	}

	if (usbDevice[reader].handle == NULL)
		return STATUS_UNSUCCESSFUL;
end:
	return STATUS_SUCCESS;
} /* OpenUSB */


/*****************************************************************************
 *
 *					WriteUSB
 *
 ****************************************************************************/
status_t WriteUSB(DWORD lun, DWORD length, unsigned char *buffer)
{
	int rv;
	int reader = LunToReaderIndex(lun);
	char debug_header[] = "-> 121234 ";

	sprintf(debug_header, "-> %06X ", (int)lun);

	DEBUG_XXD(debug_header, buffer, length);

	rv = usb_bulk_write(usbDevice[reader].handle, usbDevice[reader].bulk_out,
		(char *)buffer, length, USB_WRITE_TIMEOUT);

	if (rv < 0)
	{
		if (usbDevice[reader].dev->bus)
		{
			DEBUG_CRITICAL4("%s/%s: %s", usbDevice[reader].dev->bus->dirname,
				usbDevice[reader].dev->filename, strerror(errno));

			/* reset in case of timeout (usually means a reader freeze)
			 * The application will receive an error but can try to reconnect.
			 * Without the patch the reader must be physically
			 * unpluged-repluged */
			if (errno == ETIMEDOUT)
			{
				DEBUG_CRITICAL3("Resetting the reader: %s/%s",
					usbDevice[reader].dev->bus->dirname,
					usbDevice[reader].dev->filename);
				usb_reset(usbDevice[reader].handle);
			}
		}
		else
			DEBUG_CRITICAL2("usb_bulk_write(no device): %s", strerror(errno));

		return STATUS_UNSUCCESSFUL;
	}

	return STATUS_SUCCESS;
} /* WriteUSB */


/*****************************************************************************
 *
 *					ReadUSB
 *
 ****************************************************************************/
status_t ReadUSB(DWORD lun, DWORD * length, unsigned char *buffer)
{
	int rv;
	int reader = LunToReaderIndex(lun);
	char debug_header[] = "<- 121234 ";

	sprintf(debug_header, "<- %06X ", (int)lun);


	rv = usb_bulk_read(usbDevice[reader].handle, usbDevice[reader].bulk_in,
		(char *)buffer, *length, USB_READ_TIMEOUT);
	*length = rv;

	if (rv < 0)
	{
		DEBUG_CRITICAL4("%s/%s: %s", usbDevice[reader].dev->bus->dirname,
			usbDevice[reader].dev->filename, strerror(errno));
		return STATUS_UNSUCCESSFUL;
	}

	DEBUG_XXD(debug_header, buffer, *length);

	return STATUS_SUCCESS;
} /* ReadUSB */


/*****************************************************************************
 *
 *					CloseUSB
 *
 ****************************************************************************/
status_t CloseUSB(DWORD lun)
{
	int reader = LunToReaderIndex(lun);

	DEBUG_COMM3("device: %s/%s", usbDevice[reader].dev->bus->dirname,
		usbDevice[reader].dev->filename);

	usb_release_interface(usbDevice[reader].handle,
		usbDevice[reader].interface);
	usb_close(usbDevice[reader].handle);

	/* mark the resource unused */
	usbDevice[reader].handle = NULL;
	usbDevice[reader].dev = NULL;

	return STATUS_SUCCESS;
} /* CloseUSB */

