/**
 * gendrv.c - USB driver stack project for Windows NT 4.0
 *
 * Copyright (c) 2002-2004 Zhiming  mypublic99@yahoo.com
 *
 * This program/include file 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/include file 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 (in the main directory of the distribution, the file
 * COPYING); if not, write to the Free Software Foundation,Inc., 59 Temple
 * Place, Suite 330, Boston, MA  02111-1307  USA
 */

//this driver is part of the dev manager responsible to manage non-driver device
#include "usbdriver.h"
#include "gendrv.h"

#define if_dev( dev_obj ) \
( ( ( ( PGENDRV_DEVICE_EXTENSION)dev_obj->DeviceExtension )->pdriver->driver_desc.flags & USB_DRIVER_FLAG_IF_CAPABLE ) != 0 )

#define GENDRV_EXIT_DISPATCH( dev_OBJ, staTUS, iRp ) \
{\
	iRp->IoStatus.Status = staTUS;\
    if( staTUS != STATUS_PENDING)\
    {\
		IoCompleteRequest( iRp, IO_NO_INCREMENT);\
		return staTUS;\
    }\
    IoMarkIrpPending( iRp );\
	IoStartPacket( dev_OBJ, iRp, NULL, gendrv_cancel_queued_irp ); \
    return STATUS_PENDING;\
}

#define GENDRV_COMPLETE_START_IO( dev_OBJ, staTUS, iRP ) \
{\
	iRP->IoStatus.Status = staTUS;\
	if( staTUS != STATUS_PENDING )\
	{\
		IoStartNextPacket( dev_OBJ, TRUE );\
		IoCompleteRequest( iRP, IO_NO_INCREMENT );\
	}\
	return;\
}

extern POBJECT_TYPE NTSYSAPI IoDriverObjectType;

extern VOID disp_urb_completion(PURB purb, PVOID context);


VOID disp_noio_urb_completion(PURB purb, PVOID context);

NTSYSAPI NTSTATUS NTAPI ZwLoadDriver(IN PUNICODE_STRING DriverServiceName);

NTSYSAPI NTSTATUS NTAPI ZwClose(IN HANDLE Handle);

NTSYSAPI
NTSTATUS
NTAPI
ObOpenObjectByName(IN POBJECT_ATTRIBUTES ObjectAttributes,
                   IN POBJECT_TYPE ObjectType OPTIONAL,
                   IN KPROCESSOR_MODE AccessMode,
                   IN OUT PACCESS_STATE AccessState OPTIONAL,
                   IN ACCESS_MASK DesiredAccess OPTIONAL,
                   IN OUT PVOID ParseContext OPTIONAL, OUT PHANDLE Handle);

BOOLEAN gendrv_if_driver_destroy(PUSB_DEV_MANAGER dev_mgr, PUSB_DRIVER pdriver);

VOID gendrv_set_cfg_completion(PURB purb, PVOID context);

BOOLEAN gendrv_connect(PDEV_CONNECT_DATA param, DEV_HANDLE dev_handle);

BOOLEAN gendrv_stop(PUSB_DEV_MANAGER dev_mgr, DEV_HANDLE dev_handle);

BOOLEAN gendrv_disconnect(PUSB_DEV_MANAGER dev_mgr, DEV_HANDLE dev_handle);

VOID gendrv_startio(IN PDEVICE_OBJECT dev_obj, IN PIRP irp);

VOID NTAPI gendrv_cancel_queued_irp(PDEVICE_OBJECT pdev_obj, PIRP pirp);

VOID gendrv_release_ext_drvr_entry(PGENDRV_DRVR_EXTENSION pdrvr_ext, PGENDRV_EXT_DRVR_ENTRY pentry);

VOID gendrv_clean_up_queued_irps(PDEVICE_OBJECT dev_obj);

PGENDRV_EXT_DRVR_ENTRY gendrv_alloc_ext_drvr_entry(PGENDRV_DRVR_EXTENSION pdrvr_ext);

PDRIVER_OBJECT gendrv_open_ext_driver(PUNICODE_STRING unicode_string);

NTSTATUS
gendrv_get_key_value(IN HANDLE KeyHandle, IN PWSTR ValueName, OUT PKEY_VALUE_FULL_INFORMATION * Information);

NTSTATUS
gendrv_open_reg_key(OUT PHANDLE handle,
                    IN HANDLE base_handle OPTIONAL,
                    IN PUNICODE_STRING keyname, IN ACCESS_MASK desired_access, IN BOOLEAN create);

BOOLEAN gendrv_do_disconnect(PUSB_DEV_MANAGER dev_mgr, DEV_HANDLE if_handle, BOOLEAN is_if);

BOOLEAN gendrv_do_stop(PUSB_DEV_MANAGER dev_mgr, DEV_HANDLE dev_handle, BOOLEAN is_if);

NTSTATUS gendrv_send_pnp_msg(ULONG msg, PDEVICE_OBJECT pdev_obj, PVOID pctx);

BOOLEAN gendrv_delete_device(PUSB_DEV_MANAGER dev_mgr, PDEVICE_OBJECT dev_obj);

PDEVICE_OBJECT gendrv_create_device(PUSB_DEV_MANAGER dev_mgr, PUSB_DRIVER gen_drvr, DEV_HANDLE dev_handle);

PDRIVER_OBJECT gendrv_load_ext_drvr(PGENDRV_DRVR_EXTENSION pdrvr_ext, PUSB_DESC_HEADER pdesc);

PDRIVER_OBJECT gendrv_find_drvr_by_key(PGENDRV_DRVR_EXTENSION pdrvr_ext, ULONG key);

ULONG gendrv_make_key(PUSB_DESC_HEADER pdesc);

BOOLEAN
gendrv_driver_init(PUSB_DEV_MANAGER dev_mgr, PUSB_DRIVER pdriver)
{
    PGENDRV_DRVR_EXTENSION pdrvr_ext;

    if (dev_mgr == NULL || pdriver == NULL)
        return FALSE;

    pdriver->driver_desc.flags = USB_DRIVER_FLAG_DEV_CAPABLE;
    pdriver->driver_desc.vendor_id = 0xffff;    // USB Vendor ID
    pdriver->driver_desc.product_id = 0xffff;   // USB Product ID.
    pdriver->driver_desc.release_num = 0x100;   // Release Number of Device

    pdriver->driver_desc.config_val = 0;        // Configuration Value
    pdriver->driver_desc.if_num = 0;    // Interface Number
    pdriver->driver_desc.if_class = 0xff;       // Interface Class
    pdriver->driver_desc.if_sub_class = 0xff;   // Interface SubClass
    pdriver->driver_desc.if_protocol = 0xff;    // Interface Protocol

    pdriver->driver_desc.driver_name = "USB generic dev driver";        // Driver name for Name Registry
    pdriver->driver_desc.dev_class = USB_CLASS_VENDOR_SPEC;
    pdriver->driver_desc.dev_sub_class = 0;     // Device Subclass
    pdriver->driver_desc.dev_protocol = 0;      // Protocol Info.

    pdriver->driver_ext = usb_alloc_mem(NonPagedPool, sizeof(GENDRV_DRVR_EXTENSION));
    if (!pdriver->driver_ext)
    {
        usb_dbg_print(DBGLVL_MAXIMUM, ("gendrv_driver_init(): memory allocation failed!\n"));
        return FALSE;
    }

    pdriver->driver_ext_size = sizeof(GENDRV_DRVR_EXTENSION);

    RtlZeroMemory(pdriver->driver_ext, pdriver->driver_ext_size);
    pdrvr_ext = (PGENDRV_DRVR_EXTENSION) pdriver->driver_ext;

    // InitializeListHead( &pdrvr_ext->dev_list );
    InitializeListHead(&pdrvr_ext->ext_drvr_list);
    pdrvr_ext->ext_drvr_count = 0;
    ExInitializeFastMutex(&pdrvr_ext->drvr_ext_mutex);

    pdriver->disp_tbl.version = 1;
    pdriver->disp_tbl.dev_connect = gendrv_connect;
    pdriver->disp_tbl.dev_disconnect = gendrv_disconnect;
    pdriver->disp_tbl.dev_stop = gendrv_stop;
    pdriver->disp_tbl.dev_reserved = NULL;

    return TRUE;
}

BOOLEAN
gendrv_driver_destroy(PUSB_DEV_MANAGER dev_mgr, PUSB_DRIVER pdriver)
{
    return gendrv_if_driver_destroy(dev_mgr, pdriver);
}

BOOLEAN
gendrv_connect(PDEV_CONNECT_DATA param, DEV_HANDLE dev_handle)
{
    PURB purb;
    PUSB_CTRL_SETUP_PACKET psetup;
    NTSTATUS status;
    PUCHAR buf;
    LONG i;
    PUSB_CONFIGURATION_DESC pconfig_desc;
    PUSB_DEV_MANAGER dev_mgr;

    if (param == NULL || dev_handle == 0)
        return FALSE;

    dev_mgr = param->dev_mgr;

    // let's set the configuration
    purb = usb_alloc_mem(NonPagedPool, sizeof(URB));
    if (purb == NULL)
        return FALSE;

    buf = usb_alloc_mem(NonPagedPool, 512);
    if (buf == NULL)
    {
        usb_dbg_print(DBGLVL_MAXIMUM, ("gendrv_connect(): can not alloc buf\n"));
        usb_free_mem(purb);
        return FALSE;
    }

    // before we set the configuration, let's search to find if there
    // exist interfaces we supported
    psetup = (PUSB_CTRL_SETUP_PACKET) (purb)->setup_packet;
    urb_init((purb));
    purb->endp_handle = dev_handle | 0xffff;
    purb->data_buffer = buf;
    purb->data_length = 512;
    purb->completion = NULL;    // this is an immediate request, no completion required
    purb->context = NULL;
    purb->reference = 0;
    psetup->bmRequestType = 0x80;
    psetup->bRequest = USB_REQ_GET_DESCRIPTOR;
    psetup->wValue = USB_DT_CONFIG << 8;
    psetup->wIndex = 0;
    psetup->wLength = 512;

    status = usb_submit_urb(dev_mgr, purb);
    if (status == STATUS_PENDING)
    {
        TRAP();
        usb_free_mem(buf);
        usb_free_mem(purb);
        return FALSE;
    }

    // check the config desc valid
    pconfig_desc = (PUSB_CONFIGURATION_DESC) buf;
    if (pconfig_desc->wTotalLength > 512)
    {
        usb_free_mem(buf);
        usb_free_mem(purb);
        usb_dbg_print(DBGLVL_MAXIMUM, ("gendrv_connect(): error, bad configuration desc\n"));
        return FALSE;
    }

    i = pconfig_desc->bConfigurationValue;
    usb_free_mem(buf);
    buf = NULL;

    //set the configuration
    urb_init(purb);
    purb->endp_handle = dev_handle | 0xffff;
    purb->data_buffer = NULL;
    purb->data_length = 0;
    purb->completion = gendrv_set_cfg_completion;
    purb->context = dev_mgr;
    purb->reference = (ULONG) param->pdriver;
    psetup->bmRequestType = 0;
    psetup->bRequest = USB_REQ_SET_CONFIGURATION;
    psetup->wValue = (USHORT) i;
    psetup->wIndex = 0;
    psetup->wLength = 0;

    usb_dbg_print(DBGLVL_MAXIMUM, ("gendrv_connect(): start config the device, cfgval=%d\n", i));
    status = usb_submit_urb(dev_mgr, purb);

    if (status != STATUS_PENDING)
    {
        usb_free_mem(purb);

        if (status == STATUS_SUCCESS)
            return TRUE;

        return FALSE;
    }

    return TRUE;
}

BOOLEAN
gendrv_event_select_driver(PUSB_DEV pdev,       //always null. we do not use this param
                           ULONG event, ULONG context, ULONG param)
{
    //
    // try to search the registry to find one driver.
    // if found, create the PDO, load the driver.
    // and call its AddDevice.
    //
    LONG i;
    PUSB_DRIVER pdrvr;
    PGENDRV_DRVR_EXTENSION pdrvr_ext;
    PGENDRV_EXT_DRVR_ENTRY pentry;
    PGENDRV_DEVICE_EXTENSION pdev_ext;
    PUSB_CONFIGURATION_DESC pconfig_desc;
    PUSB_DEV_MANAGER dev_mgr;

    PDEVICE_OBJECT pdev_obj;
    PDRIVER_OBJECT pdrvr_obj;
    PLIST_ENTRY pthis, pnext;

    USE_BASIC_NON_PENDING_IRQL;

    UNREFERENCED_PARAMETER(context);
    UNREFERENCED_PARAMETER(event);

    if (pdev == NULL)
        return FALSE;

    usb_dbg_print(DBGLVL_MAXIMUM, ("gendrv_event_select_driver(): entering...\n"));

    pdrvr = (PUSB_DRIVER) param;
    //original code: pconfig_desc = (PUSB_CONFIGURATION_DESC) pdev->desc_buf[sizeof(USB_DEVICE_DESC)];
    pconfig_desc = (PUSB_CONFIGURATION_DESC) &pdev->desc_buf[sizeof(USB_DEVICE_DESC)];
    pdrvr_ext = (PGENDRV_DRVR_EXTENSION) pdrvr->driver_ext;

    //
    // well, let's do the hard work to see if there is a class driver
    // for this device.
    // in the event routine, we have no need to check if the device is zomb or
    // not, it must be alive there.
    //
    i = gendrv_make_key((PUSB_DESC_HEADER) pdev->pusb_dev_desc);
    if (i == -1)
    {
        return FALSE;
    }

    pdrvr_obj = gendrv_find_drvr_by_key(pdrvr_ext, (ULONG) i);
    if (!pdrvr_obj)
    {
        if ((pdrvr_obj = gendrv_load_ext_drvr(pdrvr_ext, (PUSB_DESC_HEADER) pdev->pusb_dev_desc)) == NULL)
            return FALSE;
    }

    dev_mgr = dev_mgr_from_dev(pdev);
    pdev_obj = gendrv_create_device(dev_mgr, pdrvr, usb_make_handle(pdev->dev_id, 0, 0));
    if (pdev_obj == NULL)
    {
        goto ERROR_OUT;
    }

    lock_dev(pdev, FALSE);
    if (dev_state(pdev) == USB_DEV_STATE_ZOMB ||
        dev_mgr_set_driver(dev_mgr, usb_make_handle(pdev->dev_id, 0, 0), pdrvr, pdev) == FALSE)
    {
        unlock_dev(pdev, FALSE);
        gendrv_delete_device(dev_mgr, pdev_obj);
        goto ERROR_OUT;
    }

    if (pdev->usb_config)
    {
        pdev->dev_obj = pdev_obj;
    }

    unlock_dev(pdev, FALSE);

    pdev_ext = (PGENDRV_DEVICE_EXTENSION) pdev_obj->DeviceExtension;
    pdev_ext->desc_buf = usb_alloc_mem(NonPagedPool, 512);
    if (!pdev_ext->desc_buf)
    {
        usb_dbg_print(DBGLVL_MAXIMUM, ("gendrv_event_select_driver(): memory allocation failed!\n"));
        goto ERROR_OUT;
    }

    RtlCopyMemory(pdev_ext->desc_buf, pconfig_desc, 512);

    // insert the device to the dev_list
    ExAcquireFastMutex(&pdrvr_ext->drvr_ext_mutex);
    ListFirst(&pdrvr_ext->ext_drvr_list, pthis);
    pentry = NULL;
    while (pthis)
    {
        pentry = (PGENDRV_EXT_DRVR_ENTRY) pthis;
        if (pentry->pext_drvr == pdrvr_obj)
            break;
        ListNext(&pdrvr_ext->ext_drvr_list, pthis, pnext);
        pthis = pnext;
        pentry = NULL;
    }
    ASSERT(pentry);
    InsertTailList(&pentry->dev_list, &pdev_ext->dev_obj_link);
    pdev_ext->ext_drvr_entry = pentry;
    pentry->ref_count++;
    ExReleaseFastMutex(&pdrvr_ext->drvr_ext_mutex);

    // notify the class driver, some device comes
    gendrv_send_pnp_msg(GENDRV_MSG_ADDDEVICE, pdev_obj, pdrvr_obj);
    usb_unlock_dev(pdev);
    return TRUE;

  ERROR_OUT:

    usb_unlock_dev(pdev);
    return FALSE;
}

VOID
gendrv_set_cfg_completion(PURB purb, PVOID context)
{
    DEV_HANDLE dev_handle;
    PUSB_DEV_MANAGER dev_mgr;
    PUSB_DRIVER pdriver;
    NTSTATUS status;
    PUSB_DEV pdev;
    PUSB_EVENT pevent;
    USE_BASIC_NON_PENDING_IRQL;

    if (purb == NULL || context == NULL)
        return;

    dev_handle = purb->endp_handle & ~0xffff;
    dev_mgr = (PUSB_DEV_MANAGER) context;
    pdriver = (PUSB_DRIVER) purb->reference;

    if (purb->status != STATUS_SUCCESS)
    {
        usb_free_mem(purb);
        return;
    }

    usb_free_mem(purb);
    purb = NULL;

    // set the dev state
    status = usb_query_and_lock_dev(dev_mgr, dev_handle, &pdev);
    if (status != STATUS_SUCCESS)
    {
        usb_unlock_dev(pdev);
        return;
    }
    usb_unlock_dev(pdev);       // safe to release the pdev ref since we are in urb completion


    KeAcquireSpinLockAtDpcLevel(&dev_mgr->event_list_lock);
    lock_dev(pdev, TRUE);

    if (dev_state(pdev) >= USB_DEV_STATE_BEFORE_ZOMB)
    {
        unlock_dev(pdev, TRUE);
        KeReleaseSpinLockFromDpcLevel(&dev_mgr->event_list_lock);
        return;
    }

    if (dev_mgr_set_driver(dev_mgr, dev_handle, pdriver, pdev) == FALSE)
    {
        unlock_dev(pdev, TRUE);
        KeReleaseSpinLockFromDpcLevel(&dev_mgr->event_list_lock);
        return;
    }

    //transit the state to configured
    pdev->flags &= ~USB_DEV_STATE_MASK;
    pdev->flags |= USB_DEV_STATE_CONFIGURED;

    pevent = alloc_event(&dev_mgr->event_pool, 1);
    if (pevent == NULL)
    {
        unlock_dev(pdev, TRUE);
        KeReleaseSpinLockFromDpcLevel(&dev_mgr->event_list_lock);
        return;
    }

    pevent->flags = USB_EVENT_FLAG_ACTIVE;
    pevent->event = USB_EVENT_DEFAULT;
    pevent->pdev = pdev;
    pevent->context = 0;
    pevent->param = (ULONG) pdriver;
    pevent->pnext = 0;          //vertical queue for serialized operation
    pevent->process_event = (PROCESS_EVENT) gendrv_event_select_driver;
    pevent->process_queue = event_list_default_process_queue;

    InsertTailList(&dev_mgr->event_list, &pevent->event_link);
    KeSetEvent(&dev_mgr->wake_up_event, 0, FALSE);
    unlock_dev(pdev, TRUE);
    KeReleaseSpinLockFromDpcLevel(&dev_mgr->event_list_lock);

    return;
}


BOOLEAN
gendrv_stop(PUSB_DEV_MANAGER dev_mgr, DEV_HANDLE dev_handle)
{
    if (dev_mgr == NULL)
        return FALSE;
    return gendrv_do_stop(dev_mgr, dev_handle, FALSE);
}

BOOLEAN
gendrv_disconnect(PUSB_DEV_MANAGER dev_mgr, DEV_HANDLE dev_handle)
{
    if (dev_mgr == NULL)
        return FALSE;
    return gendrv_do_disconnect(dev_mgr, dev_handle, FALSE);
}

BOOLEAN
gendrv_build_reg_string(PUSB_DESC_HEADER pdesc, PUNICODE_STRING pus)
{

    CHAR desc_str[128];
    STRING atemp;

    if (pdesc == NULL || pus == NULL)
        return FALSE;

    if (pdesc->bDescriptorType == USB_DT_DEVICE)
    {
        PUSB_DEVICE_DESC pdev_desc;
        pdev_desc = (PUSB_DEVICE_DESC) pdesc;
        sprintf(desc_str, "%sv_%04x&p_%04x",
                "\\Registry\\Machine\\System\\CurrentControlSet\\Services\\ehci\\device\\",
                pdev_desc->idVendor, pdev_desc->idProduct);
    }
    else if (pdesc->bDescriptorType == USB_DT_INTERFACE)
    {
        PUSB_INTERFACE_DESC pif_desc;
        pif_desc = (PUSB_INTERFACE_DESC) pdesc;
        sprintf(desc_str, "%sc_%04x&s_%04x&p_%04x",
                "\\Registry\\Machine\\System\\CurrentControlSet\\Services\\ehci\\interface\\",
                pif_desc->bInterfaceClass, pif_desc->bInterfaceSubClass, pif_desc->bInterfaceProtocol);
    }
    else
        return FALSE;

    RtlInitString(&atemp, desc_str);
    RtlAnsiStringToUnicodeString(pus, &atemp, TRUE);
    return TRUE;
}

ULONG
gendrv_make_key(PUSB_DESC_HEADER pdesc)
{
    PUSB_DEVICE_DESC pdev_desc;
    PUSB_INTERFACE_DESC pif_desc;

    if (pdesc == NULL)
        return (ULONG) - 1;
    if (pdesc->bDescriptorType == USB_DT_DEVICE)
    {
        pdev_desc = (PUSB_DEVICE_DESC) pdesc;
        return ((((ULONG) pdev_desc->idVendor) << 16) | pdev_desc->idProduct);
    }
    else if (pdesc->bDescriptorType == USB_DT_INTERFACE)
    {
        pif_desc = (PUSB_INTERFACE_DESC) pdesc;
        return ((((ULONG) pif_desc->bInterfaceClass) << 16) |
                (((ULONG) pif_desc->bInterfaceSubClass) << 8) | ((ULONG) pif_desc->bInterfaceProtocol));
    }
    return (ULONG) - 1;
}

PDRIVER_OBJECT
gendrv_find_drvr_by_key(PGENDRV_DRVR_EXTENSION pdrvr_ext, ULONG key)
{
    PGENDRV_EXT_DRVR_ENTRY pentry;
    PLIST_ENTRY pthis, pnext;
    if (pdrvr_ext == NULL || key == (ULONG) - 1)
        return NULL;

    ExAcquireFastMutex(&pdrvr_ext->drvr_ext_mutex);
    ListFirst(&pdrvr_ext->ext_drvr_list, pthis);
    while (pthis)
    {
        pentry = (PGENDRV_EXT_DRVR_ENTRY) pthis;
        if (pentry->drvr_key == key)
        {
            ExReleaseFastMutex(&pdrvr_ext->drvr_ext_mutex);
            return pentry->pext_drvr;
        }
        ListNext(&pdrvr_ext->ext_drvr_list, pthis, pnext);
        pthis = pnext;
    }
    ExReleaseFastMutex(&pdrvr_ext->drvr_ext_mutex);

    return NULL;
}

PDRIVER_OBJECT
gendrv_load_ext_drvr(PGENDRV_DRVR_EXTENSION pdrvr_ext, PUSB_DESC_HEADER pdesc)
{
    PDRIVER_OBJECT pdrvr_obj;
    PGENDRV_EXT_DRVR_ENTRY pentry;
    UNICODE_STRING usz, svc_name, svc_key, utemp;
    PKEY_VALUE_FULL_INFORMATION val_info;
    PWCHAR val_buf;
    HANDLE handle;
    NTSTATUS status;

    if (pdrvr_ext == NULL || pdesc == NULL)
        return NULL;

    // try to search and load driver from outside
    handle = NULL;
    RtlZeroMemory(&svc_key, sizeof(svc_key));
    val_info = NULL;
    RtlInitUnicodeString(&usz, L"");
    gendrv_build_reg_string(pdesc, &usz);
DbgPrint("UHCI: Trying to load driver %wZ\n", &usz);
    if (gendrv_open_reg_key(&handle, NULL, &usz, KEY_READ, FALSE) != STATUS_SUCCESS)
    {
        goto ERROR_OUT;
    }
    if (gendrv_get_key_value(handle, L"service", &val_info) != STATUS_SUCCESS)
    {
        goto ERROR_OUT;
    }

    if (val_info->DataLength > 32)
        goto ERROR_OUT;

    val_buf = (PWCHAR) (((PBYTE) val_info) + val_info->DataOffset);
    svc_key.Length = 0, svc_key.MaximumLength = 255;
    svc_key.Buffer = usb_alloc_mem(NonPagedPool, 256);

    RtlInitUnicodeString(&utemp, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\");
    RtlAppendUnicodeStringToString(&svc_key, &utemp);
    RtlInitUnicodeString(&svc_name, val_buf);
    RtlAppendUnicodeStringToString(&svc_key, &svc_name);

    status = ZwLoadDriver(&svc_key);
    if (status != STATUS_SUCCESS)
        goto ERROR_OUT;

    svc_key.Length = 0;
    RtlZeroMemory(svc_key.Buffer, 128);
    RtlInitUnicodeString(&svc_key, L"\\Driver\\");
    RtlAppendUnicodeStringToString(&svc_key, &svc_name);
    pdrvr_obj = gendrv_open_ext_driver(&svc_key);
    if (pdrvr_obj == NULL)
        goto ERROR_OUT;

    ExAcquireFastMutex(&pdrvr_ext->drvr_ext_mutex);

    // insert the driver to the drvr list
    pentry = gendrv_alloc_ext_drvr_entry(pdrvr_ext);
    if (pentry == NULL)
    {
        ExReleaseFastMutex(&pdrvr_ext->drvr_ext_mutex);
        ObDereferenceObject(pdrvr_obj);
        goto ERROR_OUT;
    }
    pentry->pext_drvr = pdrvr_obj;
    InsertTailList(&pdrvr_ext->ext_drvr_list, &pentry->drvr_link);
    pdrvr_ext->ext_drvr_count++;
    ExReleaseFastMutex(&pdrvr_ext->drvr_ext_mutex);
    ZwClose(handle);
    return pdrvr_obj;

ERROR_OUT:
    RtlFreeUnicodeString(&usz);
    if (val_info != NULL)
    {
        usb_free_mem(val_info);
        val_info = NULL;
    }
    if (svc_key.Buffer)
        usb_free_mem(svc_key.Buffer);

    if (handle)
        ZwClose(handle);

    return NULL;
}

VOID
gendrv_release_drvr(PGENDRV_DRVR_EXTENSION pdrvr_ext, PDRIVER_OBJECT pdrvr_obj)
{
    PLIST_ENTRY pthis, pnext;
    PGENDRV_EXT_DRVR_ENTRY pentry;

    if (pdrvr_ext == NULL || pdrvr_obj == NULL)
        return;
    ExAcquireFastMutex(&pdrvr_ext->drvr_ext_mutex);
    ListFirst(&pdrvr_ext->ext_drvr_list, pthis);
    while (pthis)
    {
        pentry = (PGENDRV_EXT_DRVR_ENTRY) pthis;
        if (pentry->pext_drvr == pdrvr_obj)
        {
            ASSERT(pentry->ref_count);
            ExReleaseFastMutex(&pdrvr_ext->drvr_ext_mutex);
            return;
        }
        ListNext(&pdrvr_ext->ext_drvr_list, pthis, pnext);
        pthis = pnext;
    }
    ExReleaseFastMutex(&pdrvr_ext->drvr_ext_mutex);
}

NTSTATUS
gendrv_send_pnp_msg(ULONG msg, PDEVICE_OBJECT pdev_obj, PVOID pctx)
{
    if (pdev_obj == NULL)
        return STATUS_INVALID_PARAMETER;

    switch (msg)
    {
        case GENDRV_MSG_ADDDEVICE:
        {
            PDRIVER_OBJECT pdrvr_obj;
            if (pctx == NULL)
                return STATUS_INVALID_PARAMETER;
            pdrvr_obj = (PDRIVER_OBJECT) pctx;
            if (pdrvr_obj->DriverExtension)
            {
                return pdrvr_obj->DriverExtension->AddDevice(pdrvr_obj, pdev_obj);
            }
            return STATUS_IO_DEVICE_ERROR;
        }
        case GENDRV_MSG_STOPDEVICE:
        case GENDRV_MSG_DISCDEVICE:
        {
            NTSTATUS status;
            IO_STACK_LOCATION *irpstack;
            IRP *irp;
            // IRP_MJ_PNP_POWER
            irp = IoAllocateIrp(2, FALSE);
            if (irp == NULL)
                return STATUS_NO_MEMORY;

            irpstack = IoGetNextIrpStackLocation(irp);
            irpstack->MajorFunction = IRP_MJ_PNP_POWER;
            irpstack->MinorFunction =
                (msg == GENDRV_MSG_STOPDEVICE) ? IRP_MN_STOP_DEVICE : IRP_MN_REMOVE_DEVICE;
            status = IoCallDriver(pdev_obj, irp);
            ASSERT(status != STATUS_PENDING);
            status = irp->IoStatus.Status;
            IoFreeIrp(irp);
            return STATUS_MORE_PROCESSING_REQUIRED;
        }
    }
    return STATUS_INVALID_PARAMETER;
}


BOOLEAN
gendrv_if_connect(PDEV_CONNECT_DATA params, DEV_HANDLE if_handle)
{
    //
    // try to search the registry to find one driver.
    // if found, create the PDO, load the driver.
    // and call its AddDevice.
    //
    LONG if_idx, i;
    NTSTATUS status;
    PUSB_DEV pdev;
    PUSB_DRIVER pdrvr;
    PUSB_INTERFACE_DESC pif_desc;
    PGENDRV_DEVICE_EXTENSION pdev_ext;
    PUSB_CONFIGURATION_DESC pconfig_desc;
    PUSB_DEV_MANAGER dev_mgr;
    PGENDRV_DRVR_EXTENSION pdrvr_ext;
    PGENDRV_EXT_DRVR_ENTRY pentry;

    PDEVICE_OBJECT pdev_obj;
    PDRIVER_OBJECT pdrvr_obj;
    PLIST_ENTRY pthis, pnext;
    USE_BASIC_NON_PENDING_IRQL;

    pdev = NULL;
    usb_dbg_print(DBGLVL_MAXIMUM, ("gendrv_if_connect(): entering...\n"));

    if (params == NULL)
        return FALSE;

    dev_mgr = params->dev_mgr;
    pdrvr = params->pdriver;
    pdrvr_ext = (PGENDRV_DRVR_EXTENSION) pdrvr->driver_ext;

    status = usb_query_and_lock_dev(dev_mgr, if_handle, &pdev);
    if (status != STATUS_SUCCESS)
    {
        goto ERROR_OUT;
    }
    // obtain the pointer to the config desc, the dev won't go away in this routine
    pconfig_desc = pdev->usb_config->pusb_config_desc;  //
    usb_unlock_dev(pdev);
    pdev = NULL;

    if_idx = if_idx_from_handle(if_handle);
    pif_desc = (PUSB_INTERFACE_DESC) (&pconfig_desc[1]);

    for(i = 0; i < if_idx; i++)
    {
        //search for our if
        if (usb_skip_if_and_altif((PUCHAR *) & pif_desc) == FALSE)
            break;
    }
    if (pif_desc == NULL)
        return FALSE;

    //
    // well, let's do the hard work to see if there is a class driver
    // for this device.
    //
    i = gendrv_make_key((PUSB_DESC_HEADER) pif_desc);
    if (i == -1)
    {
        return FALSE;
    }

    pdrvr_obj = gendrv_find_drvr_by_key(pdrvr_ext, (ULONG) i);
    if (!pdrvr_obj)
    {
        if ((pdrvr_obj = gendrv_load_ext_drvr(pdrvr_ext, (PUSB_DESC_HEADER) pif_desc)) == NULL)
            return FALSE;
    }


    pdev_obj = gendrv_create_device(dev_mgr, pdrvr, if_handle);
    if (pdev_obj == NULL)
    {
        goto ERROR_OUT;
    }

    lock_dev(pdev, FALSE);
    if (dev_state(pdev) == USB_DEV_STATE_ZOMB ||
        dev_mgr_set_if_driver(dev_mgr, if_handle, pdrvr, pdev) == FALSE)
    {
        unlock_dev(pdev, FALSE);
        gendrv_delete_device(dev_mgr, pdev_obj);
        goto ERROR_OUT;
    }

    if (pdev->usb_config)
    {
        pdev->usb_config->interf[if_idx].if_ext = pdev_obj;
        pdev->usb_config->interf[if_idx].if_ext_size = 0;
    }

    unlock_dev(pdev, FALSE);

    pdev_ext = (PGENDRV_DEVICE_EXTENSION) pdev_obj->DeviceExtension;
    pdev_ext->desc_buf = usb_alloc_mem(NonPagedPool, 512);
    if (!pdev_ext->desc_buf)
    {
        usb_dbg_print(DBGLVL_MAXIMUM, ("gendrv_if_connect(): memory allocation failed!\n"));
        goto ERROR_OUT;
    }

    RtlCopyMemory(pdev_ext->desc_buf, pconfig_desc, 512);
    pdev_ext->if_ctx.pif_desc =
        (PUSB_INTERFACE_DESC) & pdev_ext->desc_buf[(PBYTE) pif_desc - (PBYTE) pconfig_desc];

    // insert the device to the dev_list
    ExAcquireFastMutex(&pdrvr_ext->drvr_ext_mutex);
    ListFirst(&pdrvr_ext->ext_drvr_list, pthis);
    pentry = NULL;
    while (pthis)
    {
        pentry = (PGENDRV_EXT_DRVR_ENTRY) pthis;
        if (pentry->pext_drvr == pdrvr_obj)
            break;
        ListNext(&pdrvr_ext->ext_drvr_list, pthis, pnext);
        pthis = pnext;
        pentry = NULL;
    }
    ASSERT(pentry);
    InsertTailList(&pentry->dev_list, &pdev_ext->dev_obj_link);
    pdev_ext->ext_drvr_entry = pentry;
    pentry->ref_count++;
    ExReleaseFastMutex(&pdrvr_ext->drvr_ext_mutex);

    // notify the class driver, some device comes
    gendrv_send_pnp_msg(GENDRV_MSG_ADDDEVICE, pdev_obj, pdrvr_obj);
    usb_unlock_dev(pdev);
    return TRUE;

  ERROR_OUT:

    usb_unlock_dev(pdev);
    return FALSE;
}

BOOLEAN
gendrv_do_stop(PUSB_DEV_MANAGER dev_mgr, DEV_HANDLE dev_handle, BOOLEAN is_if)
{
    PUSB_DEV pdev;
    PDEVICE_OBJECT pdev_obj;
    ULONG if_idx;

    if (dev_mgr == NULL)
        return FALSE;

    // clean up the irps
    if_idx = if_idx_from_handle(dev_handle);
    if (usb_query_and_lock_dev(dev_mgr, dev_handle, &pdev) != STATUS_SUCCESS)
    {
        return FALSE;
    }
    if (is_if && pdev->usb_config)
        pdev_obj = (PDEVICE_OBJECT) pdev->usb_config->interf[if_idx].if_ext;
    else
        pdev_obj = pdev->dev_obj;

    gendrv_clean_up_queued_irps(pdev_obj);
    usb_unlock_dev(pdev);

    // send message to class drivers.
    gendrv_send_pnp_msg(GENDRV_MSG_STOPDEVICE, pdev_obj, NULL);

    return TRUE;
}

BOOLEAN
gendrv_if_stop(PUSB_DEV_MANAGER dev_mgr, DEV_HANDLE dev_handle)
{
    if (dev_mgr == NULL)
        return FALSE;

    return gendrv_do_stop(dev_mgr, dev_handle, TRUE);
}

BOOLEAN
gendrv_do_disconnect(PUSB_DEV_MANAGER dev_mgr, DEV_HANDLE if_handle, BOOLEAN is_if)
{
    PUSB_DEV pdev;
    PDEVICE_OBJECT dev_obj = NULL;
    NTSTATUS status;
    PUSB_DRIVER pdrvr;
    PGENDRV_DRVR_EXTENSION pdrvr_ext = NULL;
    PGENDRV_DEVICE_EXTENSION pdev_ext = NULL;
    ULONG if_idx;

    status = usb_query_and_lock_dev(dev_mgr, if_handle, &pdev);
    if (pdev == NULL)
    {
        return FALSE;
    }
    if (status == STATUS_SUCCESS)
    {
        // must be a bug
        TRAP();
    }
    if_idx = if_idx_from_handle(if_handle);
    if (pdev->usb_config)
    {
        if (is_if)
        {
            pdrvr = pdev->usb_config->interf[if_idx].pif_drv;
            dev_obj = (PDEVICE_OBJECT) pdev->usb_config->interf[if_idx].if_ext;
        }
        else
        {
            pdrvr = pdev->dev_driver;
            dev_obj = pdev->dev_obj;
        }

        if (dev_obj == NULL)
        {
            // it means no driver was found for the device and thus no device object created
            // we just do nothing here
            return TRUE;
        }

        pdrvr_ext = (PGENDRV_DRVR_EXTENSION) pdrvr->driver_ext;
        pdev_ext = (PGENDRV_DEVICE_EXTENSION) dev_obj->DeviceExtension;
    }
    else
        TRAP();
    pdev = NULL;

    // remove the device from the list
    ExAcquireFastMutex(&pdrvr_ext->drvr_ext_mutex);
    RemoveEntryList(&pdev_ext->dev_obj_link);
    pdev_ext->ext_drvr_entry->ref_count--;
    ExReleaseFastMutex(&pdrvr_ext->drvr_ext_mutex);

    // send message to class driver
    gendrv_send_pnp_msg(GENDRV_MSG_DISCDEVICE, dev_obj, NULL);
    // delete the device object
    gendrv_delete_device(dev_mgr, dev_obj);
    return TRUE;
}

BOOLEAN
gendrv_if_disconnect(PUSB_DEV_MANAGER dev_mgr, DEV_HANDLE if_handle)
{
    return gendrv_do_disconnect(dev_mgr, if_handle, TRUE);
}

BOOLEAN
gendrv_if_driver_init(PUSB_DEV_MANAGER dev_mgr, PUSB_DRIVER pdriver)
{
    PGENDRV_DRVR_EXTENSION pdrvr_ext;
    if (dev_mgr == NULL || pdriver == NULL)
        return FALSE;

    pdriver->driver_desc.flags = USB_DRIVER_FLAG_IF_CAPABLE;
    pdriver->driver_desc.vendor_id = 0x0000;    // USB Vendor ID
    pdriver->driver_desc.product_id = 0x0000;   // USB Product ID.
    pdriver->driver_desc.release_num = 0x100;   // Release Number of Device

    pdriver->driver_desc.config_val = 0;        // Configuration Value
    pdriver->driver_desc.if_num = 0;            // Interface Number
    pdriver->driver_desc.if_class = 0x0;        // Interface Class
    pdriver->driver_desc.if_sub_class = 0x0;    // Interface SubClass
    pdriver->driver_desc.if_protocol = 0x0;     // Interface Protocol

    pdriver->driver_desc.driver_name = "USB generic interface driver";  // Driver name for Name Registry
    pdriver->driver_desc.dev_class = 0;
    pdriver->driver_desc.dev_sub_class = 0;     // Device Subclass
    pdriver->driver_desc.dev_protocol = 0;      // Protocol Info.

    //we have no extra data sturcture currently

    pdriver->disp_tbl.version = 1;
    pdriver->disp_tbl.dev_connect = gendrv_if_connect;
    pdriver->disp_tbl.dev_disconnect = gendrv_if_disconnect;
    pdriver->disp_tbl.dev_stop = gendrv_if_stop;
    pdriver->disp_tbl.dev_reserved = NULL;

    pdriver->driver_ext = usb_alloc_mem(NonPagedPool, sizeof(GENDRV_DRVR_EXTENSION));
    if (!pdriver->driver_ext) return FALSE;

    pdriver->driver_ext_size = sizeof(GENDRV_DRVR_EXTENSION);

    RtlZeroMemory(pdriver->driver_ext, pdriver->driver_ext_size);
    pdrvr_ext = (PGENDRV_DRVR_EXTENSION) pdriver->driver_ext;

    // InitializeListHead( &pdrvr_ext->dev_list );
    InitializeListHead(&pdrvr_ext->ext_drvr_list);
    pdrvr_ext->ext_drvr_count = 0;
    ExInitializeFastMutex(&pdrvr_ext->drvr_ext_mutex);

    return TRUE;
}

BOOLEAN
gendrv_if_driver_destroy(PUSB_DEV_MANAGER dev_mgr, PUSB_DRIVER pdriver)
{
    PGENDRV_DRVR_EXTENSION pdrvr_ext;
    PLIST_ENTRY pthis;
    PGENDRV_EXT_DRVR_ENTRY pentry;
    if (dev_mgr == NULL || pdriver == NULL)
        return FALSE;

    if (pdriver->driver_ext)
    {
        // should we lock it?
        // ExAcquireFastMutex( &pdrvr_ext->drvr_ext_mutex );
        pdrvr_ext = (PGENDRV_DRVR_EXTENSION) pdriver->driver_ext;
        if (pdrvr_ext->ext_drvr_count)
        {
            while (IsListEmpty(&pdrvr_ext->ext_drvr_list))
            {
                pthis = RemoveHeadList(&pdrvr_ext->ext_drvr_list);
                pentry = (PGENDRV_EXT_DRVR_ENTRY) pthis;
                if (pentry->pext_drvr)
                {
                    if (pentry->ref_count)
                    {
                        // FIXME: really fail?
                        continue;
                    }
                    ObDereferenceObject(pentry->pext_drvr);
                    gendrv_release_ext_drvr_entry(pdrvr_ext, pentry);
                }
            }
            pdrvr_ext->ext_drvr_count = 0;
        }

        usb_free_mem(pdriver->driver_ext);
        pdriver->driver_ext = NULL;
        pdriver->driver_ext_size = 0;
    }
    return TRUE;
}

PDRIVER_OBJECT
gendrv_open_ext_driver(PUNICODE_STRING unicode_string)
{
    NTSTATUS status;
    OBJECT_ATTRIBUTES oa;
    HANDLE drvr_handle;
    UNICODE_STRING oname;
    PDRIVER_OBJECT pdrvr = NULL;

    RtlZeroMemory(&oa, sizeof(oa));
    oa.Length = sizeof(oa);
    oa.ObjectName = &oname;
    oa.Attributes = OBJ_CASE_INSENSITIVE;
    RtlInitUnicodeString(&oname, L"");
    RtlAppendUnicodeStringToString(&oname, unicode_string);

    status = ObOpenObjectByName(&oa, IoDriverObjectType,   // object type
                                KernelMode,               // access mode
                                NULL,                    // access state
                                FILE_READ_DATA,         // STANDARD_RIGHTS_READ, access right
                                NULL,
                                &drvr_handle);

    if (status != STATUS_SUCCESS)
    {
        return NULL;
    }
    ObReferenceObjectByHandle(drvr_handle,
                              FILE_READ_DATA,
                              IoDriverObjectType,
                              KernelMode,
                              (PVOID)&pdrvr,
                              NULL); // OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL

    ZwClose(drvr_handle);
    return pdrvr;
}

BOOLEAN
gendrv_close_ext_driver(PDRIVER_OBJECT pdrvr)
{
    if (pdrvr == NULL)
        return FALSE;
    ObDereferenceObject(pdrvr);
    return TRUE;
}

NTSTATUS
gendrv_dispatch(PDEVICE_OBJECT dev_obj, PIRP irp)
{
    IO_STACK_LOCATION *irpstack;
    PUSB_DEV_MANAGER dev_mgr;
    PDEVEXT_HEADER ext_hdr;
    NTSTATUS status;

    if (dev_obj == NULL || irp == NULL)
        return STATUS_INVALID_PARAMETER;

    ext_hdr = dev_obj->DeviceExtension;
    dev_mgr = ext_hdr->dev_mgr;

    irpstack = IoGetNextIrpStackLocation(irp);
    switch (irpstack->MajorFunction)
    {
        case IRP_MJ_PNP_POWER:
        {
            irp->IoStatus.Information = 0;
            GENDRV_EXIT_DISPATCH(dev_obj, STATUS_SUCCESS, irp);
        }
        case IRP_MJ_INTERNAL_DEVICE_CONTROL:
        {
            status = STATUS_NOT_SUPPORTED;
            if (irpstack->Parameters.DeviceIoControl.IoControlCode == IOCTL_SUBMIT_URB_RD ||
                irpstack->Parameters.DeviceIoControl.IoControlCode == IOCTL_SUBMIT_URB_WR ||
                irpstack->Parameters.DeviceIoControl.IoControlCode == IOCTL_SUBMIT_URB_NOIO)
            {
                PURB purb;
                DEV_HANDLE endp_handle;
                PGENDRV_DEVICE_EXTENSION pdev_ext;

                pdev_ext = dev_obj->DeviceExtension;
                if (irpstack->Parameters.DeviceIoControl.InputBufferLength < sizeof(URB))
                {
                    GENDRV_EXIT_DISPATCH(dev_obj, STATUS_INVALID_PARAMETER, irp);
                }

                purb = (PURB) irp->AssociatedIrp.SystemBuffer;
                endp_handle = purb->endp_handle;
                if (purb->data_buffer == NULL || purb->data_length == 0)
                {
                    if (irpstack->Parameters.DeviceIoControl.IoControlCode != IOCTL_SUBMIT_URB_NOIO)
                    {
                        GENDRV_EXIT_DISPATCH(dev_obj, STATUS_INVALID_PARAMETER, irp);
                    }
                }
                if (!default_endp_handle(endp_handle))
                {
                    //no permit to other interface if interface dev
                    if (if_dev(dev_obj) && if_idx_from_handle(endp_handle) != pdev_ext->if_ctx.if_idx)
                        GENDRV_EXIT_DISPATCH(dev_obj, STATUS_INVALID_PARAMETER, irp);
                }

                GENDRV_EXIT_DISPATCH(dev_obj, STATUS_PENDING, irp);
            }
            else if (irpstack->Parameters.DeviceIoControl.IoControlCode == IOCTL_GET_DEV_DESC)
            {
                // this is a synchronous call, route to dev_mgr_dispatch
                return dev_mgr_dispatch(dev_mgr, irp);
            }
            else if (irpstack->Parameters.DeviceIoControl.IoControlCode == IOCTL_GET_DEV_HANDLE)
            {
                PGENDRV_DEVICE_EXTENSION pdev_ext;
                pdev_ext = dev_obj->DeviceExtension;
                if (irpstack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(LONG))
                    GENDRV_EXIT_DISPATCH(dev_obj, STATUS_INVALID_PARAMETER, irp);

                *((PLONG) irp->AssociatedIrp.SystemBuffer) = pdev_ext->dev_handle;
                irp->IoStatus.Information = sizeof(LONG);
                GENDRV_EXIT_DISPATCH(dev_obj, STATUS_SUCCESS, irp);
            }
            GENDRV_EXIT_DISPATCH(dev_obj, STATUS_NOT_SUPPORTED, irp);
        }
        case IRP_MJ_DEVICE_CONTROL:
        {
            status = STATUS_NOT_SUPPORTED;
            if (irpstack->Parameters.DeviceIoControl.IoControlCode == IOCTL_SUBMIT_URB_RD ||
                irpstack->Parameters.DeviceIoControl.IoControlCode == IOCTL_SUBMIT_URB_WR ||
                irpstack->Parameters.DeviceIoControl.IoControlCode == IOCTL_SUBMIT_URB_NOIO)
            {
                PURB purb;
                DEV_HANDLE endp_handle;
                PGENDRV_DEVICE_EXTENSION pdev_ext;

                pdev_ext = dev_obj->DeviceExtension;
                if (irpstack->Parameters.DeviceIoControl.InputBufferLength < sizeof(URB))
                {
                    GENDRV_EXIT_DISPATCH(dev_obj, STATUS_INVALID_PARAMETER, irp);
                }

                purb = (PURB) irp->AssociatedIrp.SystemBuffer;
                endp_handle = purb->endp_handle;
                if (!default_endp_handle(endp_handle))
                {
                    //no permit to other interface if interface dev
                    if (if_dev(dev_obj) && if_idx_from_handle(endp_handle) != pdev_ext->if_ctx.if_idx)
                        GENDRV_EXIT_DISPATCH(dev_obj, STATUS_INVALID_PARAMETER, irp);
                }

                GENDRV_EXIT_DISPATCH(dev_obj, STATUS_PENDING, irp);
            }
            else if (irpstack->Parameters.DeviceIoControl.IoControlCode == IOCTL_GET_DEV_DESC)
            {
                // this is a synchronous call, route to dev_mgr_dispatch
                return dev_mgr_dispatch(dev_mgr, irp);
            }
            else if (irpstack->Parameters.DeviceIoControl.IoControlCode == IOCTL_GET_DEV_HANDLE)
            {
                PGENDRV_DEVICE_EXTENSION pdev_ext;
                pdev_ext = dev_obj->DeviceExtension;
                if (irpstack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(LONG))
                    GENDRV_EXIT_DISPATCH(dev_obj, STATUS_INVALID_PARAMETER, irp);

                *((PLONG) irp->AssociatedIrp.SystemBuffer) = pdev_ext->dev_handle;
                irp->IoStatus.Information = sizeof(LONG);
                GENDRV_EXIT_DISPATCH(dev_obj, STATUS_SUCCESS, irp);
            }
            GENDRV_EXIT_DISPATCH(dev_obj, STATUS_NOT_SUPPORTED, irp);
        }
    }
    irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
    irp->IoStatus.Information = 0;
    IoCompleteRequest(irp, IO_NO_INCREMENT);

    return STATUS_NOT_SUPPORTED;
}

BOOLEAN
gendrv_init_dev_ext_hdr(PDEVICE_OBJECT dev_obj, PUSB_DEV_MANAGER dev_mgr)
{
    PDEVEXT_HEADER dev_hdr = NULL;
    if (dev_obj == NULL || dev_mgr == NULL)
        return FALSE;

    dev_hdr = (PDEVEXT_HEADER) dev_obj->DeviceExtension;
    dev_hdr->type = NTDEV_TYPE_CLIENT_DEV;
    dev_hdr->dispatch = gendrv_dispatch;
    dev_hdr->start_io = (PDRIVER_STARTIO) gendrv_startio;
    return TRUE;
}

PDEVICE_OBJECT
gendrv_create_device(PUSB_DEV_MANAGER dev_mgr, PUSB_DRIVER gen_drvr, DEV_HANDLE dev_handle)
{
    BOOLEAN is_if;
    PDEVICE_OBJECT pdev;
    PGENDRV_DEVICE_EXTENSION pdev_ext;
    ULONG dev_id;
    PGENDRV_DRVR_EXTENSION pdrvr_ext;
    CHAR dev_name[64];
    STRING string;
    UNICODE_STRING name_string, symb_link;
    NTSTATUS status;

    if (dev_mgr == NULL || gen_drvr == NULL || dev_handle == 0)
        return NULL;

    is_if = (gen_drvr->driver_desc.flags & USB_DRIVER_FLAG_IF_CAPABLE) ? 1 : 0;
    usb_dbg_print(DBGLVL_MAXIMUM, ("gendrv_create_device(): entering...\n"));
    pdrvr_ext = (PGENDRV_DRVR_EXTENSION) gen_drvr->driver_ext;
    dev_id = (UCHAR) dev_id_from_handle(dev_handle);

    if (is_if == FALSE)
        sprintf(dev_name, "\\Device\\gendev_%d", (int)dev_id);
    else
        sprintf(dev_name, "\\Device\\genifdev_%d", (int)dev_id);

    RtlInitString(&string, dev_name);
    RtlAnsiStringToUnicodeString(&name_string, &string, TRUE);
    pdev = NULL;

    status = IoCreateDevice(dev_mgr->usb_driver_obj,
                            sizeof(GENDRV_DEVICE_EXTENSION), &name_string, FILE_USB_DEV_TYPE, 0, TRUE, &pdev);

    if (status == STATUS_SUCCESS)
    {
        //
        // We do direct io
        //
        pdev->Flags |= DO_DIRECT_IO;

        pdev->Flags &= ~DO_DEVICE_INITIALIZING;
        pdev->StackSize = 2;    //one for fdo, one for file device obj

        pdev_ext = (PGENDRV_DEVICE_EXTENSION) pdev->DeviceExtension;

        //may be accessed by other thread

        gendrv_init_dev_ext_hdr(pdev, dev_mgr);

        pdev_ext->dev_id = (UCHAR) dev_id;
        pdev_ext->pdo = pdev;
        pdev_ext->dev_handle = dev_handle;
        pdev_ext->dev_mgr = dev_mgr;
        pdev_ext->pdriver = gen_drvr;

        if (is_if == FALSE)
            sprintf(dev_name, "\\DosDevices\\gendev%d", (int)dev_id);
        else
            sprintf(dev_name, "\\DosDevices\\genifdev%d", (int)dev_id);

        RtlInitString(&string, dev_name);
        RtlAnsiStringToUnicodeString(&symb_link, &string, TRUE);
        IoCreateSymbolicLink(&symb_link, &name_string);
        RtlFreeUnicodeString(&symb_link);
        KeInitializeEvent(&pdev_ext->sync_event, SynchronizationEvent, FALSE);
        KeInitializeSpinLock(&pdev_ext->dev_lock);

    }
    RtlFreeUnicodeString(&name_string);
    return pdev;
}



VOID
gendrv_deferred_delete_device(PVOID context)
{
    PDEVICE_OBJECT dev_obj;
    PGENDRV_DEVICE_EXTENSION pdev_ext;
    PGENDRV_DRVR_EXTENSION pdrvr_ext;
    LARGE_INTEGER interval;

    if (context == NULL)
        return;

    dev_obj = (PDEVICE_OBJECT) context;
    pdev_ext = dev_obj->DeviceExtension;
    pdrvr_ext = (PGENDRV_DRVR_EXTENSION) pdev_ext->pdriver->driver_ext;

    interval.QuadPart = -20000; //two ms

    for(;;)
    {
        if (dev_obj->ReferenceCount)
            KeDelayExecutionThread(KernelMode, TRUE, &interval);
        else
        {
            KeDelayExecutionThread(KernelMode, TRUE, &interval);
            if (dev_obj->ReferenceCount == 0)
                break;
        }
    }
    usb_dbg_print(DBGLVL_MAXIMUM, ("gendrv_deferred_delete_device(): delete device, 0x%x\n", dev_obj));

    ExAcquireFastMutex(&pdrvr_ext->drvr_ext_mutex);
    RemoveEntryList(&pdev_ext->dev_obj_link);
    pdev_ext->ext_drvr_entry->ref_count--;
    ExReleaseFastMutex(&pdrvr_ext->drvr_ext_mutex);

    IoDeleteDevice(dev_obj);
    return;
}

BOOLEAN
gendrv_delete_device(PUSB_DEV_MANAGER dev_mgr, PDEVICE_OBJECT dev_obj)
{
    BOOLEAN is_if;
    PUSB_DRIVER pdrvr;
    PGENDRV_DEVICE_EXTENSION pdev_ext;
    CHAR dev_name[64];
    STRING string;
    UNICODE_STRING symb_link;
    PGENDRV_DRVR_EXTENSION pdrvr_ext;

    if (dev_mgr == NULL || dev_obj == 0)
        return FALSE;

    pdev_ext = (PGENDRV_DEVICE_EXTENSION) dev_obj->DeviceExtension;
    pdrvr = pdev_ext->pdriver;
    pdrvr_ext = (PGENDRV_DRVR_EXTENSION) pdrvr->driver_ext;
    is_if = (BOOLEAN) if_dev(dev_obj);
    if (is_if == FALSE)
        sprintf(dev_name, "\\DosDevices\\gendev%d", (int)pdev_ext->dev_id);
    else
        sprintf(dev_name, "\\DosDevices\\genifdev%d", (int)pdev_ext->dev_id);

    RtlInitString(&string, dev_name);
    RtlAnsiStringToUnicodeString(&symb_link, &string, TRUE);
    IoDeleteSymbolicLink(&symb_link);
    RtlFreeUnicodeString(&symb_link);

    if (pdev_ext->desc_buf)
    {
        usb_dbg_print(DBGLVL_MAXIMUM, ("gendrv_delete_device(): delete desc_buf\n"));
        usb_free_mem(pdev_ext->desc_buf);
        pdev_ext->desc_buf = NULL;

    }

    if (dev_obj->ReferenceCount == 0)
    {
        ExAcquireFastMutex(&pdrvr_ext->drvr_ext_mutex);
        RemoveEntryList(&pdev_ext->dev_obj_link);
        pdev_ext->ext_drvr_entry->ref_count--;  //the ext_drvr_entry is actually in pdrvr_ext, so lock it.
        ExReleaseFastMutex(&pdrvr_ext->drvr_ext_mutex);

        IoDeleteDevice(dev_obj);
        return TRUE;
    }

    // borrow from umss's work routine
    return umss_schedule_workitem(dev_obj, gendrv_deferred_delete_device, NULL, 0);
}

// must have the drvr_ext_mutex acquired.
PGENDRV_EXT_DRVR_ENTRY
gendrv_alloc_ext_drvr_entry(PGENDRV_DRVR_EXTENSION pdrvr_ext)
{
    LONG i;
    if (pdrvr_ext == NULL)
        return NULL;
    if (pdrvr_ext->ext_drvr_count == GENDRV_MAX_EXT_DRVR)
        return NULL;
    for(i = 0; i < GENDRV_MAX_EXT_DRVR; i++)
    {
        if (pdrvr_ext->ext_drvr_array[i].drvr_link.Flink == NULL &&
            pdrvr_ext->ext_drvr_array[i].drvr_link.Blink == NULL)
        {
            return &pdrvr_ext->ext_drvr_array[i];
        }
    }
    return NULL;
}

// must have the drvr_ext_mutex acquired.
VOID
gendrv_release_ext_drvr_entry(PGENDRV_DRVR_EXTENSION pdrvr_ext, PGENDRV_EXT_DRVR_ENTRY pentry)
{
    if (pdrvr_ext == NULL || pentry == NULL)
        return;
    RtlZeroMemory(pentry, sizeof(GENDRV_EXT_DRVR_ENTRY));
    InitializeListHead(&pentry->dev_list);
    return;
}

NTSTATUS
gendrv_open_reg_key(OUT PHANDLE handle,
                    IN HANDLE base_handle OPTIONAL,
                    IN PUNICODE_STRING keyname, IN ACCESS_MASK desired_access, IN BOOLEAN create)
/*++

Routine Description:

    Opens or creates a VOLATILE registry key using the name passed in based
    at the BaseHandle node.

Arguments:

    Handle - Pointer to the handle which will contain the registry key that
        was opened.

    BaseHandle - Handle to the base path from which the key must be opened.

    KeyName - Name of the Key that must be opened/created.

    DesiredAccess - Specifies the desired access that the caller needs to
        the key.

    Create - Determines if the key is to be created if it does not exist.

Return Value:

   The function value is the final status of the operation.

--*/
{
    OBJECT_ATTRIBUTES object_attr;
    ULONG disposition;

    //
    // Initialize the object for the key.
    //

    InitializeObjectAttributes(&object_attr,
                               keyname, OBJ_CASE_INSENSITIVE, base_handle, (PSECURITY_DESCRIPTOR) NULL);

    //
    // Create the key or open it, as appropriate based on the caller's
    // wishes.
    //

    if (create)
    {
        return ZwCreateKey(handle,
                           desired_access,
                           &object_attr, 0, (PUNICODE_STRING) NULL, REG_OPTION_VOLATILE, &disposition);
    }
    else
    {
        return ZwOpenKey(handle, desired_access, &object_attr);
    }
    return STATUS_INVALID_PARAMETER;
}

NTSTATUS
gendrv_get_key_value(IN HANDLE KeyHandle, IN PWSTR ValueName, OUT PKEY_VALUE_FULL_INFORMATION * Information)
/*++

Routine Description:

    This routine is invoked to retrieve the data for a registry key's value.
    This is done by querying the value of the key with a zero-length buffer
    to determine the size of the value, and then allocating a buffer and
    actually querying the value into the buffer.

    It is the responsibility of the caller to free the buffer.

Arguments:

    KeyHandle - Supplies the key handle whose value is to be queried

    ValueName - Supplies the null-terminated Unicode name of the value.

    Information - Returns a pointer to the allocated data buffer.

Return Value:

    The function value is the final status of the query operation.

--*/
{
    UNICODE_STRING unicodeString;
    NTSTATUS status;
    PKEY_VALUE_FULL_INFORMATION infoBuffer;
    ULONG keyValueLength;

    PAGED_CODE();

    RtlInitUnicodeString(&unicodeString, ValueName);

    //
    // Figure out how big the data value is so that a buffer of the
    // appropriate size can be allocated.
    //

    status = ZwQueryValueKey(KeyHandle,
                             &unicodeString, KeyValueFullInformation, (PVOID) NULL, 0, &keyValueLength);
    if (status != STATUS_BUFFER_OVERFLOW && status != STATUS_BUFFER_TOO_SMALL)
    {
        return status;
    }

    //
    // Allocate a buffer large enough to contain the entire key data value.
    //

    infoBuffer = usb_alloc_mem(NonPagedPool, keyValueLength);
    if (!infoBuffer)
    {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Query the data for the key value.
    //

    status = ZwQueryValueKey(KeyHandle,
                             &unicodeString,
                             KeyValueFullInformation, infoBuffer, keyValueLength, &keyValueLength);
    if (!NT_SUCCESS(status))
    {
        usb_free_mem(infoBuffer);
        return status;
    }

    //
    // Everything worked, so simply return the address of the allocated
    // buffer to the caller, who is now responsible for freeing it.
    //

    *Information = infoBuffer;
    return STATUS_SUCCESS;
}

VOID
gendrv_startio(IN PDEVICE_OBJECT dev_obj, IN PIRP irp)
{
    PIO_STACK_LOCATION irp_stack;
    ULONG ctrl_code;
    PUSB_DEV_MANAGER dev_mgr;
    USE_NON_PENDING_IRQL;

    if (dev_obj == NULL || irp == NULL)
        return;

    // standard process from walter oney
    IoAcquireCancelSpinLock(&old_irql);
    if (irp != dev_obj->CurrentIrp || irp->Cancel)
    {
        // already move on to other irp
        IoReleaseCancelSpinLock(old_irql);
        return;
    }
    else
    {
        (void)IoSetCancelRoutine(irp, NULL);
    }
    IoReleaseCancelSpinLock(old_irql);

    irp->IoStatus.Information = 0;

    irp_stack = IoGetCurrentIrpStackLocation(irp);
    ctrl_code = irp_stack->Parameters.DeviceIoControl.IoControlCode;
    dev_mgr = ((PDEVEXT_HEADER) dev_obj->DeviceExtension)->dev_mgr;

    if (irp_stack->MajorFunction != IRP_MJ_DEVICE_CONTROL &&
        irp_stack->MajorFunction != IRP_MJ_INTERNAL_DEVICE_CONTROL)
    {
        GENDRV_COMPLETE_START_IO(dev_obj, STATUS_INVALID_DEVICE_REQUEST, irp);
    }

    switch (ctrl_code)
    {
        case IOCTL_SUBMIT_URB_RD:
        case IOCTL_SUBMIT_URB_NOIO:
        case IOCTL_SUBMIT_URB_WR:
        {
            PURB purb;
            ULONG endp_idx, if_idx, user_buffer_length = 0;
            PUCHAR user_buffer = NULL;
            PUSB_DEV pdev;
            DEV_HANDLE endp_handle;
            PUSB_ENDPOINT pendp;

            NTSTATUS status;

            if (irp_stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(URB))
            {
                GENDRV_COMPLETE_START_IO(dev_obj, STATUS_INVALID_PARAMETER, irp);
            }

            purb = (PURB) irp->AssociatedIrp.SystemBuffer;
            if (ctrl_code == IOCTL_SUBMIT_URB_RD || ctrl_code == IOCTL_SUBMIT_URB_WR)
            {
                if (irp_stack->MajorFunction == IRP_MJ_DEVICE_CONTROL)
                {
                    user_buffer = MmGetSystemAddressForMdl(irp->MdlAddress);
                    user_buffer_length = irp_stack->Parameters.DeviceIoControl.OutputBufferLength;
                    if (user_buffer_length == 0)
                        GENDRV_COMPLETE_START_IO(dev_obj, STATUS_INVALID_PARAMETER, irp);
                }
                else
                {
                    if (purb->data_buffer == NULL || purb->data_length == 0)
                        GENDRV_COMPLETE_START_IO(dev_obj, STATUS_INVALID_PARAMETER, irp);
                    user_buffer = purb->data_buffer;
                    user_buffer_length = purb->data_length;
                }
            }

            purb->reference = 0;
            endp_handle = purb->endp_handle;

            if (usb_query_and_lock_dev(dev_mgr, endp_handle & ~0xffff, &pdev) != STATUS_SUCCESS)
            {
                GENDRV_COMPLETE_START_IO(dev_obj, STATUS_IO_DEVICE_ERROR, irp);
            }

            lock_dev(pdev, FALSE);
            if (dev_state(pdev) == USB_DEV_STATE_ZOMB || (dev_state(pdev) < USB_DEV_STATE_ADDRESSED))

            {
                status = STATUS_INVALID_DEVICE_STATE;
                goto ERROR_OUT1;
            }

            if (dev_state(pdev) == USB_DEV_STATE_ADDRESSED && !default_endp_handle(endp_handle))
            {
                status = STATUS_DEVICE_NOT_READY;
                goto ERROR_OUT1;
            }

            if_idx = if_idx_from_handle(endp_handle);
            endp_idx = endp_idx_from_handle(endp_handle);

            //if_idx exceeds the upper limit
            if (pdev->usb_config)
            {
                if (if_idx >= pdev->usb_config->if_count
                    || endp_idx >= pdev->usb_config->interf[if_idx].endp_count)
                {
                    if (!default_endp_handle(endp_handle))
                    {
                        status = STATUS_INVALID_DEVICE_STATE;
                        goto ERROR_OUT1;
                    }
                }
            }

            endp_from_handle(pdev, endp_handle, pendp);

            // FIXME: don't know what evil will let loose
            if (endp_type(pendp) != USB_ENDPOINT_XFER_CONTROL)
            {
                if (user_buffer_length > 16)
                {
                    status = STATUS_INVALID_PARAMETER;
                    goto ERROR_OUT1;
                }
            }

            purb->pirp = irp;
            purb->context = dev_mgr;
            purb->reference = ctrl_code;

            if (ctrl_code == IOCTL_SUBMIT_URB_RD || ctrl_code == IOCTL_SUBMIT_URB_WR)
            {
                purb->data_buffer = user_buffer;
                purb->data_length = user_buffer_length;
                purb->completion = disp_urb_completion;
            }
            else
            {
                purb->completion = disp_noio_urb_completion;
            }

            unlock_dev(pdev, FALSE);

            //
            // we have to register irp before the urb is scheduled to
            // avoid race condition.
            //
            ASSERT(dev_mgr_register_irp(dev_mgr, irp, purb));
            //
            // the irp can not be canceled at this point, since it is
            // now the current irp and not in any urb queue. dev_mgr_cancel_irp
            // can not find it and simply return.
            //
            //      FIXME: there is a time window that the irp is registered and
            // the urb is not queued. In the meantime, the cancel
            // request may come and cause the irp removed from the irp
            // queue while fail to cancel due to urb not in any urb queue .
            // Thus from that point on, the irp can not be canceled till it
            // is completed or hanging there forever.
            //
            status = usb_submit_urb(dev_mgr, purb);
            if (status != STATUS_PENDING)
            {
                // unmark the pending bit
                IoGetCurrentIrpStackLocation((irp))->Control &= ~SL_PENDING_RETURNED;
                dev_mgr_remove_irp(dev_mgr, irp);
            }
            usb_unlock_dev(pdev);
            if (status != STATUS_PENDING)
            {
                irp->IoStatus.Status = status;
                GENDRV_COMPLETE_START_IO(dev_obj, status, irp);
            }
            return;

          ERROR_OUT1:
            unlock_dev(pdev, FALSE);
            usb_unlock_dev(pdev);
            irp->IoStatus.Information = 0;
            GENDRV_COMPLETE_START_IO(dev_obj, status, irp);
        }
    }
    GENDRV_COMPLETE_START_IO(dev_obj, STATUS_INVALID_DEVICE_REQUEST, irp);
}

VOID
gendrv_clean_up_queued_irps(PDEVICE_OBJECT dev_obj)
{
    // called when device may not function or about to be removed, need cleanup
    KIRQL cancelIrql;
    PIRP irp, cur_irp;
    PKDEVICE_QUEUE_ENTRY packet;
    LIST_ENTRY cancel_irps, *pthis;

    //
    // cancel all the irps in the queue
    //
    if (dev_obj == NULL)
        return;

    InitializeListHead(&cancel_irps);

    // remove the irps from device queue
    IoAcquireCancelSpinLock(&cancelIrql);
    cur_irp = dev_obj->CurrentIrp;
    while ((packet = KeRemoveDeviceQueue(&dev_obj->DeviceQueue)))
    {
        irp = struct_ptr(packet, IRP, Tail.Overlay.DeviceQueueEntry);
        InsertTailList(&cancel_irps, &irp->Tail.Overlay.DeviceQueueEntry.DeviceListEntry);
    }
    IoReleaseCancelSpinLock(cancelIrql);

    // cancel the irps in process
    // we did not cancel the current irp, it will be done by hcd when
    // disconnect is detected.
    // remove_irp_from_list( &dev_mgr->irp_list, cur_irp, dev_mgr );

    while (IsListEmpty(&cancel_irps) == FALSE)
    {
        pthis = RemoveHeadList(&cancel_irps);
        irp = struct_ptr(pthis, IRP, Tail.Overlay.DeviceQueueEntry.DeviceListEntry);
        irp->IoStatus.Information = 0;
        irp->IoStatus.Status = STATUS_CANCELLED;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
    }
    return;
}

VOID
NTAPI
gendrv_cancel_queued_irp(PDEVICE_OBJECT dev_obj, PIRP pirp)
{
    // cancel routine for irps queued in the device queue
    PUSB_DEV_MANAGER dev_mgr;
    PDEVEXT_HEADER pdev_ext_hdr;

    pdev_ext_hdr = (PDEVEXT_HEADER) dev_obj->DeviceExtension;
    dev_mgr = pdev_ext_hdr->dev_mgr;

    if (dev_obj->CurrentIrp == pirp)
    {
        // just before start_io set the cancel routine to null
        IoReleaseCancelSpinLock(pirp->CancelIrql);
        // we did not IoStartNextPacket, leave it for dev_mgr_cancel_irp, that
        // is user have to call CancelIo again.
        return;
    }

    KeRemoveEntryDeviceQueue(&dev_obj->DeviceQueue, &pirp->Tail.Overlay.DeviceQueueEntry);
    IoReleaseCancelSpinLock(pirp->CancelIrql);

    pirp->IoStatus.Information = 0;
    pirp->IoStatus.Status = STATUS_CANCELLED;
    IoCompleteRequest(pirp, IO_NO_INCREMENT);
    // the device queue is moved on, no need to call IoStartNextPacket
    return;
}
