/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         USB hub driver
 * FILE:            drivers/usb/cromwell/hub/fdo.c
 * PURPOSE:         IRP_MJ_PNP operations for FDOs
 *
 * PROGRAMMERS:     Hervé Poussineau (hpoussin@reactos.com)
 */

#define INITGUID
#define NDEBUG
#include "usbhub.h"

#define IO_METHOD_FROM_CTL_CODE(ctlCode) (ctlCode&0x00000003)

static VOID
UsbhubGetUserBuffers(
	IN PIRP Irp,
	IN ULONG IoControlCode,
	OUT PVOID* BufferIn,
	OUT PVOID* BufferOut)
{
	ASSERT(Irp);
	ASSERT(BufferIn);
	ASSERT(BufferOut);

	switch (IO_METHOD_FROM_CTL_CODE(IoControlCode))
	{
		case METHOD_BUFFERED:
			*BufferIn = *BufferOut = Irp->AssociatedIrp.SystemBuffer;
			break;
		case METHOD_IN_DIRECT:
		case METHOD_OUT_DIRECT:
			*BufferIn = Irp->AssociatedIrp.SystemBuffer;
			*BufferOut = MmGetSystemAddressForMdl(Irp->MdlAddress);
			break;
		case METHOD_NEITHER:
			*BufferIn = IoGetCurrentIrpStackLocation(Irp)->Parameters.DeviceIoControl.Type3InputBuffer;
			*BufferOut = Irp->UserBuffer;
			break;
		default:
			/* Should never happen */
			*BufferIn = NULL;
			*BufferOut = NULL;
			break;
	}
}

static NTSTATUS
UsbhubFdoQueryBusRelations(
	IN PDEVICE_OBJECT DeviceObject,
	OUT PDEVICE_RELATIONS* pDeviceRelations)
{
	PHUB_DEVICE_EXTENSION DeviceExtension;
	PDEVICE_RELATIONS DeviceRelations;
	PDEVICE_OBJECT Pdo;
	PHUB_DEVICE_EXTENSION PdoExtension;
	struct usb_device* dev;
	ULONG i;
	ULONG Children = 0;
	ULONG NeededSize;
	NTSTATUS Status;
	CHAR Buffer[3][40];
	
	DeviceExtension = (PHUB_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
	dev = DeviceExtension->dev;
	
	/* Create PDOs that are missing */
	for (i = 0; i < dev->maxchild; i++)
	{
		if (dev->children[i] == NULL)
		{
			/* No child device at this place */
			continue;
		}
		Children++;
		if (DeviceExtension->Children[i] != NULL)
		{
			/* PDO already exists */
			continue;
		}
		/* Need to create the PDO */
		Status = IoCreateDevice(
			DeviceObject->DriverObject,
			sizeof(HUB_DEVICE_EXTENSION),
			NULL, /* Device name */
			FILE_DEVICE_CONTROLLER,
			FILE_AUTOGENERATED_DEVICE_NAME,
			FALSE,
			&DeviceExtension->Children[i]);
		if (!NT_SUCCESS(Status))
		{
			DPRINT("Usbhub: IoCreateDevice() failed with status 0x%08lx\n", Status);
			return Status;
		}
		
		Pdo = DeviceExtension->Children[i];
		Pdo->Flags |= DO_BUS_ENUMERATED_DEVICE;
		
		PdoExtension = Pdo->DeviceExtension;
		RtlZeroMemory(PdoExtension, sizeof(HUB_DEVICE_EXTENSION));
		
		PdoExtension->IsFDO = FALSE;
		PdoExtension->dev = dev->children[i];
		
		sprintf(Buffer[0], "%lu", i);
		Status = UsbhubInitMultiSzString(
			&PdoExtension->InstanceId,
			Buffer[0], NULL);
		if (!NT_SUCCESS(Status))
			goto ByeBye;
		
		DPRINT1("child #%lu: USB\\Vid_%04x&Pid_%04x&Rev_%04x\n",
			i,
			PdoExtension->dev->descriptor.idVendor,
			PdoExtension->dev->descriptor.idProduct,
			PdoExtension->dev->descriptor.bcdDevice);
		sprintf(Buffer[0], "USB\\Vid_%04x&Pid_%04x&Rev_%04x",
			PdoExtension->dev->descriptor.idVendor,
			PdoExtension->dev->descriptor.idProduct,
			PdoExtension->dev->descriptor.bcdDevice);
		sprintf(Buffer[1], "USB\\Vid_%04x&Pid_%04x",
			PdoExtension->dev->descriptor.idVendor,
			PdoExtension->dev->descriptor.idProduct);
		Status = UsbhubInitMultiSzString(
			&PdoExtension->HardwareIds,
			Buffer[0], Buffer[1], NULL);
		if (!NT_SUCCESS(Status))
			goto ByeBye;
		
		Status = UsbhubInitMultiSzString(
			&PdoExtension->DeviceId,
			Buffer[1], NULL);
		if (!NT_SUCCESS(Status))
			goto ByeBye;
		
		if (PdoExtension->dev->actconfig->desc.bNumInterfaces == 1)
		{
			/* Single-interface USB device */
			if (PdoExtension->dev->descriptor.bDeviceClass != 0)
			{
				/* Use these values for device class/sub class/protocol */
				sprintf(Buffer[0], "USB\\Class_%02x&SubClass_%02x&Prot_%02x",
					PdoExtension->dev->descriptor.bDeviceClass,
					PdoExtension->dev->descriptor.bDeviceSubClass,
					PdoExtension->dev->descriptor.bDeviceProtocol);
				sprintf(Buffer[1], "USB\\Class_%02x&SubClass_%02x",
					PdoExtension->dev->descriptor.bDeviceClass,
					PdoExtension->dev->descriptor.bDeviceSubClass);
				sprintf(Buffer[2], "USB\\Class_%02x",
					PdoExtension->dev->descriptor.bDeviceClass);
			}
			else
			{
				/* Use values specified in the interface descriptor */
				struct usb_host_interface *itf = PdoExtension->dev->actconfig->interface->altsetting;
				sprintf(Buffer[0], "USB\\Class_%02x&SubClass_%02x&Prot_%02x",
					itf->desc.bInterfaceClass,
					itf->desc.bInterfaceSubClass,
					itf->desc.bInterfaceProtocol);
				sprintf(Buffer[1], "USB\\Class_%02x&SubClass_%02x",
					itf->desc.bInterfaceClass,
					itf->desc.bInterfaceSubClass);
				sprintf(Buffer[2], "USB\\Class_%02x",
					itf->desc.bInterfaceClass);
			}
			Status = UsbhubInitMultiSzString(
				&PdoExtension->CompatibleIds,
				Buffer[0], Buffer[1], Buffer[2], NULL);
		}
		else
		{
			/* Multiple-interface USB device */
			sprintf(Buffer[0], "USB\\DevClass_%02x&SubClass_%02x&Prot_%02x",
				PdoExtension->dev->descriptor.bDeviceClass,
				PdoExtension->dev->descriptor.bDeviceSubClass,
				PdoExtension->dev->descriptor.bDeviceProtocol);
			sprintf(Buffer[1], "USB\\DevClass_%02x&SubClass_%02x",
				PdoExtension->dev->descriptor.bDeviceClass,
				PdoExtension->dev->descriptor.bDeviceSubClass);
			sprintf(Buffer[2], "USB\\DevClass_%02x",
				PdoExtension->dev->descriptor.bDeviceClass);
			Status = UsbhubInitMultiSzString(
				&PdoExtension->CompatibleIds,
				Buffer[0], Buffer[1], Buffer[2], "USB\\COMPOSITE", NULL);
		}
		
		if (!NT_SUCCESS(Status))
			goto ByeBye;
		
		Pdo->Flags &= ~DO_DEVICE_INITIALIZING;
	}
	
	/* Fill returned structure */
	NeededSize = sizeof(DEVICE_RELATIONS);
	if (Children > 1)
		NeededSize += (Children - 1) * sizeof(PDEVICE_OBJECT);
	DeviceRelations = (PDEVICE_RELATIONS)ExAllocatePool(
		PagedPool,
		NeededSize);
	if (!DeviceRelations)
		return STATUS_INSUFFICIENT_RESOURCES;
	DeviceRelations->Count = Children;
	Children = 0;
	for (i = 0; i < USB_MAXCHILDREN; i++)
	{
		if (DeviceExtension->Children[i])
		{
			ObReferenceObject(DeviceExtension->Children[i]);
			DeviceRelations->Objects[Children++] = DeviceExtension->Children[i];
		}
	}
	ASSERT(Children == DeviceRelations->Count);
	
	*pDeviceRelations = DeviceRelations;
	return STATUS_SUCCESS;

ByeBye:
	RtlFreeUnicodeString(&PdoExtension->DeviceId);
	RtlFreeUnicodeString(&PdoExtension->InstanceId);
	RtlFreeUnicodeString(&PdoExtension->HardwareIds);
	RtlFreeUnicodeString(&PdoExtension->CompatibleIds);
	IoDeleteDevice(Pdo);
	return Status;
}

NTSTATUS STDCALL
UsbhubPnpFdo(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp)
{
	PIO_STACK_LOCATION IrpSp;
	NTSTATUS Status;
	ULONG MinorFunction;
	ULONG_PTR Information = 0;
	
	IrpSp = IoGetCurrentIrpStackLocation(Irp);
	MinorFunction = IrpSp->MinorFunction;

	switch (MinorFunction)
	{
		case IRP_MN_START_DEVICE: /* 0x0 */
		{
			DPRINT("Usbhub: IRP_MJ_PNP / IRP_MN_START_DEVICE\n");
			Status = ForwardIrpAndWait(DeviceObject, Irp);
			break;
		}

		case IRP_MN_QUERY_DEVICE_RELATIONS: /* (optional) 0x7 */
		{
			switch (IrpSp->Parameters.QueryDeviceRelations.Type)
			{
				case BusRelations:
				{
					PDEVICE_RELATIONS DeviceRelations = NULL;
					DPRINT("Usbhub: IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / BusRelations\n");
					Status = UsbhubFdoQueryBusRelations(DeviceObject, &DeviceRelations);
					Information = (ULONG_PTR)DeviceRelations;
					break;
				}
				case RemovalRelations:
				{
					DPRINT1("Usbhub: IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / RemovalRelations\n");
					return ForwardIrpAndForget(DeviceObject, Irp);
				}
				default:
					DPRINT1("Usbhub: IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / Unknown type 0x%lx\n",
						IrpSp->Parameters.QueryDeviceRelations.Type);
					return ForwardIrpAndForget(DeviceObject, Irp);
			}
			break;
		}

		default:
		{
			DPRINT1("Usbhub: IRP_MJ_PNP / unknown minor function 0x%lx\n", MinorFunction);
			return ForwardIrpAndForget(DeviceObject, Irp);
		}
	}
	Irp->IoStatus.Information = Information;
	Irp->IoStatus.Status = Status;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return Status;
}

NTSTATUS
UsbhubDeviceControlFdo(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp)
{
	PIO_STACK_LOCATION Stack;
	ULONG IoControlCode;
	PHUB_DEVICE_EXTENSION DeviceExtension;
	ULONG LengthIn, LengthOut;
	ULONG_PTR Information = 0;
	PVOID BufferIn, BufferOut;
	NTSTATUS Status;

	Stack = IoGetCurrentIrpStackLocation(Irp);
	LengthIn = Stack->Parameters.DeviceIoControl.InputBufferLength;
	LengthOut = Stack->Parameters.DeviceIoControl.OutputBufferLength;
	DeviceExtension = (PHUB_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
	IoControlCode = Stack->Parameters.DeviceIoControl.IoControlCode;
	UsbhubGetUserBuffers(Irp, IoControlCode, &BufferIn, &BufferOut);

	switch (IoControlCode)
	{
		case IOCTL_USB_GET_NODE_INFORMATION:
		{
			PUSB_NODE_INFORMATION NodeInformation;
			struct usb_device* dev;
			DPRINT("Usbhub: IOCTL_USB_GET_NODE_INFORMATION\n");
			if (LengthOut < sizeof(USB_NODE_INFORMATION))
				Status = STATUS_BUFFER_TOO_SMALL;
			else if (BufferOut == NULL)
				Status = STATUS_INVALID_PARAMETER;
			else
			{
				NodeInformation = (PUSB_NODE_INFORMATION)BufferOut;
				dev = ((PHUB_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->dev;
				NodeInformation->NodeType = UsbHub;
				RtlCopyMemory(
					&NodeInformation->u.HubInformation.HubDescriptor,
					((struct usb_hub *)usb_get_intfdata(to_usb_interface(&dev->actconfig->interface[0].dev)))->descriptor,
					sizeof(USB_HUB_DESCRIPTOR));
				NodeInformation->u.HubInformation.HubIsBusPowered = dev->actconfig->desc.bmAttributes & 0x80;
				Information = sizeof(USB_NODE_INFORMATION);
				Status = STATUS_SUCCESS;
			}
			break;
		}
		case IOCTL_USB_GET_NODE_CONNECTION_NAME:
		{
			PHUB_DEVICE_EXTENSION DeviceExtension;
			PUSB_NODE_CONNECTION_NAME ConnectionName;
			DeviceExtension = (PHUB_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
			ConnectionName = (PUSB_NODE_CONNECTION_NAME)BufferOut;
			
			DPRINT("Usbhub: IOCTL_USB_GET_NODE_CONNECTION_NAME\n");
			if (LengthOut < sizeof(USB_NODE_CONNECTION_NAME))
				Status = STATUS_BUFFER_TOO_SMALL;
			else if (BufferOut == NULL)
				Status = STATUS_INVALID_PARAMETER;
			else if (ConnectionName->ConnectionIndex < 1
				|| ConnectionName->ConnectionIndex > USB_MAXCHILDREN)
				Status = STATUS_INVALID_PARAMETER;
			else if (DeviceExtension->Children[ConnectionName->ConnectionIndex - 1] == NULL)
				Status = STATUS_INVALID_PARAMETER;
			else
			{
				ULONG NeededStructureSize;
				DeviceExtension = (PHUB_DEVICE_EXTENSION)DeviceExtension->Children[ConnectionName->ConnectionIndex - 1]->DeviceExtension;
				NeededStructureSize = DeviceExtension->SymbolicLinkName.Length + sizeof(UNICODE_NULL) + FIELD_OFFSET(USB_NODE_CONNECTION_NAME, NodeName);
				if (ConnectionName->ActualLength < NeededStructureSize / sizeof(WCHAR)
					|| LengthOut < NeededStructureSize)
				{
					/* Buffer too small */
					ConnectionName->ActualLength = NeededStructureSize / sizeof(WCHAR);
					Information = sizeof(USB_NODE_CONNECTION_NAME);
					Status = STATUS_BUFFER_TOO_SMALL;
				}
				else
				{
					RtlCopyMemory(
						ConnectionName->NodeName,
						DeviceExtension->SymbolicLinkName.Buffer,
						DeviceExtension->SymbolicLinkName.Length);
					ConnectionName->NodeName[DeviceExtension->SymbolicLinkName.Length / sizeof(WCHAR)] = UNICODE_NULL;
					DPRINT("Usbhub: IOCTL_USB_GET_NODE_CONNECTION_NAME returns '%S'\n", ConnectionName->NodeName);
					ConnectionName->ActualLength = NeededStructureSize / sizeof(WCHAR);
					Information = NeededStructureSize;
					Status = STATUS_SUCCESS;
				}
				Information = LengthOut;
			}
			break;
		}
		case IOCTL_USB_GET_NODE_CONNECTION_INFORMATION:
		{
			PUSB_NODE_CONNECTION_INFORMATION ConnectionInformation;
			ULONG i, j, k;
			struct usb_device* dev;
			ULONG NumberOfOpenPipes = 0;
			ULONG SizeOfOpenPipesArray;
			ConnectionInformation = (PUSB_NODE_CONNECTION_INFORMATION)BufferOut;
			
			DPRINT("Usbhub: IOCTL_USB_GET_NODE_CONNECTION_INFORMATION\n");
			if (LengthOut < sizeof(USB_NODE_CONNECTION_INFORMATION))
				Status = STATUS_BUFFER_TOO_SMALL;
			else if (BufferOut == NULL)
				Status = STATUS_INVALID_PARAMETER;
			else if (ConnectionInformation->ConnectionIndex < 1
				|| ConnectionInformation->ConnectionIndex > USB_MAXCHILDREN)
				Status = STATUS_INVALID_PARAMETER;
			else
			{
				dev = ((PHUB_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->dev;
				dev = dev->children[ConnectionInformation->ConnectionIndex - 1];
				if (dev == NULL)
				{
					/* No device connected to this port */
					RtlZeroMemory(
						&ConnectionInformation->DeviceDescriptor,
						sizeof(USB_NODE_CONNECTION_INFORMATION) - FIELD_OFFSET(USB_NODE_CONNECTION_INFORMATION, DeviceDescriptor));
					ConnectionInformation->ConnectionStatus = NoDeviceConnected;
					Information = sizeof(USB_NODE_CONNECTION_INFORMATION);
					Status = STATUS_SUCCESS;
					break;
				}
				SizeOfOpenPipesArray = (LengthOut - FIELD_OFFSET(USB_NODE_CONNECTION_INFORMATION, PipeList)) / sizeof(USB_PIPE_INFO);
				RtlCopyMemory(
					&ConnectionInformation->DeviceDescriptor,
					&dev->descriptor,
					sizeof(USB_DEVICE_DESCRIPTOR));
				ConnectionInformation->CurrentConfigurationValue = dev->actconfig->desc.bConfigurationValue;
				ConnectionInformation->LowSpeed = dev->speed == USB_SPEED_LOW || dev->speed == USB_SPEED_FULL;
				ConnectionInformation->DeviceIsHub = dev->descriptor.bDeviceClass == USB_CLASS_HUB;
				ConnectionInformation->DeviceAddress = dev->devnum;
				ConnectionInformation->ConnectionStatus = DeviceConnected;
				
				for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++)
					for (j = 0; j < dev->actconfig->interface[i].num_altsetting; j++)
						for (k = 0; k < dev->actconfig->interface[i].altsetting[j].desc.bNumEndpoints; k++)
						{
							if (NumberOfOpenPipes < SizeOfOpenPipesArray)
							{
								PUSB_PIPE_INFO Pipe = &ConnectionInformation->PipeList[NumberOfOpenPipes];
								struct usb_host_endpoint* endpoint = &dev->actconfig->interface[i].altsetting[j].endpoint[k];
								RtlCopyMemory(
									&Pipe->EndpointDescriptor,
									&endpoint->desc,
									endpoint->desc.bLength);
								Pipe->ScheduleOffset = 0; /* FIXME */
							}
							NumberOfOpenPipes++;
						}
				ConnectionInformation->NumberOfOpenPipes = NumberOfOpenPipes;
				
				Information = sizeof(USB_NODE_CONNECTION_INFORMATION);
				if (NumberOfOpenPipes <= SizeOfOpenPipesArray)
					Status = STATUS_SUCCESS;
				else
					Status = STATUS_BUFFER_OVERFLOW;
			}
			break;
		}
		case IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION:
		{
			//PUSB_DESCRIPTOR_REQUEST Descriptor;
			DPRINT("Usbhub: IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION\n");
			DPRINT1("Usbhub: IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION unimplemented\n");
			Information = 0;
			Status = STATUS_NOT_IMPLEMENTED;
			break;
		}
		case IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME:
		{
			PHUB_DEVICE_EXTENSION DeviceExtension;
			PUSB_NODE_CONNECTION_DRIVERKEY_NAME StringDescriptor;
			DPRINT("Usbhub: IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME\n");
			DeviceExtension = (PHUB_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
			StringDescriptor = (PUSB_NODE_CONNECTION_DRIVERKEY_NAME)BufferOut;
			if (LengthOut < sizeof(USB_NODE_CONNECTION_DRIVERKEY_NAME))
				Status = STATUS_BUFFER_TOO_SMALL;
			else if (StringDescriptor == NULL)
				Status = STATUS_INVALID_PARAMETER;
			else if (StringDescriptor->ConnectionIndex < 1
				|| StringDescriptor->ConnectionIndex > USB_MAXCHILDREN)
				Status = STATUS_INVALID_PARAMETER;
			else if (DeviceExtension->Children[StringDescriptor->ConnectionIndex - 1] == NULL)
				Status = STATUS_INVALID_PARAMETER;
			else
			{
				ULONG StringSize;
				Status = IoGetDeviceProperty(
					DeviceExtension->Children[StringDescriptor->ConnectionIndex - 1],
					DevicePropertyDriverKeyName,
					LengthOut - FIELD_OFFSET(USB_NODE_CONNECTION_DRIVERKEY_NAME, DriverKeyName),
					StringDescriptor->DriverKeyName,
					&StringSize);
				if (NT_SUCCESS(Status) || Status == STATUS_BUFFER_TOO_SMALL)
				{
					StringDescriptor->ActualLength = StringSize + FIELD_OFFSET(USB_NODE_CONNECTION_DRIVERKEY_NAME, DriverKeyName);
					Information = LengthOut;
					Status = STATUS_SUCCESS;
				}
			}
			break;
		}
		default:
		{
			/* Pass Irp to lower driver */
			DPRINT1("Usbhub: Unknown IOCTL code 0x%lx\n", Stack->Parameters.DeviceIoControl.IoControlCode);
			return ForwardIrpAndForget(DeviceObject, Irp);
		}
	}

	Irp->IoStatus.Information = Information;
	Irp->IoStatus.Status = Status;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return Status;
}
