/* libusb-win32, Generic Windows USB Library
 * Copyright (c) 2002-2005 Stephan Meyer <ste_meyer@web.de>
 * Copyright (c) 2000-2005 Johannes Erdfelt <johannes@erdfelt.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <windows.h>
#include <winioctl.h>
#include <setupapi.h>

#include "lusb0_usb.h"
#include "error.h"
#include "usbi.h"
#include "driver_api.h"
#include "registry.h"
#include "libusb-win32_version.h"

#define LIBUSB_WIN32_DLL_LARGE_TRANSFER_SUPPORT

#define LIBUSB_DEFAULT_TIMEOUT 5000
#define LIBUSB_DEVICE_NAME "\\\\.\\libusb0-"
#define LIBUSB_BUS_NAME "bus-0"
#define LIBUSB_MAX_DEVICES 256

typedef struct
{
    usb_dev_handle *dev;
    libusb_request req;
    char *bytes;
    int size;
    DWORD control_code;
    OVERLAPPED ol;
} usb_context_t;


static struct usb_version _usb_version =
{
    { VERSION_MAJOR,
        VERSION_MINOR,
        VERSION_MICRO,
        VERSION_NANO },
    { -1, -1, -1, -1 }
};


static int _usb_setup_async(usb_dev_handle *dev, void **context,
                            DWORD control_code,
                            unsigned char ep, int pktsize);
static int _usb_transfer_sync(usb_dev_handle *dev, int control_code,
                              int ep, int pktsize, char *bytes, int size,
                              int timeout);

static int usb_get_configuration(usb_dev_handle *dev, bool_t cached);
static int _usb_cancel_io(usb_context_t *context);
static int _usb_abort_ep(usb_dev_handle *dev, unsigned int ep);

static int _usb_io_sync(HANDLE dev, unsigned int code, void *in, int in_size,
                        void *out, int out_size, int *ret);
static int _usb_reap_async(void *context, int timeout, int cancel);
static int _usb_add_virtual_hub(struct usb_bus *bus);

static void _usb_free_bus_list(struct usb_bus *bus);
static void _usb_free_dev_list(struct usb_device *dev);
static void _usb_deinit(void);

/* DLL main entry point */
BOOL WINAPI DllMain(HANDLE module, DWORD reason, LPVOID reserved)
{
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        break;
    case DLL_PROCESS_DETACH:
        _usb_deinit();
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    default:
        break;
    }
    return TRUE;
}


static int usb_get_configuration(usb_dev_handle *dev, bool_t cached)
{
	int ret;
	char config;
	libusb_request request;

	if (cached)
	{
		memset(&request, 0, sizeof(request));
		request.timeout = LIBUSB_DEFAULT_TIMEOUT;

		if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_GET_CACHED_CONFIGURATION,
			&request, sizeof(request), &request, sizeof(request), &ret))
		{
			USBERR("sending get cached configuration ioctl failed, win error: %s\n", usb_win_error_to_string());
			ret = -usb_win_error_to_errno();
		}

		if (ret < 1)
			ret = -EINVAL;
		else
			config = *((char*)&request);
	}
	else
	{
		ret = usb_control_msg(dev, USB_RECIP_DEVICE | USB_ENDPOINT_IN,
			USB_REQ_GET_CONFIGURATION, 0, 0, &config, 1,
			LIBUSB_DEFAULT_TIMEOUT); 
	}

	if(ret < 0)
		return ret;

	return config;
}

int usb_os_open(usb_dev_handle *dev)
{
	char dev_name[LIBUSB_PATH_MAX];
	char *p;
	int config;
	if (!dev)
	{
		USBERR("invalid device handle %p", dev);
		return -EINVAL;
	}

	dev->impl_info = INVALID_HANDLE_VALUE;
	dev->config = 0;
	dev->interface = -1;
	dev->altsetting = -1;

	if (!dev->device->filename)
	{
		USBERR0("invalid file name\n");
		return -ENOENT;
	}

	/* build the Windows file name from the unique device name */
	strcpy(dev_name, dev->device->filename);

	p = strstr(dev_name, "--");

	if (!p)
	{
		USBERR("invalid file name %s\n", dev->device->filename);
		return -ENOENT;
	}

	*p = 0;

	dev->impl_info = CreateFile(dev_name, 0, 0, NULL, OPEN_EXISTING,
		FILE_FLAG_OVERLAPPED, NULL);

	if (dev->impl_info == INVALID_HANDLE_VALUE)
	{
		USBERR("failed to open %s: win error: %s",
			dev->device->filename, usb_win_error_to_string());
		return -ENOENT;
	}

	// get the cached configuration (no device i/o)
	config = usb_get_configuration(dev, TRUE);
	if (config > 0)
	{
		dev->config = config;
		dev->interface = -1;
		dev->altsetting = -1;
	}

	return 0;
}

int usb_os_close(usb_dev_handle *dev)
{
    if (dev->impl_info != INVALID_HANDLE_VALUE)
    {
        if (dev->interface >= 0)
        {
            usb_release_interface(dev, dev->interface);
        }

        CloseHandle(dev->impl_info);
        dev->impl_info = INVALID_HANDLE_VALUE;
        dev->interface = -1;
        dev->altsetting = -1;
    }

    return 0;
}

int usb_set_configuration(usb_dev_handle *dev, int configuration)
{
    libusb_request req;

    if (dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("error: device not open\n");
        return -EINVAL;
    }

    if (dev->config == configuration)
    {
        return 0;
    }

    if (dev->interface >= 0)
    {
        USBERR0("can't change configuration, an interface is still in use (claimed)\n");
        return -EINVAL;
    }

    req.configuration.configuration = configuration;
    req.timeout = LIBUSB_DEFAULT_TIMEOUT;

    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_SET_CONFIGURATION,
                      &req, sizeof(libusb_request), NULL, 0, NULL))
    {
        USBERR("could not set config %d: "
                  "win error: %s", configuration, usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }

    dev->config = configuration;
    dev->interface = -1;
    dev->altsetting = -1;

    return 0;
}

int usb_claim_interface(usb_dev_handle *dev, int interface)
{
    libusb_request req;

    if (dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("device not open\n");
        return -EINVAL;
    }

    if (!dev->config)
    {
        USBERR("could not claim interface %d, invalid configuration %d\n", interface, dev->config);
        return -EINVAL;
    }

    if (dev->interface == interface)
    {
        return 0;
    }

    req.intf.interface_number = interface;

    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_CLAIM_INTERFACE,
                      &req, sizeof(libusb_request), NULL, 0, NULL))
    {
        USBERR("could not claim interface %d, "
                  "win error: %s", interface, usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }
    else
    {
        dev->interface = interface;
        dev->altsetting = 0;
        return 0;
    }
}

int usb_release_interface(usb_dev_handle *dev, int interface)
{
    libusb_request req;

    if (dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("device not open\n");
        return -EINVAL;
    }

    if (!dev->config)
    {
        USBERR("could not release interface %d, invalid configuration %d\n", interface, dev->config);
        return -EINVAL;
    }

    req.intf.interface_number = interface;

    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_RELEASE_INTERFACE,
                      &req, sizeof(libusb_request), NULL, 0, NULL))
    {
        USBERR("could not release interface %d, "
                  "win error: %s", interface, usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }
    else
    {
        dev->interface = -1;
        dev->altsetting = -1;

        return 0;
    }
}

int usb_set_altinterface(usb_dev_handle *dev, int alternate)
{
    libusb_request req;

    if (dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("device not open\n");
        return -EINVAL;
    }

    if (dev->config <= 0)
    {
        USBERR("could not set alt interface %d: invalid configuration %d\n", alternate, dev->config);
        return -EINVAL;
    }

    if (dev->interface < 0)
    {
        USBERR("could not set alt interface %d: no interface claimed\n", alternate);
        return -EINVAL;
    }

    req.intf.interface_number = dev->interface;
    req.intf.altsetting_number = alternate;
    req.timeout = LIBUSB_DEFAULT_TIMEOUT;

    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_SET_INTERFACE,
                      &req, sizeof(libusb_request),
                      NULL, 0, NULL))
    {
        USBERR("could not set alt interface "
                  "%d/%d: win error: %s",
                  dev->interface, alternate, usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }

    dev->altsetting = alternate;

    return 0;
}

static int _usb_setup_async(usb_dev_handle *dev, void **context,
                            DWORD control_code,
                            unsigned char ep, int pktsize)
{
    usb_context_t **c = (usb_context_t **)context;

    if (((control_code == LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE)
            || (control_code == LIBUSB_IOCTL_ISOCHRONOUS_WRITE))
            && (ep & USB_ENDPOINT_IN))
    {
        USBERR("invalid endpoint 0x%02x", ep);
        return -EINVAL;
    }

    if (((control_code == LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ)
            || (control_code == LIBUSB_IOCTL_ISOCHRONOUS_READ))
            && !(ep & USB_ENDPOINT_IN))
    {
        USBERR("invalid endpoint 0x%02x\n", ep);
        return -EINVAL;
    }

    *c = malloc(sizeof(usb_context_t));

    if (!*c)
    {
        USBERR0("memory allocation error\n");
        return -ENOMEM;
    }

    memset(*c, 0, sizeof(usb_context_t));

    (*c)->dev = dev;
    (*c)->req.endpoint.endpoint = ep;
    (*c)->req.endpoint.packet_size = pktsize;
    (*c)->control_code = control_code;

    (*c)->ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (!(*c)->ol.hEvent)
    {
        free(*c);
        *c = NULL;
        USBERR("creating event failed: win error: %s",
                  usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }

    return 0;
}

int usb_submit_async(void *context, char *bytes, int size)
{
    usb_context_t *c = (usb_context_t *)context;

    if (!c)
    {
        USBERR0("invalid context");
        return -EINVAL;
    }

    if (c->dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("device not open\n");
        return -EINVAL;
    }

    if (c->dev->config <= 0)
    {
        USBERR("invalid configuration %d\n", c->dev->config);
        return -EINVAL;
    }

    if (c->dev->interface < 0)
    {
        USBERR("invalid interface %d\n", c->dev->interface);
        return -EINVAL;
    }


    c->ol.Offset = 0;
    c->ol.OffsetHigh = 0;
    c->bytes = bytes;
    c->size = size;

    ResetEvent(c->ol.hEvent);

    if (!DeviceIoControl(c->dev->impl_info,
                         c->control_code,
                         &c->req, sizeof(libusb_request),
                         c->bytes,
                         c->size, NULL, &c->ol))
    {
        if (GetLastError() != ERROR_IO_PENDING)
        {
            USBERR("submitting request failed, "
                      "win error: %s", usb_win_error_to_string());
            return -usb_win_error_to_errno();
        }
    }

    return 0;
}

static int _usb_reap_async(void *context, int timeout, int cancel)
{
    usb_context_t *c = (usb_context_t *)context;
    ULONG ret = 0;

    if (!c)
    {
        USBERR0("invalid context\n");
        return -EINVAL;
    }

    if (WaitForSingleObject(c->ol.hEvent, timeout) == WAIT_TIMEOUT)
    {
        /* request timed out */
        if (cancel)
        {
            _usb_cancel_io(c);
        }

        USBERR0("timeout error\n");
        return -ETRANSFER_TIMEDOUT;
    }

    if (!GetOverlappedResult(c->dev->impl_info, &c->ol, &ret, TRUE))
    {
        USBERR("reaping request failed, win error: %s\n",usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }

    return ret;
}

int usb_reap_async(void *context, int timeout)
{
    return _usb_reap_async(context, timeout, TRUE);
}

int usb_reap_async_nocancel(void *context, int timeout)
{
    return _usb_reap_async(context, timeout, FALSE);
}


int usb_cancel_async(void *context)
{
    /* NOTE that this function will cancel all pending URBs */
    /* on the same endpoint as this particular context, or even */
    /* all pending URBs for this particular device. */

    usb_context_t *c = (usb_context_t *)context;

    if (!c)
    {
        USBERR0("invalid context\n");
        return -EINVAL;
    }

    if (c->dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("device not open\n");
        return -EINVAL;
    }

    _usb_cancel_io(c);

    return 0;
}

int usb_free_async(void **context)
{
    usb_context_t **c = (usb_context_t **)context;

    if (!*c)
    {
        USBERR0("invalid context\n");
        return -EINVAL;
    }

    CloseHandle((*c)->ol.hEvent);

    free(*c);
    *c = NULL;

    return 0;
}

static int _usb_transfer_sync(usb_dev_handle *dev, int control_code,
                              int ep, int pktsize, char *bytes, int size,
                              int timeout)
{
    void *context = NULL;
    int transmitted = 0;
    int ret;
    int requested;

	if (!timeout) timeout=INFINITE;
    ret = _usb_setup_async(dev, &context, control_code, (unsigned char )ep,
                           pktsize);

    if (ret < 0)
    {
        return ret;
    }

    do
    {
#ifdef LIBUSB_WIN32_DLL_LARGE_TRANSFER_SUPPORT
        requested = size > LIBUSB_MAX_READ_WRITE ? LIBUSB_MAX_READ_WRITE : size;
#else
        requested = size;
#endif
        ret = usb_submit_async(context, bytes, requested);

        if (ret < 0)
        {
            transmitted = ret;
            break;
        }

        ret = usb_reap_async(context, timeout);

        if (ret < 0)
        {
            transmitted = ret;
            break;
        }

        transmitted += ret;
        bytes += ret;
        size -= ret;
    }
    while (size > 0 && ret == requested);

    usb_free_async(&context);

    return transmitted;
}

int usb_bulk_write(usb_dev_handle *dev, int ep, char *bytes, int size,
                   int timeout)
{
    return _usb_transfer_sync(dev, LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE,
                              ep, 0, bytes, size, timeout);
}

int usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size,
                  int timeout)
{
    return _usb_transfer_sync(dev, LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ,
                              ep, 0, bytes, size, timeout);
}

int usb_interrupt_write(usb_dev_handle *dev, int ep, char *bytes, int size,
                        int timeout)
{
    return _usb_transfer_sync(dev, LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE,
                              ep, 0, bytes, size, timeout);
}

int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size,
                       int timeout)
{
    return _usb_transfer_sync(dev, LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ,
                              ep, 0, bytes, size, timeout);
}

int usb_isochronous_setup_async(usb_dev_handle *dev, void **context,
                                unsigned char ep, int pktsize)
{
    if (ep & 0x80)
        return _usb_setup_async(dev, context, LIBUSB_IOCTL_ISOCHRONOUS_READ,
                                ep, pktsize);
    else
        return _usb_setup_async(dev, context, LIBUSB_IOCTL_ISOCHRONOUS_WRITE,
                                ep, pktsize);
}

int usb_bulk_setup_async(usb_dev_handle *dev, void **context, unsigned char ep)
{
    if (ep & 0x80)
        return _usb_setup_async(dev, context, LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ,
                                ep, 0);
    else
        return _usb_setup_async(dev, context, LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE,
                                ep, 0);
}

int usb_interrupt_setup_async(usb_dev_handle *dev, void **context,
                              unsigned char ep)
{
    if (ep & 0x80)
        return _usb_setup_async(dev, context, LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ,
                                ep, 0);
    else
        return _usb_setup_async(dev, context, LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE,
                                ep, 0);
}

int usb_control_msg(usb_dev_handle *dev, int requesttype, int request,
                    int value, int index, char *bytes, int size, int timeout)
{
    int read = 0;
    libusb_request req;
    void *out = &req;
    int out_size = sizeof(libusb_request);
    void *in = bytes;
    int in_size = size;
    int code;

    if (dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("device not open\n");
        return -EINVAL;
    }

    req.timeout = timeout;

    /* windows doesn't support generic control messages, so it needs to be */
    /* split up */
    switch (requesttype & (0x03 << 5))
    {
    case USB_TYPE_STANDARD:
        switch (request)
        {
        case USB_REQ_GET_STATUS:
            req.status.recipient = requesttype & 0x1F;
            req.status.index = index;
            code = LIBUSB_IOCTL_GET_STATUS;
            break;

        case USB_REQ_CLEAR_FEATURE:
            req.feature.recipient = requesttype & 0x1F;
            req.feature.feature = value;
            req.feature.index = index;
            code = LIBUSB_IOCTL_CLEAR_FEATURE;
            break;

        case USB_REQ_SET_FEATURE:
            req.feature.recipient = requesttype & 0x1F;
            req.feature.feature = value;
            req.feature.index = index;
            code = LIBUSB_IOCTL_SET_FEATURE;
            break;

        case USB_REQ_GET_DESCRIPTOR:
            req.descriptor.recipient = requesttype & 0x1F;
            req.descriptor.type = (value >> 8) & 0xFF;
            req.descriptor.index = value & 0xFF;
            req.descriptor.language_id = index;
            code = LIBUSB_IOCTL_GET_DESCRIPTOR;
            break;

        case USB_REQ_SET_DESCRIPTOR:
            req.descriptor.recipient = requesttype & 0x1F;
            req.descriptor.type = (value >> 8) & 0xFF;
            req.descriptor.index = value & 0xFF;
            req.descriptor.language_id = index;
            code = LIBUSB_IOCTL_SET_DESCRIPTOR;
            break;

        case USB_REQ_GET_CONFIGURATION:
            code = LIBUSB_IOCTL_GET_CONFIGURATION;
            break;

        case USB_REQ_SET_CONFIGURATION:
            req.configuration.configuration = value;
            code = LIBUSB_IOCTL_SET_CONFIGURATION;
            break;

        case USB_REQ_GET_INTERFACE:
            req.intf.interface_number = index;
            code = LIBUSB_IOCTL_GET_INTERFACE;
            break;

        case USB_REQ_SET_INTERFACE:
            req.intf.interface_number = index;
            req.intf.altsetting_number = value;
            code = LIBUSB_IOCTL_SET_INTERFACE;
            break;

        default:
            USBERR("invalid request 0x%x", request);
            return -EINVAL;
        }
        break;

    case USB_TYPE_VENDOR:
    case USB_TYPE_CLASS:

        req.vendor.type = (requesttype >> 5) & 0x03;
        req.vendor.recipient = requesttype & 0x1F;
        req.vendor.request = request;
        req.vendor.value = value;
        req.vendor.index = index;

        if (requesttype & 0x80)
            code = LIBUSB_IOCTL_VENDOR_READ;
        else
            code = LIBUSB_IOCTL_VENDOR_WRITE;
        break;

    case USB_TYPE_RESERVED:
    default:
        USBERR("invalid or unsupported request type: %x",
                  requesttype);
        return -EINVAL;
    }

    /* out request? */
    if (!(requesttype & USB_ENDPOINT_IN))
    {
        if (!(out = malloc(sizeof(libusb_request) + size)))
        {
            USBERR0("memory allocation failed\n");
            return -ENOMEM;
        }

        memcpy(out, &req, sizeof(libusb_request));
        memcpy((char *)out + sizeof(libusb_request), bytes, size);
        out_size = sizeof(libusb_request) + size;
        in = NULL;
        in_size = 0;
    }

    if (!_usb_io_sync(dev->impl_info, code, out, out_size, in, in_size, &read))
    {
        USBERR("sending control message failed, win error: %s\n", usb_win_error_to_string());
        if (!(requesttype & USB_ENDPOINT_IN))
        {
            free(out);
        }
        return -usb_win_error_to_errno();
    }

    /* out request? */
    if (!(requesttype & USB_ENDPOINT_IN))
    {
        free(out);
        return size;
    }
    else
        return read;
}


int usb_os_find_busses(struct usb_bus **busses)
{
    struct usb_bus *bus = NULL;

    /* create one 'virtual' bus */

    bus = malloc(sizeof(struct usb_bus));

    if (!bus)
    {
        USBERR0("memory allocation failed\n");
        return -ENOMEM;
    }

    memset(bus, 0, sizeof(*bus));
    strcpy(bus->dirname, LIBUSB_BUS_NAME);

    USBMSG("found %s\n", bus->dirname);

    *busses = bus;

    return 0;
}

int usb_os_find_devices(struct usb_bus *bus, struct usb_device **devices)
{
    int i;
    struct usb_device *dev, *fdev = NULL;
    char dev_name[LIBUSB_PATH_MAX];
    int ret;
    HANDLE handle;
    libusb_request req;

    for (i = 1; i < LIBUSB_MAX_DEVICES; i++)
    {
        ret = 0;

        _snprintf(dev_name, sizeof(dev_name) - 1,"%s%04d",
                  LIBUSB_DEVICE_NAME, i);

        if (!(dev = malloc(sizeof(*dev))))
        {
            USBERR0("memory allocation failed\n");
            return -ENOMEM;
        }

        memset(dev, 0, sizeof(*dev));
        dev->bus = bus;
        dev->devnum = (unsigned char)i;

        handle = CreateFile(dev_name, 0, 0, NULL, OPEN_EXISTING,
                            FILE_FLAG_OVERLAPPED, NULL);

        if (handle == INVALID_HANDLE_VALUE)
        {
            free(dev);
            continue;
        }

        /* retrieve device descriptor */
        req.descriptor.type = USB_DT_DEVICE;
        req.descriptor.recipient = USB_RECIP_DEVICE;
        req.descriptor.index = 0;
        req.descriptor.language_id = 0;
        req.timeout = LIBUSB_DEFAULT_TIMEOUT;

        _usb_io_sync(handle, LIBUSB_IOCTL_GET_DESCRIPTOR,
                     &req, sizeof(libusb_request),
                     &dev->descriptor, USB_DT_DEVICE_SIZE, &ret);

        if (ret < USB_DT_DEVICE_SIZE)
        {
            USBERR0("couldn't read device descriptor\n");
            free(dev);
            CloseHandle(handle);
            continue;
        }

        _snprintf(dev->filename, LIBUSB_PATH_MAX - 1, "%s--0x%04x-0x%04x",
                  dev_name, dev->descriptor.idVendor, dev->descriptor.idProduct);

        CloseHandle(handle);

        LIST_ADD(fdev, dev);

        USBMSG("found %s on %s\n", dev->filename, bus->dirname);
    }

    *devices = fdev;

    return 0;
}


void usb_os_init(void)
{
    HANDLE dev;
    libusb_request req;
    int i;
    int ret;
    char dev_name[LIBUSB_PATH_MAX];

    USBMSG("dll version: %d.%d.%d.%d\n",
                VERSION_MAJOR, VERSION_MINOR,
                VERSION_MICRO, VERSION_NANO);


    for (i = 1; i < LIBUSB_MAX_DEVICES; i++)
    {
        /* build the Windows file name */
        _snprintf(dev_name, sizeof(dev_name) - 1,"%s%04d",
                  LIBUSB_DEVICE_NAME, i);

        dev = CreateFile(dev_name, 0, 0, NULL, OPEN_EXISTING,
                         FILE_FLAG_OVERLAPPED, NULL);

        if (dev == INVALID_HANDLE_VALUE)
        {
            continue;
        }

        if (!_usb_io_sync(dev, LIBUSB_IOCTL_GET_VERSION,
                          &req, sizeof(libusb_request),
                          &req, sizeof(libusb_request), &ret)
                || (ret < sizeof(libusb_request)))
        {
            USBERR0("getting driver version failed\n");
            CloseHandle(dev);
            continue;
        }
        else
        {
            _usb_version.driver.major = req.version.major;
            _usb_version.driver.minor = req.version.minor;
            _usb_version.driver.micro = req.version.micro;
            _usb_version.driver.nano = req.version.nano;

            USBMSG("driver version: %d.%d.%d.%d\n",
                        req.version.major, req.version.minor,
                        req.version.micro, req.version.nano);

            /* set debug level */
            req.timeout = 0;
            req.debug.level = usb_log_get_level();

            if (!_usb_io_sync(dev, LIBUSB_IOCTL_SET_DEBUG_LEVEL,
                              &req, sizeof(libusb_request),
                              NULL, 0, NULL))
            {
                USBERR0("setting debug level failed");
            }

            CloseHandle(dev);
            break;
        }
    }
}


int usb_resetep(usb_dev_handle *dev, unsigned int ep)
{
    libusb_request req;

    if (dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("device not open\n");
        return -EINVAL;
    }

    req.endpoint.endpoint = (int)ep;
    req.timeout = LIBUSB_DEFAULT_TIMEOUT;

    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_ABORT_ENDPOINT, &req,
                      sizeof(libusb_request), NULL, 0, NULL))
    {
        USBERR("could not abort ep 0x%02x, win error: %s\n", ep, usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }

    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_RESET_ENDPOINT, &req,
                      sizeof(libusb_request), NULL, 0, NULL))
    {
        USBERR("could not reset ep 0x%02x, win error: %s\n", ep, usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }

    return 0;
}

int usb_clear_halt(usb_dev_handle *dev, unsigned int ep)
{
    libusb_request req;

    if (dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("device not open\n");
        return -EINVAL;
    }

    req.endpoint.endpoint = (int)ep;
    req.timeout = LIBUSB_DEFAULT_TIMEOUT;

    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_RESET_ENDPOINT, &req,
                      sizeof(libusb_request), NULL, 0, NULL))
    {
        USBERR("could not clear halt, ep 0x%02x, "
                  "win error: %s", ep, usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }

    return 0;
}

int usb_reset(usb_dev_handle *dev)
{
    libusb_request req;

    if (dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("device not open\n");
        return -EINVAL;
    }

    req.timeout = LIBUSB_DEFAULT_TIMEOUT;

    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_RESET_DEVICE,
                      &req, sizeof(libusb_request), NULL, 0, NULL))
    {
        USBERR("could not reset device, win error: %s\n", usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }

    return 0;
}

int usb_reset_ex(usb_dev_handle *dev, unsigned int reset_type)
{
    libusb_request req;

    if (dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("device not open\n");
        return -EINVAL;
    }

    req.timeout = LIBUSB_DEFAULT_TIMEOUT;
    req.reset_ex.reset_type = reset_type;

    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_RESET_DEVICE_EX,
                      &req, sizeof(libusb_request), NULL, 0, NULL))
    {
        USBERR("could not reset device, win error: %s\n", usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }

    return 0;
}

const struct usb_version *usb_get_version(void)
{
    return &_usb_version;
}

void usb_set_debug(int level)
{
    HANDLE dev;
    libusb_request req;
    int i;
    char dev_name[LIBUSB_PATH_MAX];

    if (usb_log_get_level() || level)
    {
        USBMSG("setting debugging level to %d (%s)\n",
                    level, level ? "on" : "off");
    }

    usb_log_set_level(level);

    /* find a valid device */
    for (i = 1; i < LIBUSB_MAX_DEVICES; i++)
    {
        /* build the Windows file name */
        _snprintf(dev_name, sizeof(dev_name) - 1,"%s%04d",
                  LIBUSB_DEVICE_NAME, i);

        dev = CreateFile(dev_name, 0, 0, NULL, OPEN_EXISTING,
                         FILE_FLAG_OVERLAPPED, NULL);

        if (dev == INVALID_HANDLE_VALUE)
        {
            continue;
        }

        /* set debug level */
        req.timeout = 0;
        req.debug.level = usb_log_get_level();

        if (!_usb_io_sync(dev, LIBUSB_IOCTL_SET_DEBUG_LEVEL,
                          &req, sizeof(libusb_request),
                          NULL, 0, NULL))
        {
            USBERR0("setting debug level failed\n");
        }

        CloseHandle(dev);

        break;
    }
}

int usb_os_determine_children(struct usb_bus *bus)
{
    struct usb_device *dev;
    int i = 0;

    /* add a virtual hub to the bus to emulate this feature */
    if (_usb_add_virtual_hub(bus))
    {
        if (bus->root_dev->children)
        {
            free(bus->root_dev->children);
        }

        bus->root_dev->num_children = 0;
        for (dev = bus->devices; dev; dev = dev->next)
            bus->root_dev->num_children++;

        bus->root_dev->children
        = malloc(sizeof(struct usb_device *) * bus->root_dev->num_children);

        for (dev = bus->devices; dev; dev = dev->next)
            bus->root_dev->children[i++] = dev;
    }

    return 0;
}

static int _usb_cancel_io(usb_context_t *context)
{
    int ret;
    ret = _usb_abort_ep(context->dev, context->req.endpoint.endpoint);
    WaitForSingleObject(context->ol.hEvent, 0);
    return ret;
}

static int _usb_abort_ep(usb_dev_handle *dev, unsigned int ep)
{
    libusb_request req;

    if (dev->impl_info == INVALID_HANDLE_VALUE)
    {
        USBERR0("device not open");
        return -EINVAL;
    }

    req.endpoint.endpoint = (int)ep;
    req.timeout = LIBUSB_DEFAULT_TIMEOUT;

    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_ABORT_ENDPOINT, &req,
                      sizeof(libusb_request), NULL, 0, NULL))
    {
        USBERR("could not abort ep 0x%02x, win error: %s\n", ep, usb_win_error_to_string());
        return -usb_win_error_to_errno();
    }

    return 0;
}

static int _usb_io_sync(HANDLE dev, unsigned int code, void *out, int out_size,
                        void *in, int in_size, int *ret)
{
    OVERLAPPED ol;
    DWORD _ret;

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

    if (ret)
        *ret = 0;

    ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (!ol.hEvent)
        return FALSE;

    if (!DeviceIoControl(dev, code, out, out_size, in, in_size, NULL, &ol))
    {
        if (GetLastError() != ERROR_IO_PENDING)
        {
            CloseHandle(ol.hEvent);
            return FALSE;
        }
    }

    if (GetOverlappedResult(dev, &ol, &_ret, TRUE))
    {
        if (ret)
            *ret = (int)_ret;
        CloseHandle(ol.hEvent);
        return TRUE;
    }

    CloseHandle(ol.hEvent);
    return FALSE;
}

static int _usb_add_virtual_hub(struct usb_bus *bus)
{
    struct usb_device *dev;

    if (!bus->root_dev)
    {
        if (!(dev = malloc(sizeof(*dev))))
            return FALSE;

        memset(dev, 0, sizeof(*dev));
        strcpy(dev->filename, "virtual-hub");
        dev->bus = bus;

        dev->descriptor.bLength = USB_DT_DEVICE_SIZE;
        dev->descriptor.bDescriptorType = USB_DT_DEVICE;
        dev->descriptor.bcdUSB = 0x0200;
        dev->descriptor.bDeviceClass = USB_CLASS_HUB;
        dev->descriptor.bDeviceSubClass = 0;
        dev->descriptor.bDeviceProtocol = 0;
        dev->descriptor.bMaxPacketSize0 = 64;
        dev->descriptor.idVendor = 0;
        dev->descriptor.idProduct = 0;
        dev->descriptor.bcdDevice = 0x100;
        dev->descriptor.iManufacturer = 0;
        dev->descriptor.iProduct = 0;
        dev->descriptor.iSerialNumber = 0;
        dev->descriptor.bNumConfigurations = 0;

        bus->root_dev = dev;
    }

    return TRUE;
}

static void _usb_free_bus_list(struct usb_bus *bus)
{
    if (bus)
    {
        _usb_free_bus_list(bus->next);
        if (bus->root_dev)
            usb_free_dev(bus->root_dev);
        _usb_free_dev_list(bus->devices);
        usb_free_bus(bus);
    }
}

static void _usb_free_dev_list(struct usb_device *dev)
{
    if (dev)
    {
        _usb_free_dev_list(dev->next);
        usb_free_dev(dev);
    }
}

static void _usb_deinit(void)
{
    _usb_free_bus_list(usb_get_busses());
}
