/*
 *  Copyright (C) 2005 Giridhar Pemmasani
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 */

#include "usb.h"
#include "pnp.h"
#include "wrapndis.h"
#include "loader.h"

/* Functions callable from the NDIS driver */
wstdcall NTSTATUS pdoDispatchDeviceControl(struct device_object *pdo,
					   struct irp *irp);
wstdcall NTSTATUS pdoDispatchPnp(struct device_object *pdo, struct irp *irp);
wstdcall NTSTATUS pdoDispatchPower(struct device_object *pdo, struct irp *irp);

static NTSTATUS start_pdo(struct device_object *pdo)
{
	int i, ret, count, resources_size;
	struct wrap_device *wd;
	struct pci_dev *pdev;
	struct cm_partial_resource_descriptor *entry;
	struct cm_partial_resource_list *partial_resource_list;

	ENTER1("%p, %p", pdo, pdo->reserved);
	wd = pdo->reserved;
	if (ntoskernel_init_device(wd))
		EXIT1(return STATUS_FAILURE);
	if (wrap_is_usb_bus(wd->dev_bus)) {
#ifdef ENABLE_USB
		if (usb_init_device(wd)) {
			ntoskernel_exit_device(wd);
			EXIT1(return STATUS_FAILURE);
		}
#endif
		EXIT1(return STATUS_SUCCESS);
	}
	if (!wrap_is_pci_bus(wd->dev_bus))
		EXIT1(return STATUS_SUCCESS);
	pdev = wd->pci.pdev;
	ret = pci_enable_device(pdev);
	if (ret) {
		ERROR("couldn't enable PCI device: %x", ret);
		return STATUS_FAILURE;
	}
	ret = pci_request_regions(pdev, DRIVER_NAME);
	if (ret) {
		ERROR("couldn't request PCI regions: %x", ret);
		goto err_enable;
	}
	pci_set_power_state(pdev, PCI_D0);
#ifdef CONFIG_X86_64
	/* 64-bit broadcom driver doesn't work if DMA is allocated
	 * from over 1GB */
	if (wd->vendor == 0x14e4) {
		if (pci_set_dma_mask(pdev, DMA_30BIT_MASK) ||
		    pci_set_consistent_dma_mask(pdev, DMA_30BIT_MASK))
			WARNING("couldn't set DMA mask; this driver "
				"may not work with more than 1GB RAM");
	}
#endif
	/* IRQ resource entry is filled in from pdev, instead of
	 * pci_resource macros */
	for (i = count = 0; pci_resource_start(pdev, i); i++)
		if ((pci_resource_flags(pdev, i) & IORESOURCE_MEM) ||
		    (pci_resource_flags(pdev, i) & IORESOURCE_IO))
			count++;
	/* space for entry for IRQ is already in
	 * cm_partial_resource_list */
	resources_size = sizeof(struct cm_resource_list) +
		sizeof(struct cm_partial_resource_descriptor) * count;
	TRACE2("resources: %d, %d", count, resources_size);
	wd->resource_list = kzalloc(resources_size, GFP_KERNEL);
	if (!wd->resource_list) {
		WARNING("couldn't allocate memory");
		goto err_regions;
	}
	wd->resource_list->count = 1;
	wd->resource_list->list[0].interface_type = PCIBus;
	/* bus_number is not used by WDM drivers */
	wd->resource_list->list[0].bus_number = pdev->bus->number;

	partial_resource_list =
		&wd->resource_list->list->partial_resource_list;
	partial_resource_list->version = 1;
	partial_resource_list->revision = 1;
	partial_resource_list->count = count + 1;

	for (i = count = 0; pci_resource_start(pdev, i); i++) {
		entry = &partial_resource_list->partial_descriptors[count];
		TRACE2("%d", count);
		if (pci_resource_flags(pdev, i) & IORESOURCE_MEM) {
			entry->type = CmResourceTypeMemory;
			entry->flags = CM_RESOURCE_MEMORY_READ_WRITE;
			entry->share = CmResourceShareDeviceExclusive;
		} else if (pci_resource_flags(pdev, i) & IORESOURCE_IO) {
			entry->type = CmResourceTypePort;
			entry->flags = CM_RESOURCE_PORT_IO;
			entry->share = CmResourceShareDeviceExclusive;
#if 0
		} else if (pci_resource_flags(pdev, i) & IORESOURCE_DMA) {
			/* it looks like no driver uses this resource */
			typeof(pci_resource_flags(pdev, 0)) flags;
			entry->type = CmResourceTypeDma;
			flags = pci_resource_flags(pdev, i);
			if (flags & IORESOURCE_DMA_TYPEA)
				entry->flags |= CM_RESOURCE_DMA_TYPE_A;
			else if (flags & IORESOURCE_DMA_TYPEB)
				entry->flags |= CM_RESOURCE_DMA_TYPE_B;
			else if (flags & IORESOURCE_DMA_TYPEF)
				entry->flags |= CM_RESOURCE_DMA_TYPE_F;
			if (flags & IORESOURCE_DMA_8BIT)
				entry->flags |= CM_RESOURCE_DMA_8;
			else if (flags & IORESOURCE_DMA_16BIT)
				entry->flags |= CM_RESOURCE_DMA_16;
			/* what about 32bit DMA? */
			else if (flags & IORESOURCE_DMA_8AND16BIT)
				entry->flags |= CM_RESOURCE_DMA_8_AND_16;
			if (flags & IORESOURCE_DMA_MASTER)
				entry->flags |= CM_RESOURCE_DMA_BUS_MASTER;
			entry->u.dma.channel = pci_resource_start(pdev, i);
			/* what should this be? */
			entry->u.dma.port = 1;
#endif
		} else
			continue;
		/* TODO: Add other resource types? */
		entry->u.generic.start =
			(ULONG_PTR)pci_resource_start(pdev, i);
		entry->u.generic.length = pci_resource_len(pdev, i);
		count++;
	}

	/* put IRQ resource at the end */
	entry = &partial_resource_list->partial_descriptors[count++];
	entry->type = CmResourceTypeInterrupt;
	entry->flags = CM_RESOURCE_INTERRUPT_LEVEL_SENSITIVE;
	/* we assume all devices use shared IRQ */
	entry->share = CmResourceShareShared;
	/* as per documentation, interrupt level should be DIRQL, but
	 * examples from DDK as well some drivers, such as AR5211,
	 * RT8180L use interrupt level as interrupt vector also in
	 * NdisMRegisterInterrupt */
	entry->u.interrupt.level = pdev->irq;
	entry->u.interrupt.vector = pdev->irq;
	entry->u.interrupt.affinity = -1;

	TRACE2("resource list count %d, irq: %d",
	       partial_resource_list->count, pdev->irq);
	pci_set_drvdata(pdev, wd);
	EXIT1(return STATUS_SUCCESS);
err_regions:
	pci_release_regions(pdev);
err_enable:
	pci_disable_device(pdev);
	wd->pci.pdev = NULL;
	wd->pdo = NULL;
	EXIT1(return STATUS_FAILURE);
}

static void remove_pdo(struct device_object *pdo)
{
	struct wrap_device *wd = pdo->reserved;

	ntoskernel_exit_device(wd);
	if (wrap_is_pci_bus(wd->dev_bus)) {
		struct pci_dev *pdev = wd->pci.pdev;
		pci_release_regions(pdev);
		pci_disable_device(pdev);
		wd->pci.pdev = NULL;
		pci_set_drvdata(pdev, NULL);
	} else if (wrap_is_usb_bus(wd->dev_bus)) {
#ifdef ENABLE_USB
		usb_exit_device(wd);
#endif
	}
	if (wd->resource_list)
		kfree(wd->resource_list);
	wd->resource_list = NULL;
	return;
}

static NTSTATUS IoSendIrpTopDev(struct device_object *dev_obj, ULONG major_fn,
				ULONG minor_fn, struct io_stack_location *sl)
{
	NTSTATUS status;
	struct nt_event event;
	struct irp *irp;
	struct io_stack_location *irp_sl;
	struct device_object *top_dev = IoGetAttachedDeviceReference(dev_obj);

	KeInitializeEvent(&event, NotificationEvent, FALSE);
	irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP, top_dev, NULL, 0, NULL,
					   &event, NULL);
	irp->io_status.status = STATUS_NOT_IMPLEMENTED;
	irp->io_status.info = 0;
	irp_sl = IoGetNextIrpStackLocation(irp);
	if (sl)
		memcpy(irp_sl, sl, sizeof(*irp_sl));
	irp_sl->major_fn = major_fn;
	irp_sl->minor_fn = minor_fn;
	status = IoCallDriver(top_dev, irp);
	if (status == STATUS_PENDING) {
		KeWaitForSingleObject(&event, Executive, KernelMode,
				      FALSE, NULL);
		status = irp->io_status.status;
	}
	ObDereferenceObject(top_dev);
	return status;
}

wstdcall NTSTATUS pdoDispatchDeviceControl(struct device_object *pdo,
					   struct  irp *irp)
{
	struct io_stack_location *irp_sl;
	NTSTATUS status;

	DUMP_IRP(irp);
	irp_sl = IoGetCurrentIrpStackLocation(irp);
#ifdef ENABLE_USB
	status = wrap_submit_irp(pdo, irp);
	IOTRACE("status: %08X", status);
	if (status != STATUS_PENDING)
		IoCompleteRequest(irp, IO_NO_INCREMENT);
#else
	status = irp->io_status.status = STATUS_NOT_IMPLEMENTED;
	IoCompleteRequest(irp, IO_NO_INCREMENT);
#endif
	IOEXIT(return status);
}
WIN_FUNC_DECL(pdoDispatchDeviceControl,2)

wstdcall NTSTATUS pdoDispatchPnp(struct device_object *pdo, struct irp *irp)
{
	struct io_stack_location *irp_sl;
	struct wrap_device *wd;
	NTSTATUS status;
#ifdef ENABLE_USB
	struct usbd_bus_interface_usbdi *usb_intf;
#endif

	irp_sl = IoGetCurrentIrpStackLocation(irp);
	TRACE2("%p %d:%d", pdo, irp_sl->major_fn, irp_sl->minor_fn);
	wd = pdo->reserved;
	switch (irp_sl->minor_fn) {
	case IRP_MN_START_DEVICE:
		status = start_pdo(pdo);
		break;
	case IRP_MN_QUERY_STOP_DEVICE:
	case IRP_MN_STOP_DEVICE:
	case IRP_MN_QUERY_REMOVE_DEVICE:
		status = STATUS_SUCCESS;
		break;
	case IRP_MN_REMOVE_DEVICE:
		remove_pdo(pdo);
		status = STATUS_SUCCESS;
		break;
	case IRP_MN_QUERY_INTERFACE:
#ifdef ENABLE_USB
		if (!wrap_is_usb_bus(wd->dev_bus)) {
			status = STATUS_NOT_IMPLEMENTED;
			break;
		}
		TRACE2("type: %x, size: %d, version: %d",
		       irp_sl->params.query_intf.type->data1,
		       irp_sl->params.query_intf.size,
		       irp_sl->params.query_intf.version);
		usb_intf = (struct usbd_bus_interface_usbdi *)
			irp_sl->params.query_intf.intf;
		usb_intf->Context = wd;
		usb_intf->InterfaceReference = USBD_InterfaceReference;
		usb_intf->InterfaceDereference = USBD_InterfaceDereference;
		usb_intf->GetUSBDIVersion = USBD_InterfaceGetUSBDIVersion;
		usb_intf->QueryBusTime = USBD_InterfaceQueryBusTime;
		usb_intf->SubmitIsoOutUrb = USBD_InterfaceSubmitIsoOutUrb;
		usb_intf->QueryBusInformation =
			USBD_InterfaceQueryBusInformation;
		if (irp_sl->params.query_intf.version >=
		    USB_BUSIF_USBDI_VERSION_1)
			usb_intf->IsDeviceHighSpeed =
				USBD_InterfaceIsDeviceHighSpeed;
		if (irp_sl->params.query_intf.version >=
		    USB_BUSIF_USBDI_VERSION_2)
			usb_intf->LogEntry = USBD_InterfaceLogEntry;
		status = STATUS_SUCCESS;
#else
		status = STATUS_NOT_IMPLEMENTED;
#endif
		break;
	default:
		TRACE2("fn %d not implemented", irp_sl->minor_fn);
		status = STATUS_SUCCESS;
		break;
	}
	irp->io_status.status = status;
	TRACE2("status: %08X", status);
	IoCompleteRequest(irp, IO_NO_INCREMENT);
	IOEXIT(return status);
}
WIN_FUNC_DECL(pdoDispatchPnp,2)

wstdcall NTSTATUS pdoDispatchPower(struct device_object *pdo, struct irp *irp)
{
	struct io_stack_location *irp_sl;
	struct wrap_device *wd;
	union power_state power_state;
	struct pci_dev *pdev;
	NTSTATUS status;

	irp_sl = IoGetCurrentIrpStackLocation(irp);
	wd = pdo->reserved;
	TRACE2("pdo: %p, fn: %d:%d, wd: %p",
	       pdo, irp_sl->major_fn, irp_sl->minor_fn, wd);
	switch (irp_sl->minor_fn) {
	case IRP_MN_WAIT_WAKE:
		/* TODO: this is not complete/correct */
		TRACE2("state: %d, completion: %p",
			  irp_sl->params.power.state.system_state,
			  irp_sl->completion_routine);
		IoMarkIrpPending(irp);
		status = STATUS_PENDING;
		break;
	case IRP_MN_SET_POWER:
		power_state = irp_sl->params.power.state;
		if (power_state.device_state == PowerDeviceD0) {
			TRACE2("resuming %p", wd);
			if (wrap_is_pci_bus(wd->dev_bus)) {
				pdev = wd->pci.pdev;
				pci_restore_state(pdev);
				if (wd->pci.wake_state == PowerDeviceD3) {
					pci_enable_wake(wd->pci.pdev,
							PCI_D3hot, 0);
					pci_enable_wake(wd->pci.pdev,
							PCI_D3cold, 0);
				}
				pci_set_power_state(pdev, PCI_D0);
			} else { // usb device
#ifdef ENABLE_USB
				wrap_resume_urbs(wd);
#endif
			}
		} else {
			TRACE2("suspending device %p", wd);
			if (wrap_is_pci_bus(wd->dev_bus)) {
				pdev = wd->pci.pdev;
				pci_save_state(pdev);
				TRACE2("%d", wd->pci.wake_state);
				if (wd->pci.wake_state == PowerDeviceD3) {
					pci_enable_wake(wd->pci.pdev,
							PCI_D3hot, 1);
					pci_enable_wake(wd->pci.pdev,
							PCI_D3cold, 1);
				}
				pci_set_power_state(pdev, PCI_D3hot);
			} else { // usb device
#ifdef ENABLE_USB
				wrap_suspend_urbs(wd);
#endif
			}
		}
		status = STATUS_SUCCESS;
		break;
	case IRP_MN_QUERY_POWER:
		status = STATUS_SUCCESS;
		break;
	default:
		TRACE2("fn %d not implemented", irp_sl->minor_fn);
		status = STATUS_SUCCESS;
		break;
	}
	irp->io_status.status = status;
	IoCompleteRequest(irp, IO_NO_INCREMENT);
	return status;
}
WIN_FUNC_DECL(pdoDispatchPower,2)

static NTSTATUS pnp_set_device_power_state(struct wrap_device *wd,
					   enum device_power_state state)
{
	NTSTATUS status;
	struct device_object *pdo;
	struct io_stack_location irp_sl;

	pdo = wd->pdo;
	IOTRACE("%p, %p", pdo, IoGetAttachedDevice(pdo));
	memset(&irp_sl, 0, sizeof(irp_sl));
	irp_sl.params.power.state.device_state = state;
	irp_sl.params.power.type = DevicePowerState;
	if (state > PowerDeviceD0) {
		status = IoSendIrpTopDev(pdo, IRP_MJ_POWER, IRP_MN_QUERY_POWER,
					 &irp_sl);
		if (status != STATUS_SUCCESS) {
			TRACE1("query of power to %d returns %08X",
			       state, status);
			EXIT1(return status);
		}
	}
	status = IoSendIrpTopDev(pdo, IRP_MJ_POWER, IRP_MN_SET_POWER, &irp_sl);
	if (status != STATUS_SUCCESS)
		WARNING("setting power to %d failed: %08X", state, status);
	EXIT1(return status);
}

NTSTATUS pnp_start_device(struct wrap_device *wd)
{
	struct device_object *fdo;
	struct device_object *pdo;
	struct io_stack_location irp_sl;
	NTSTATUS status;

	pdo = wd->pdo;
	/* TODO: for now we use same resources for both translated
	 * resources and raw resources */
	memset(&irp_sl, 0, sizeof(irp_sl));
	irp_sl.params.start_device.allocated_resources =
		wd->resource_list;
	irp_sl.params.start_device.allocated_resources_translated =
		wd->resource_list;
	status = IoSendIrpTopDev(pdo, IRP_MJ_PNP, IRP_MN_START_DEVICE, &irp_sl);
	fdo = IoGetAttachedDevice(pdo);
	if (status == STATUS_SUCCESS)
		fdo->drv_obj->drv_ext->count++;
	else
		WARNING("Windows driver couldn't initialize the device (%08X)",
			status);
	EXIT1(return status);
}

NTSTATUS pnp_stop_device(struct wrap_device *wd)
{
	struct device_object *pdo;
	NTSTATUS status;

	pdo = wd->pdo;
	status = IoSendIrpTopDev(pdo, IRP_MJ_PNP, IRP_MN_QUERY_STOP_DEVICE,
				 NULL);
	if (status != STATUS_SUCCESS)
		WARNING("status: %08X", status);
	/* for now we ignore query status */
	status = IoSendIrpTopDev(pdo, IRP_MJ_PNP, IRP_MN_STOP_DEVICE, NULL);
	if (status != STATUS_SUCCESS)
		WARNING("status: %08X", status);
	if (status != STATUS_SUCCESS)
		WARNING("status: %08X", status);
	EXIT2(return status);
}

NTSTATUS pnp_remove_device(struct wrap_device *wd)
{
	struct device_object *pdo, *fdo;
	struct driver_object *fdo_drv_obj;
	NTSTATUS status;

	pdo = wd->pdo;
	fdo = IoGetAttachedDevice(pdo);
	fdo_drv_obj = fdo->drv_obj;
	TRACE2("%p, %p, %p", pdo, fdo, fdo_drv_obj);
	status = IoSendIrpTopDev(pdo, IRP_MJ_PNP, IRP_MN_QUERY_REMOVE_DEVICE,
				 NULL);
	if (status != STATUS_SUCCESS)
		WARNING("status: %08X", status);

	status = IoSendIrpTopDev(pdo, IRP_MJ_PNP, IRP_MN_REMOVE_DEVICE, NULL);
	if (status != STATUS_SUCCESS)
		WARNING("status: %08X", status);
	/* TODO: should we use count in drv_ext or driver's Object
	 * header reference count to keep count of devices associated
	 * with a driver? */
	if (status == STATUS_SUCCESS)
		fdo_drv_obj->drv_ext->count--;
	TRACE1("count: %d", fdo_drv_obj->drv_ext->count);
	if (fdo_drv_obj->drv_ext->count < 0)
		WARNING("wrong count: %d", fdo_drv_obj->drv_ext->count);
	if (fdo_drv_obj->drv_ext->count == 0) {
		struct wrap_driver *wrap_driver;
		TRACE1("unloading driver: %p", fdo_drv_obj);
		wrap_driver =
			IoGetDriverObjectExtension(fdo_drv_obj,
					   (void *)WRAP_DRIVER_CLIENT_ID);
		if (fdo_drv_obj->unload)
			LIN2WIN1(fdo_drv_obj->unload, fdo_drv_obj);
		if (wrap_driver) {
			if (down_interruptible(&loader_mutex))
				WARNING("couldn't obtain loader_mutex");
			unload_wrap_driver(wrap_driver);
			up(&loader_mutex);
		} else
			ERROR("couldn't get wrap_driver");
		ObDereferenceObject(fdo_drv_obj);
	}
	IoDeleteDevice(pdo);
	unload_wrap_device(wd);
	EXIT1(return status);
}

WIN_FUNC_DECL(IoInvalidDeviceRequest,2)

static struct device_object *alloc_pdo(struct driver_object *drv_obj)
{
	struct device_object *pdo;
	NTSTATUS status ;
	int i;
	struct ansi_string ansi_name;
	struct unicode_string unicode_name;

	RtlInitAnsiString(&ansi_name, "NDISpdo");
	if (RtlAnsiStringToUnicodeString(&unicode_name, &ansi_name, TRUE) ==
	    STATUS_SUCCESS) {
		status = IoCreateDevice(drv_obj, 0, &unicode_name,
					FILE_DEVICE_UNKNOWN,
					FILE_AUTOGENERATED_DEVICE_NAME,
					FALSE, &pdo);
		RtlFreeUnicodeString(&unicode_name);
	} else {
		status = IoCreateDevice(drv_obj, 0, NULL,
					FILE_DEVICE_UNKNOWN,
					FILE_AUTOGENERATED_DEVICE_NAME,
					FALSE, &pdo);
	}
	TRACE1("%p, %d, %p", drv_obj, status, pdo);
	if (status != STATUS_SUCCESS)
		return NULL;
	/* dispatch routines are called as Windows functions */
	for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
		drv_obj->major_func[i] = WIN_FUNC_PTR(IoInvalidDeviceRequest,2);
	drv_obj->major_func[IRP_MJ_INTERNAL_DEVICE_CONTROL] =
		WIN_FUNC_PTR(pdoDispatchDeviceControl,2);
	drv_obj->major_func[IRP_MJ_DEVICE_CONTROL] =
		WIN_FUNC_PTR(pdoDispatchDeviceControl,2);
	drv_obj->major_func[IRP_MJ_POWER] = WIN_FUNC_PTR(pdoDispatchPower,2);
	drv_obj->major_func[IRP_MJ_PNP] = WIN_FUNC_PTR(pdoDispatchPnp,2);
	return pdo;
}

static int wrap_pnp_start_device(struct wrap_device *wd)
{
	struct wrap_driver *driver;
	struct device_object *pdo;
	struct driver_object *pdo_drv_obj;

	ENTER1("wd: %p", wd);

	if (!((wrap_is_pci_bus(wd->dev_bus)) ||
	      (wrap_is_usb_bus(wd->dev_bus)))) {
		ERROR("bus type %d (%d) not supported",
		      WRAP_BUS(wd->dev_bus), wd->dev_bus);
		EXIT1(return -EINVAL);
	}
	driver = load_wrap_driver(wd);
	if (!driver)
		return -ENODEV;

	wd->driver = driver;
	wd->dev_bus = WRAP_DEVICE_BUS(driver->dev_type, WRAP_BUS(wd->dev_bus));
	TRACE1("dev type: %d, bus type: %d, %d", WRAP_DEVICE(wd->dev_bus),
	       WRAP_BUS(wd->dev_bus), wd->dev_bus);
	TRACE1("%d, %d", driver->dev_type, wrap_is_usb_bus(wd->dev_bus));
	/* first create pdo */
	if (wrap_is_pci_bus(wd->dev_bus))
		pdo_drv_obj = find_bus_driver("PCI");
	else // if (wrap_is_usb_bus(wd->dev_bus))
		pdo_drv_obj = find_bus_driver("USB");
	if (!pdo_drv_obj)
		return -EINVAL;
	pdo = alloc_pdo(pdo_drv_obj);
	if (!pdo)
		return -ENOMEM;
	wd->pdo = pdo;
	pdo->reserved = wd;
	if (WRAP_DEVICE(wd->dev_bus) == WRAP_NDIS_DEVICE) {
		if (init_ndis_driver(driver->drv_obj)) {
			IoDeleteDevice(pdo);
			return -EINVAL;
		}
	}
	TRACE1("%p", driver->drv_obj->drv_ext->add_device);
	if (driver->drv_obj->drv_ext->add_device(driver->drv_obj, pdo) !=
	    STATUS_SUCCESS) {
		IoDeleteDevice(pdo);
		return -ENOMEM;
	}
	if (pnp_start_device(wd) != STATUS_SUCCESS) {
		/* TODO: we need proper cleanup, to deallocate memory,
		 * for example */
		pnp_remove_device(wd);
		return -EINVAL;
	}
	return 0;
}

/*
 * This function should not be marked __devinit because PCI IDs are
 * added dynamically.
 */
int wrap_pnp_start_pci_device(struct pci_dev *pdev,
			      const struct pci_device_id *ent)
{
	struct load_device load_device;
	struct wrap_device *wd;

	ENTER1("called for %04x:%04x:%04x:%04x", pdev->vendor, pdev->device,
	       pdev->subsystem_vendor, pdev->subsystem_device);

	load_device.bus = WRAP_PCI_BUS;
	load_device.vendor = pdev->vendor;
	load_device.device = pdev->device;
	load_device.subvendor = pdev->subsystem_vendor;
	load_device.subdevice = pdev->subsystem_device;
	wd = load_wrap_device(&load_device);
	if (!wd)
		EXIT1(return -ENODEV);
	wd->pci.pdev = pdev;
	return wrap_pnp_start_device(wd);
}

void wrap_pnp_remove_pci_device(struct pci_dev *pdev)
{
	struct wrap_device *wd;

	wd = (struct wrap_device *)pci_get_drvdata(pdev);
	ENTER1("%p, %p", pdev, wd);
	if (!wd)
		EXIT1(return);
	pnp_remove_device(wd);
}

int wrap_pnp_suspend_pci_device(struct pci_dev *pdev, pm_message_t state)
{
	struct wrap_device *wd;

	wd = (struct wrap_device *)pci_get_drvdata(pdev);
	return pnp_set_device_power_state(wd, PowerDeviceD3);
}

int wrap_pnp_resume_pci_device(struct pci_dev *pdev)
{
	struct wrap_device *wd;

	wd = (struct wrap_device *)pci_get_drvdata(pdev);
	return pnp_set_device_power_state(wd, PowerDeviceD0);
}

#ifdef ENABLE_USB
int wrap_pnp_start_usb_device(struct usb_interface *intf,
			      const struct usb_device_id *usb_id)
{
	struct wrap_device *wd;
	int ret;
	struct usb_device *udev = interface_to_usbdev(intf);
	ENTER1("%04x, %04x, %04x", udev->descriptor.idVendor,
	       udev->descriptor.idProduct, udev->descriptor.bDeviceClass);

	/* USB device (e.g., RNDIS) may have multiple interfaces;
	  initialize one interface only (is there a way to know which
	  of these interfaces is for network?) */

	if ((wd = get_wrap_device(udev, WRAP_USB_BUS))) {
		TRACE1("device already initialized: %p", wd);
		usb_set_intfdata(intf, NULL);
		ret = 0;
	} else {
		struct load_device load_device;

		load_device.bus = WRAP_USB_BUS;
		load_device.vendor = le16_to_cpu(udev->descriptor.idVendor);
		load_device.device = le16_to_cpu(udev->descriptor.idProduct);
		load_device.subvendor = 0;
		load_device.subdevice = 0;
		wd = load_wrap_device(&load_device);
		TRACE2("%p", wd);
		if (wd) {
			/* some devices (e.g., TI 4150, RNDIS) need
			 * full reset */
			ret = usb_reset_device(udev);
			if (ret)
				WARNING("reset failed: %d", ret);
			usb_set_intfdata(intf, wd);
			wd->usb.intf = intf;
			wd->usb.udev = udev;
			ret = wrap_pnp_start_device(wd);
		} else
			ret = -ENODEV;
	}

	TRACE2("ret: %d", ret);
	if (ret)
		EXIT1(return ret);
	else
		return 0;
}

void __devexit wrap_pnp_remove_usb_device(struct usb_interface *intf)
{
	struct wrap_device *wd;

	wd = (struct wrap_device *)usb_get_intfdata(intf);
	TRACE1("%p, %p", intf, wd);
	if (wd == NULL)
		EXIT1(return);
	usb_set_intfdata(intf, NULL);
	wd->usb.intf = NULL;
	pnp_remove_device(wd);
}

int wrap_pnp_suspend_usb_device(struct usb_interface *intf, pm_message_t state)
{
	struct wrap_device *wd;
	struct device_object *pdo;

	wd = usb_get_intfdata(intf);
	ENTER1("%p, %p", intf, wd);
	if (!wd)
		EXIT1(return 0);
	pdo = wd->pdo;
	if (pnp_set_device_power_state(wd, PowerDeviceD3))
		return -1;
	return 0;
}

int wrap_pnp_resume_usb_device(struct usb_interface *intf)
{
	struct wrap_device *wd;
	wd = usb_get_intfdata(intf);
	ENTER1("%p, %p", intf, wd);
	if (!wd)
		EXIT1(return 0);
	if (pnp_set_device_power_state(wd, PowerDeviceD0))
		return -1;
	return 0;
}

#endif // USB
