/*
  Author   : Benoit PAPILLAULT <benoit.papillault@free.fr>
  Creation : 29/05/2001

  Portable USB user library -- Linux implementation
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/time.h>

#include <signal.h>
#include <errno.h>
#include <string.h>

#include <linux/usb.h>
#include <linux/usbdevice_fs.h>
#include <asm/page.h>

#include "pusb.h"

struct pusb_device_t
{
	int fd;
};

struct pusb_endpoint_t
{
	int fd;
	int ep;
};

/* for ident(1) command */
static char id[] = "@(#) $Id: pusb-linux.c,v 1.1 2001/06/25 18:10:06 papillau Exp $";

static const char usb_path[] = "/proc/bus/usb";

/* Device descriptor */
struct usb_device_descriptor {
	__u8  bLength;
	__u8  bDescriptorType;
	__u16 bcdUSB;
	__u8  bDeviceClass;
	__u8  bDeviceSubClass;
	__u8  bDeviceProtocol;
	__u8  bMaxPacketSize0;
	__u16 idVendor;
	__u16 idProduct;
	__u16 bcdDevice;
	__u8  iManufacturer;
	__u8  iProduct;
	__u8  iSerialNumber;
	__u8  bNumConfigurations;
} __attribute__ ((packed));

/* try to open the file and get USB device information,
   if it's ok, check if it matches vendorID & productID and return
   a usable file descriptor in this case. Else returns -1.
*/

int test_file(const char *path, int vendorID, int productID)
{
	int fd;
	struct usb_device_descriptor desc;
	
	fd = open(path,O_RDWR);
	if (fd == -1)
    {
		perror(path);
		return -1;
    }
  
	if (read(fd,&desc,sizeof(desc)) == sizeof(desc))
    {
		/* great, we read something */
		
		/* check, it match the correct structure */
		
		if (desc.bLength == sizeof(desc))
		{
			/*	  
				  printf("=== %s ===\n",path);
				  printf("  bLength         = %u\n",desc.bLength);
				  printf("  bDescriptorType = %u\n",desc.bDescriptorType);
				  printf("  bcdUSB          = %04x\n",desc.bcdUSB);
				  printf("  idVendor        = %04x\n",desc.idVendor);
				  printf("  idProduct       = %04x\n",desc.idProduct);
				  printf("  bcdDevice       = %04x\n",desc.bcdDevice);
			*/
			if (vendorID == desc.idVendor
				&& productID == desc.idProduct)
				return fd;
		}
    }
	
	close(fd);
	return -1;
}

/* search for a vendorID, productID. return -1 if search failed
   or a usable file descriptor
*/

int usbfs_search(const char *path, int vendorID, int productID)
{
	int result = -1;
	
	DIR * dir;
	struct dirent * dirp;
	
	dir = opendir(path);
	if (dir == NULL)
    {
		perror(path);
		return -1;
    }
	
	while ((dirp=readdir(dir)) != NULL)
    {
		char file[PATH_MAX+1];
		struct stat statbuf;
		
		if (strcmp(dirp->d_name,".")==0)
			continue;
		if (strcmp(dirp->d_name,"..")==0)
			continue;
		
		sprintf(file,"%s/%s",path,dirp->d_name);
		
		if (stat(file,&statbuf)!=0)
		{
			perror(file);
			continue;
		}
		
		if (S_ISDIR(statbuf.st_mode))
		{
			if ((result = usbfs_search(file,vendorID,productID)) < 0)
				continue;
			else
				break;
		}
		
		if (S_ISREG(statbuf.st_mode))
		{
			/* check the file size, which must be 18 
			   = sizeof(struct usb_device_descriptor) */
			
			if (statbuf.st_size != sizeof(struct usb_device_descriptor))
				continue;
			
			if ((result=test_file(file,vendorID,productID)) < 0)
				continue;
			else
				break;
		}
    }
	
	closedir(dir);
	return result;
}

static pusb_device_t make_device(int fd)
{
    pusb_device_t dev;

    if(!(dev = malloc(sizeof(*dev))))
	{
		close (fd);
		return 0;
	}
	
    dev->fd = fd;
    return dev;
}

pusb_device_t pusb_search_open(int vendorID, int productID)
{
	int fd;

	fd = usbfs_search("/proc/bus/usb",vendorID,productID);
	if (fd < 0)
		return NULL;

	return make_device(fd);
}

pusb_device_t pusb_open(const char *path)
{
	int fd; 

	fd = open(path,O_RDWR);
	if (fd < 0)
	{
		perror(path);
		return NULL;
	}

	return make_device(fd);
}

int pusb_close(pusb_device_t dev)
{
	int ret;

	ret = close (dev->fd);
	free (dev);

	return ret;
}

int pusb_control_msg(pusb_device_t dev,
		     int request_type, int request,
		     int value, int index, 
		     unsigned char *buf, int size, int timeout)
{
	int ret;
	struct usbdevfs_ctrltransfer ctrl;

	ctrl.requesttype = request_type;
	ctrl.request     = request;
	ctrl.value       = value;
	ctrl.index       = index;
	ctrl.length      = size;
	ctrl.timeout     = timeout;
	ctrl.data        = buf;

	ret = ioctl(dev->fd,USBDEVFS_CONTROL,&ctrl);
	return ret;
}

int pusb_set_configuration(pusb_device_t dev, int config)
{
  int ret;

  ret = ioctl(dev->fd,USBDEVFS_SETCONFIGURATION,&config);
  return ret;
}

int pusb_set_interface(pusb_device_t dev, int interface, int alternate)
{
  struct usbdevfs_setinterface setintf;
  int ret;

  setintf.interface = interface;
  setintf.altsetting = alternate;

  ret = ioctl(dev->fd,USBDEVFS_SETINTERFACE,&setintf);
  return ret;
}

pusb_endpoint_t pusb_endpoint_open(pusb_device_t dev, int epnum, int flags)
{
	pusb_endpoint_t ep;

	ep = (pusb_endpoint_t) malloc(sizeof(*ep));
	if (ep == NULL)
		return NULL;

	ep->fd = dev->fd;
	ep->ep = epnum & 0xf;

	return ep;
}

int pusb_endpoint_rw_no_timeout(int fd,int ep,
		       unsigned char *buf, int size)
{
	struct usbdevfs_urb urb, * purb;
	int ret;

	memset(&urb,0,sizeof(urb));

	urb.type = USBDEVFS_URB_TYPE_BULK;
	urb.endpoint = ep;
	urb.flags  = 0;
	urb.buffer = buf;
	urb.buffer_length = size;
	urb.signr = 0;

	do
	{
		ret = ioctl(fd,USBDEVFS_SUBMITURB,&urb);
	} while (ret < 0 && errno == EINTR);

	if (ret < 0)
		return ret;

	do
	{
		ret = ioctl(fd,USBDEVFS_REAPURB,&purb);
	} while (ret < 0 && errno == EINTR);

	if (ret < 0)
		return ret;

	if (purb != &urb)
		printf("purb=%p, &urb=%p\n",purb,&urb);

	if (purb->buffer != buf)
		printf("purb->buffer=%p, buf=%p\n",purb->buffer,buf);

	return purb->actual_length;
}

int pusb_endpoint_rw(int fd,int ep,unsigned char * buf,int size,int timeout)
{
	struct usbdevfs_bulktransfer bulk;
	int ret, received = 0;

	do
    {
		bulk.ep      = ep;
		bulk.len     = size;
		if (bulk.len > PAGE_SIZE)
			bulk.len = PAGE_SIZE;
		bulk.timeout = timeout;
		bulk.data    = buf;

		do
		{
			ret = ioctl(fd,USBDEVFS_BULK,&bulk);
		}
		while (ret < 0 && errno == EINTR);

		if (ret < 0)
			return ret;
		
		buf  += ret;
		size -= ret;
		
		received += ret;
    }
	while (ret==bulk.len && size>0);
	
	return received;
}

int pusb_endpoint_write(pusb_endpoint_t ep, 
			const unsigned char *buf, int size, int timeout)
{
	if (timeout == 0)
		return pusb_endpoint_rw_no_timeout(ep->fd,ep->ep|USB_DIR_OUT,buf,size);
	return pusb_endpoint_rw(ep->fd,ep->ep|USB_DIR_OUT,buf,size,timeout);
}

int pusb_endpoint_read(pusb_endpoint_t ep, 
			unsigned char *buf, int size, int timeout)
{
	if (timeout == 0)
		return pusb_endpoint_rw_no_timeout(ep->fd,ep->ep|USB_DIR_IN,buf,size);
	return pusb_endpoint_rw(ep->fd,ep->ep|USB_DIR_IN,buf,size,timeout);
}

int pusb_endpoint_close(pusb_endpoint_t ep)
{
	/* nothing to do on the struct content */
	free (ep);
	return 0;
}

int pusb_claim_interface(pusb_device_t dev,int interface)
{
  int ret;

  ret = ioctl(dev->fd, USBDEVFS_CLAIMINTERFACE,&interface);
  return ret;
}

int pusb_release_interface(pusb_device_t dev,int interface)
{
  int ret;

  ret = ioctl(dev->fd,USBDEVFS_RELEASEINTERFACE,&interface);
  return ret;
}
