/*****************************************************************************/

/*
 *	usbdrvlinux.c  --  Linux USB driver interface.
 *
 *	Copyright (C) 1999-2001
 *          Thomas Sailer (t.sailer@alumni.ethz.ch)
 *
 *	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.
 *
 *  $Id: usbdrvlinux.c,v 1.4 2000/01/12 15:54:07 tom Exp $	 
 *
 *  History:
 *   0.1  23.06.1999  Created
 *   0.2  07.01.2000  Expanded to usbdevfs capabilities
 *
 */

/*****************************************************************************/

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include "usbdrv.h"

/* --------------------------------------------------------------------- */

struct usbdevice {
	int fd;
	struct usb_device_descriptor desc;
};

static char usb_devicefs_mountpoint[256] = "/proc/bus/usb";

/* --------------------------------------------------------------------- */

/*
 * Parse and show the different USB descriptors.
 */
void usb_show_device_descriptor(FILE *f, struct usb_device_descriptor *desc)
{
        fprintf(f, "  Length              = %2d%s\n", desc->bLength,
		desc->bLength == USB_DT_DEVICE_SIZE ? "" : " (!!!)");
        fprintf(f, "  DescriptorType      = %02x\n", desc->bDescriptorType);
        fprintf(f, "  USB version         = %x.%02x\n",
	       desc->bcdUSB[1], desc->bcdUSB[0]);
        fprintf(f, "  Vendor:Product      = %02x%02x:%02x%02x\n",
	       desc->idVendor[1], desc->idVendor[0], desc->idProduct[1], desc->idProduct[0]);
        fprintf(f, "  MaxPacketSize0      = %d\n", desc->bMaxPacketSize0);
        fprintf(f, "  NumConfigurations   = %d\n", desc->bNumConfigurations);
        fprintf(f, "  Device version      = %x.%02x\n",
		desc->bcdDevice[1], desc->bcdDevice[0]);
        fprintf(f, "  Device Class:SubClass:Protocol = %02x:%02x:%02x\n",
		desc->bDeviceClass, desc->bDeviceSubClass, desc->bDeviceProtocol);
        switch (desc->bDeviceClass) {
        case 0:
                fprintf(f, "    Per-interface classes\n");
                break;
        case 9:
                fprintf(f, "    Hub device class\n");
                break;
        case 0xff:
                fprintf(f, "    Vendor class\n");
                break;
        default:
                fprintf(f, "    Unknown class\n");
        }
}

/* ---------------------------------------------------------------------- */

void usb_setmountpoint(const char *mnt)
{
	if (!mnt)
		mnt = "/proc/bus/usb";
	strncpy(usb_devicefs_mountpoint, mnt, sizeof(usb_devicefs_mountpoint));
}

const char *usb_getmountpoint(void)
{
	return usb_devicefs_mountpoint;
}

void usb_close(struct usbdevice *dev)
{
	if (!dev)
		return;
	close(dev->fd);
	lprintf(20, "usb_close: fd %d\n", dev->fd);
	free(dev);
}

static int parsedev(int fd, unsigned int *bus, unsigned int *dev, int vendorid, int productid, unsigned int index)
{
	char buf[16384];
	char *start, *end, *lineend, *cp;
	int devnum = -1, busnum = -1, vendor = -1, product = -1;
	int ret;

	if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
		return -1;
	ret = read(fd, buf, sizeof(buf)-1);
        if (ret == -1)
                return -1;
        end = buf + ret;
        *end = 0;
        start = buf;
	ret = 0;
        while (start < end) {
                lineend = strchr(start, '\n');
                if (!lineend)
                        break;
                *lineend = 0;
                switch (start[0]) {
		case 'T':  /* topology line */
                        if ((cp = strstr(start, "Dev#="))) {
                                devnum = strtoul(cp + 5, NULL, 0);
                        } else
                                devnum = -1;
                        if ((cp = strstr(start, "Bus="))) {
                                busnum = strtoul(cp + 4, NULL, 0);
                        } else
                                busnum = -1;
			break;

                case 'P':
                        if ((cp = strstr(start, "Vendor="))) {
                                vendor = strtoul(cp + 7, NULL, 16);
                        } else
                                vendor = -1;
                        if ((cp = strstr(start, "ProdID="))) {
                                product = strtoul(cp + 7, NULL, 16);
                        } else
                                product = -1;
			if (vendor != -1 && product != -1 && devnum >= 1 && devnum <= 127 &&
			    busnum >= 0 && busnum <= 999 &&
			    (vendorid == vendor || vendorid == -1) &&
			    (productid == product || productid == -1)) {
				if (index)
					index--;
				else {
					if (!ret) {
						if (bus)
							*bus = busnum;
						if (dev)
							*dev = devnum;
					}
					ret++;
				}
			}
			break;
		}
                start = lineend + 1;
	}
	return ret;
}

struct usbdevice *usb_open_bynumber(unsigned int busnum, unsigned int devnum, int vendorid, int productid)
{
	struct usbdevice *dev;
        struct usb_device_descriptor desc;
	unsigned int vid, pid;
	char devsfile[256];
	int ret, fd;

	snprintf(devsfile, sizeof(devsfile), "%s/%03u/%03u", usb_devicefs_mountpoint, busnum, devnum);
	if ((fd =  open(devsfile, O_RDWR)) == -1)
		return NULL;
	if ((ret = read(fd, &desc, sizeof(desc))) != sizeof(desc)) {
		if (ret > 0)
			errno = EIO;
		close(fd);
		return NULL;
	}
	vid = desc.idVendor[0] | (desc.idVendor[1] << 8);
	pid = desc.idProduct[0] | (desc.idProduct[1] << 8);
	if ((vid != vendorid && vendorid != -1) ||
	    (pid != productid && productid != -1)) {
		errno = -ENOENT;
		close(fd);
		return NULL;
	}
	if (!(dev = malloc(sizeof(struct usbdevice)))) {
		close(fd);
		return NULL;
	}
	dev->fd = fd;
	dev->desc = desc;
	lprintf(20, "usb_open_bynumber: device %03u:%03u vid %04x pid %04x fd %d\n",
		busnum, devnum, vid, pid, fd);
	return dev;
}

struct usbdevice *usb_open(int vendorid, int productid, unsigned int timeout, unsigned int index)
{
	time_t starttime, curtime;
	unsigned int busnum, devnum;
	char devsfile[256];
	long timediff;
	int ret;
	struct pollfd pfd;

	snprintf(devsfile, sizeof(devsfile), "%s/devices", usb_devicefs_mountpoint);
	time(&starttime);
	if ((pfd.fd =  open(devsfile, O_RDONLY)) == -1)
		return NULL;
	for (;;) {
		ret = parsedev(pfd.fd, &busnum, &devnum, vendorid, productid, index);
		if (ret < 0) {
			close(pfd.fd);
			return NULL;
		}
		if (ret > 0)
			break;
		time(&curtime);
		timediff = curtime - starttime;
		timediff = timeout - timediff;
		if (timediff <= 0) {
			close(pfd.fd);
			errno = ETIMEDOUT;
			return NULL;
		}
		if (timediff > 10)
			timediff = 10;
		pfd.events = POLLIN;
		ret = poll(&pfd, 1, timediff * 1000);
		if (ret < 0) {
			close(pfd.fd);
			return NULL;
		}
	}
	close(pfd.fd);
	return usb_open_bynumber(busnum, devnum, vendorid, productid);
}

int usb_control_msg(struct usbdevice *dev, unsigned char requesttype, unsigned char request,
		    unsigned short value, unsigned short index, unsigned short length, void *data, unsigned int timeout)
{
        struct usbdevfs_ctrltransfer ctrl;
	int i;

	ctrl = (struct usbdevfs_ctrltransfer){ requesttype, request, value, index, length, timeout, data };
	for (;;) {
		i = ioctl(dev->fd, USBDEVFS_CONTROL, &ctrl);
		if (i != -1 || errno != ENXIO)
			break;
		lprintf(20, "usb_control: URB already queued, retrying\n");
		usleep(20000);
	}
	lprintf(20, "usb_control: rqt 0x%02x rq 0x%02x val 0x%04x idx 0x%04x len %u ret %d\n",
		requesttype, request, value, index, length, i);
	if (i < 0) {
		lprintf(2, "ioctl: USBDEVFS_CONTROL (rqt=0x%x rq=0x%x val=%u idx=%u len=%u) error %s (%d)\n",
			requesttype, request, value, index, length, strerror(errno), errno);
		return -1;
	}
	return i;
}

int usb_bulk_msg(struct usbdevice *dev, unsigned int ep, unsigned int dlen, void *data, unsigned int timeout)
{
	struct usbdevfs_bulktransfer bulk;
	int i;

	bulk = (struct usbdevfs_bulktransfer){ ep, dlen, timeout, data };
	for (;;) {
		i = ioctl(dev->fd, USBDEVFS_BULK, &bulk);
		if (i != -1 || errno != ENXIO)
			break;
		lprintf(20, "usb_bulk: URB already queued, retrying\n");
		usleep(20000);
	}
	lprintf(20, "usb_bulk: ep 0x%02x len %u ret %d\n", ep, dlen, i);
	if (i < 0) {
		lprintf(2, "ioctl: USBDEVFS_BULK (ep=0x%x len=%u) error %s (%d)\n", ep, dlen, strerror(errno), errno);
		return -1;
	}
	return i;
}

int usb_resetep(struct usbdevice *dev, unsigned int ep)
{
	int i;

	i = ioctl(dev->fd, USBDEVFS_RESETEP, &ep);
	if (i < 0) {
		lprintf(2, "ioctl: USBDEVFS_RESETEP (ep=0x%x) error %s (%d)\n", ep, strerror(errno), errno);
		return -1;
	}
	return 0;
}

int usb_setconfiguration(struct usbdevice *dev, unsigned int configuration)
{
	int i;

	i = ioctl(dev->fd, USBDEVFS_SETCONFIGURATION, &configuration);
	if (i < 0) {
		lprintf(2, "ioctl: USBDEVFS_SETCONFIGURATION (cfg=%d) error %s (%d)\n",
			configuration, strerror(errno), errno);
		return -1;
	}
	return 0;
}

int usb_setinterface(struct usbdevice *dev, unsigned int intf, unsigned int altsetting)
{
	struct usbdevfs_setinterface setif;
	int i;

	setif = (struct usbdevfs_setinterface) { intf, altsetting };
	i = ioctl(dev->fd, USBDEVFS_SETINTERFACE, &setif);
	if (i < 0) {
		lprintf(2, "ioctl: USBDEVFS_SETINTERFACE (intf=%d,altsetting=%d) error %s (%d)\n",
			intf, altsetting, strerror(errno), errno);
		return -1;
	}
	return 0;
}

int usb_getdevicedescriptor(struct usbdevice *dev, struct usb_device_descriptor *desc)
{
	if (desc)
		memcpy(desc, &dev->desc, sizeof(*desc));
	return 0;
}

int usb_claiminterface(struct usbdevice *dev, unsigned int intf)
{
	return ioctl(dev->fd, USBDEVFS_CLAIMINTERFACE, &intf);
}

int usb_releaseinterface(struct usbdevice *dev, unsigned int intf)
{
	return ioctl(dev->fd, USBDEVFS_RELEASEINTERFACE, &intf);
}

int usb_discsignal(struct usbdevice *dev, unsigned int signr, void *context)
{
	struct usbdevfs_disconnectsignal s = { signr, context };

	return ioctl(dev->fd, USBDEVFS_DISCSIGNAL, &s);
}

int usb_submiturb(struct usbdevice *dev, struct usbdevfs_urb *urb)
{
	return ioctl(dev->fd, USBDEVFS_SUBMITURB, urb);
}

int usb_discardurb(struct usbdevice *dev, struct usbdevfs_urb *urb)
{
	return ioctl(dev->fd, USBDEVFS_DISCARDURB, urb);
}

struct usbdevfs_urb *usb_reapurb(struct usbdevice *dev, int timeout)
{
	int ret;
	struct usbdevfs_urb *urb;
	struct timeval tv1, tv2;
	long wt;
	struct pollfd pfd;

	if (timeout <= 0) {
		ret = ioctl(dev->fd, timeout ? USBDEVFS_REAPURB : USBDEVFS_REAPURBNDELAY, &urb);
		if (ret < 0)
			return NULL;
		return urb;
	}
	gettimeofday(&tv1, NULL);
	pfd.fd = dev->fd;
	pfd.events = POLLOUT;
	for (;;) {
		ret = ioctl(dev->fd, USBDEVFS_REAPURBNDELAY, &urb);
		if (ret >= 0)
			return urb;
		gettimeofday(&tv2, NULL);
		wt = timeout + (tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000;
		if (wt <= 0)
			return NULL;
		if (poll(&pfd, 1, wt) < 0)
			return NULL;
	}
}

int usb_getfd(struct usbdevice *dev)
{
	return dev->fd;
}

/* ---------------------------------------------------------------------- */
