/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         PCI IDE bus driver extension
 * FILE:            drivers/storage/pciidex/pdo.c
 * PURPOSE:         IRP_MJ_PNP operations for PDOs
 * PROGRAMMERS:     Hervé Poussineau (hpoussin@reactos.org)
 */

#include "pciidex.h"

#define NDEBUG
#include <debug.h>

static NTSTATUS
PciIdeXPdoQueryId(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp,
	OUT ULONG_PTR* Information)
{
	PPDO_DEVICE_EXTENSION DeviceExtension;
	PFDO_DEVICE_EXTENSION FdoDeviceExtension;
	WCHAR Buffer[256];
	ULONG Index = 0;
	ULONG IdType;
	UNICODE_STRING SourceString;
	UNICODE_STRING String;
	NTSTATUS Status;

	IdType = IoGetCurrentIrpStackLocation(Irp)->Parameters.QueryId.IdType;
	DeviceExtension = (PPDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
	FdoDeviceExtension = (PFDO_DEVICE_EXTENSION)DeviceExtension->ControllerFdo->DeviceExtension;

	switch (IdType)
	{
		case BusQueryDeviceID:
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_ID / BusQueryDeviceID\n");
			RtlInitUnicodeString(&SourceString, L"PCIIDE\\IDEChannel");
			break;
		}
		case BusQueryHardwareIDs:
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_ID / BusQueryHardwareIDs\n");

			switch (FdoDeviceExtension->VendorId)
			{
				case 0x0e11:
					Index += swprintf(&Buffer[Index], L"Compaq-%04x", FdoDeviceExtension->DeviceId) + 1;
					break;
				case 0x1039:
					Index += swprintf(&Buffer[Index], L"SiS-%04x", FdoDeviceExtension->DeviceId) + 1;
					break;
				case 0x1050:
					Index += swprintf(&Buffer[Index], L"WinBond-%04x", FdoDeviceExtension->DeviceId) + 1;
					break;
				case 0x1095:
					Index += swprintf(&Buffer[Index], L"CMD-%04x", FdoDeviceExtension->DeviceId) + 1;
					break;
				case 0x8086:
				{
					switch (FdoDeviceExtension->DeviceId)
					{
						case 0x1230:
							Index += swprintf(&Buffer[Index], L"Intel-PIIX") + 1;
							break;
						case 0x7010:
							Index += swprintf(&Buffer[Index], L"Intel-PIIX3") + 1;
							break;
						case 0x7111:
							Index += swprintf(&Buffer[Index], L"Intel-PIIX4") + 1;
							break;
						default:
							Index += swprintf(&Buffer[Index], L"Intel-%04x", FdoDeviceExtension->DeviceId) + 1;
							break;
					}
					break;
				}
				default:
					break;
			}
			if (DeviceExtension->Channel == 0)
				Index += swprintf(&Buffer[Index], L"Primary_IDE_Channel") + 1;
			else
				Index += swprintf(&Buffer[Index], L"Secondary_IDE_Channel") + 1;
			Index += swprintf(&Buffer[Index], L"*PNP0600") + 1;
			Buffer[Index] = UNICODE_NULL;
			SourceString.Length = SourceString.MaximumLength = Index * sizeof(WCHAR);
			SourceString.Buffer = Buffer;
			break;
		}
		case BusQueryCompatibleIDs:
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_ID / BusQueryCompatibleIDs\n");

			Index += swprintf(&Buffer[Index], L"*PNP0600") + 1;
			Buffer[Index] = UNICODE_NULL;
			SourceString.Length = SourceString.MaximumLength = Index * sizeof(WCHAR);
			SourceString.Buffer = Buffer;
			break;
		}
		case BusQueryInstanceID:
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_ID / BusQueryInstanceID\n");
			swprintf(Buffer, L"%lu", DeviceExtension->Channel);
			RtlInitUnicodeString(&SourceString, Buffer);
			break;
		}
		default:
			DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_ID / unknown query id type 0x%lx\n", IdType);
			ASSERT(FALSE);
			return STATUS_NOT_SUPPORTED;
	}

	Status = DuplicateUnicodeString(
		RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE,
		&SourceString,
		&String);
	*Information = (ULONG_PTR)String.Buffer;
	return Status;
}

static NTSTATUS
GetCurrentResources(
	IN PDEVICE_OBJECT DeviceObject,
	OUT PULONG CommandPortBase,
	OUT PULONG ControlPortBase,
	OUT PULONG BusMasterPortBase,
	OUT PULONG InterruptVector)
{
	PPDO_DEVICE_EXTENSION DeviceExtension;
	PFDO_DEVICE_EXTENSION FdoDeviceExtension;
	ULONG BaseIndex;
	ULONG BytesRead;
	PCI_COMMON_CONFIG PciConfig;
	NTSTATUS ret = STATUS_UNSUCCESSFUL;

	DeviceExtension = (PPDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
	FdoDeviceExtension = (PFDO_DEVICE_EXTENSION)DeviceExtension->ControllerFdo->DeviceExtension;
	BaseIndex = DeviceExtension->Channel * 2;

	BytesRead = (*FdoDeviceExtension->BusInterface->GetBusData)(
		FdoDeviceExtension->BusInterface->Context,
		PCI_WHICHSPACE_CONFIG,
		&PciConfig,
		0,
		PCI_COMMON_HDR_LENGTH);
	if (BytesRead != PCI_COMMON_HDR_LENGTH)
		return STATUS_IO_DEVICE_ERROR;

	/* We have found a known native pci ide controller */
	if ((PciConfig.ProgIf & 0x80) && (PciConfig.u.type0.BaseAddresses[4] & PCI_ADDRESS_IO_SPACE))
	{
		DPRINT("Found IDE Bus Master controller!\n");
		*BusMasterPortBase = PciConfig.u.type0.BaseAddresses[4] & PCI_ADDRESS_IO_ADDRESS_MASK;
		DPRINT("  IDE Bus Master Registers at IO %lx\n", *BusMasterPortBase);
	}
	else
	{
		*BusMasterPortBase = 0;
	}

	if ((PciConfig.ProgIf >> BaseIndex) & 0x1)
	{
		/* Native mode */
		if ((PciConfig.u.type0.BaseAddresses[BaseIndex + 0] & PCI_ADDRESS_IO_SPACE) &&
		    (PciConfig.u.type0.BaseAddresses[BaseIndex + 1] & PCI_ADDRESS_IO_SPACE))
		{
			/* Channel is enabled */
			*CommandPortBase = PciConfig.u.type0.BaseAddresses[BaseIndex + 0] & PCI_ADDRESS_IO_ADDRESS_MASK;
			*ControlPortBase = PciConfig.u.type0.BaseAddresses[BaseIndex + 1] & PCI_ADDRESS_IO_ADDRESS_MASK;
			*InterruptVector = PciConfig.u.type0.InterruptLine;
			ret = STATUS_SUCCESS;
		}
	}
	else
	{
		/* Compatibility mode */
		switch (DeviceExtension->Channel)
		{
			case 0:
				if (IoGetConfigurationInformation()->AtDiskPrimaryAddressClaimed)
					ret = STATUS_INSUFFICIENT_RESOURCES;
				else
				{
					*CommandPortBase = 0x1F0;
					*ControlPortBase = 0x3F6;
					*InterruptVector = 14;
					ret = STATUS_SUCCESS;
				}
				break;
			case 1:
				if (IoGetConfigurationInformation()->AtDiskSecondaryAddressClaimed)
					ret = STATUS_INSUFFICIENT_RESOURCES;
				else
				{
					*CommandPortBase = 0x170;
					*ControlPortBase = 0x376;
					*InterruptVector = 15;
					ret = STATUS_SUCCESS;
				}
				break;
		}
	}

	return ret;
}

static NTSTATUS
PciIdeXPdoQueryResourceRequirements(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp,
	OUT ULONG_PTR* Information)
{
	ULONG CommandPortBase;
	ULONG ControlPortBase;
	ULONG BusMasterPortBase;
	ULONG InterruptVector;
	ULONG ListSize;
	PIO_RESOURCE_REQUIREMENTS_LIST RequirementsList;
	PIO_RESOURCE_DESCRIPTOR Descriptor;
	NTSTATUS Status;

	Status = GetCurrentResources(DeviceObject, &CommandPortBase,
		&ControlPortBase, &BusMasterPortBase, &InterruptVector);
	if (!NT_SUCCESS(Status))
		return Status;

	DPRINT("IDE Channel %lu: IO %x and %x, BM %lx, Irq %lu\n",
		((PPDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->Channel,
		CommandPortBase, ControlPortBase,
		BusMasterPortBase, InterruptVector);

	/* FIXME: what to do with BusMasterPortBase? */

	ListSize = sizeof(IO_RESOURCE_REQUIREMENTS_LIST)
		+ 2 * sizeof(IO_RESOURCE_DESCRIPTOR);
	RequirementsList = ExAllocatePool(PagedPool, ListSize);
	if (!RequirementsList)
		return STATUS_INSUFFICIENT_RESOURCES;

	RtlZeroMemory(RequirementsList, ListSize);
	RequirementsList->ListSize = ListSize;
	RequirementsList->AlternativeLists = 1;

	RequirementsList->List[0].Version = 1;
	RequirementsList->List[0].Revision = 1;
	RequirementsList->List[0].Count = 3;

	Descriptor = &RequirementsList->List[0].Descriptors[0];

	/* Command port base */
	Descriptor->Option = 0; /* Required */
	Descriptor->Type = CmResourceTypePort;
	Descriptor->ShareDisposition = CmResourceShareDeviceExclusive;
	Descriptor->Flags = CM_RESOURCE_PORT_IO |
	                    CM_RESOURCE_PORT_16_BIT_DECODE |
	                    CM_RESOURCE_PORT_POSITIVE_DECODE;
	Descriptor->u.Port.Length = 7;
	Descriptor->u.Port.Alignment = 1;
	Descriptor->u.Port.MinimumAddress.QuadPart = (ULONGLONG)CommandPortBase;
	Descriptor->u.Port.MaximumAddress.QuadPart = (ULONGLONG)(CommandPortBase + 7 - 1);
	Descriptor++;

	/* Control port base */
	Descriptor->Option = 0; /* Required */
	Descriptor->Type = CmResourceTypePort;
	Descriptor->ShareDisposition = CmResourceShareDeviceExclusive;
	Descriptor->Flags = CM_RESOURCE_PORT_IO |
	                    CM_RESOURCE_PORT_16_BIT_DECODE |
	                    CM_RESOURCE_PORT_POSITIVE_DECODE;
	Descriptor->u.Port.Length = 1;
	Descriptor->u.Port.Alignment = 1;
	Descriptor->u.Port.MinimumAddress.QuadPart = (ULONGLONG)ControlPortBase;
	Descriptor->u.Port.MaximumAddress.QuadPart = (ULONGLONG)(ControlPortBase + 1 - 1);
	Descriptor++;

	/* Interrupt */
	Descriptor->Option = 0; /* Required */
	Descriptor->Type = CmResourceTypeInterrupt;
	Descriptor->ShareDisposition = CmResourceShareShared;
	Descriptor->Flags = CM_RESOURCE_INTERRUPT_LEVEL_SENSITIVE;
	Descriptor->u.Interrupt.MinimumVector = InterruptVector;
	Descriptor->u.Interrupt.MaximumVector = InterruptVector;

	*Information = (ULONG_PTR)RequirementsList;
	return STATUS_SUCCESS;
}

static NTSTATUS
PciIdeXPdoQueryDeviceText(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp,
	OUT ULONG_PTR* Information)
{
	PPDO_DEVICE_EXTENSION DeviceExtension;
	ULONG DeviceTextType;
	PCWSTR SourceString;
	UNICODE_STRING String;

	DeviceTextType = IoGetCurrentIrpStackLocation(Irp)->Parameters.QueryDeviceText.DeviceTextType;
	DeviceExtension = (PPDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

	switch (DeviceTextType)
	{
		case DeviceTextDescription:
		case DeviceTextLocationInformation:
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_TEXT / %S\n",
				DeviceTextType == DeviceTextDescription ? L"DeviceTextDescription" : L"DeviceTextLocationInformation");
			if (DeviceExtension->Channel == 0)
				SourceString = L"Primary channel";
			else
				SourceString = L"Secondary channel";
			break;
		}
		default:
			DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_TEXT / unknown type 0x%lx\n", DeviceTextType);
			ASSERT(FALSE);
			return STATUS_NOT_SUPPORTED;
	}

	if (RtlCreateUnicodeString(&String, SourceString))
	{
		*Information = (ULONG_PTR)String.Buffer;
		return STATUS_SUCCESS;
	}
	else
		return STATUS_INSUFFICIENT_RESOURCES;
}

static NTSTATUS
PciIdeXPdoQueryDeviceRelations(
	IN PDEVICE_OBJECT DeviceObject,
	OUT PDEVICE_RELATIONS* pDeviceRelations)
{
	PFDO_DEVICE_EXTENSION DeviceExtension;
	PDEVICE_RELATIONS DeviceRelations;

	DeviceExtension = (PFDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
	ASSERT(DeviceExtension->Common.IsFDO);

	DeviceRelations = (PDEVICE_RELATIONS)ExAllocatePool(
		PagedPool,
		sizeof(DEVICE_RELATIONS));
	if (!DeviceRelations)
		return STATUS_INSUFFICIENT_RESOURCES;

	ObReferenceObject(DeviceObject);
	DeviceRelations->Count = 1;
	DeviceRelations->Objects[0] = DeviceObject;

	*pDeviceRelations = DeviceRelations;
	return STATUS_SUCCESS;
}

NTSTATUS NTAPI
PciIdeXPdoPnpDispatch(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp)
{
	ULONG MinorFunction;
	PIO_STACK_LOCATION Stack;
	ULONG_PTR Information = Irp->IoStatus.Information;
	NTSTATUS Status;

	Stack = IoGetCurrentIrpStackLocation(Irp);
	MinorFunction = Stack->MinorFunction;

	switch (MinorFunction)
	{
		/* FIXME:
		 * Those are required:
		 *    IRP_MN_START_DEVICE (done)
		 *    IRP_MN_QUERY_STOP_DEVICE
		 *    IRP_MN_STOP_DEVICE
		 *    IRP_MN_CANCEL_STOP_DEVICE
		 *    IRP_MN_QUERY_REMOVE_DEVICE
		 *    IRP_MN_REMOVE_DEVICE
		 *    IRP_MN_CANCEL_REMOVE_DEVICE
		 *    IRP_MN_SURPRISE_REMOVAL
		 *    IRP_MN_QUERY_CAPABILITIES (done)
		 *    IRP_MN_QUERY_DEVICE_RELATIONS / TargetDeviceRelations (done)
		 *    IRP_MN_QUERY_ID / BusQueryDeviceID (done)
		 * Those may be required/optional:
		 *    IRP_MN_DEVICE_USAGE_NOTIFICATION
		 *    IRP_MN_QUERY_RESOURCES
		 *    IRP_MN_QUERY_RESOURCE_REQUIREMENTS (done)
		 *    IRP_MN_QUERY_DEVICE_TEXT
		 *    IRP_MN_QUERY_BUS_INFORMATION
		 *    IRP_MN_QUERY_INTERFACE
		 *    IRP_MN_READ_CONFIG
		 *    IRP_MN_WRITE_CONFIG
		 *    IRP_MN_EJECT
		 *    IRP_MN_SET_LOCK
		 * Those are optional:
		 *    IRP_MN_QUERY_DEVICE_RELATIONS / EjectionRelations
		 *    IRP_MN_QUERY_ID / BusQueryHardwareIDs (done)
		 *    IRP_MN_QUERY_ID / BusQueryCompatibleIDs (done)
		 *    IRP_MN_QUERY_ID / BusQueryInstanceID (done)
		 */
		case IRP_MN_START_DEVICE: /* 0x00 */
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_START_DEVICE\n");
			Status = STATUS_SUCCESS;
			break;
		}
                case IRP_MN_QUERY_REMOVE_DEVICE: /* 0x01 */
                {
                        DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_REMOVE_DEVICE\n");
                        Status = STATUS_UNSUCCESSFUL;
                        break;
                }
		case IRP_MN_QUERY_DEVICE_RELATIONS: /* 0x07 */
		{
			switch (Stack->Parameters.QueryDeviceRelations.Type)
			{
				case TargetDeviceRelation:
				{
					PDEVICE_RELATIONS DeviceRelations = NULL;
					DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / TargetDeviceRelation\n");
					Status = PciIdeXPdoQueryDeviceRelations(DeviceObject, &DeviceRelations);
					Information = (ULONG_PTR)DeviceRelations;
					break;
				}
				default:
				{
					DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / Unknown type 0x%lx\n",
						Stack->Parameters.QueryDeviceRelations.Type);
					ASSERT(FALSE);
					Status = STATUS_NOT_SUPPORTED;
					break;
				}
			}
			break;
		}
		case IRP_MN_QUERY_CAPABILITIES: /* 0x09 */
		{
			PDEVICE_CAPABILITIES DeviceCapabilities;
			ULONG i;
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_CAPABILITIES\n");

			DeviceCapabilities = (PDEVICE_CAPABILITIES)Stack->Parameters.DeviceCapabilities.Capabilities;
			/* FIXME: capabilities can change with connected device */
			DeviceCapabilities->LockSupported = FALSE;
			DeviceCapabilities->EjectSupported = FALSE;
			DeviceCapabilities->Removable = TRUE;
			DeviceCapabilities->DockDevice = FALSE;
			DeviceCapabilities->UniqueID = FALSE;
			DeviceCapabilities->SilentInstall = FALSE;
			DeviceCapabilities->RawDeviceOK = FALSE;
			DeviceCapabilities->SurpriseRemovalOK = TRUE;
			DeviceCapabilities->HardwareDisabled = FALSE; /* FIXME */
			//DeviceCapabilities->NoDisplayInUI = FALSE; /* FIXME */
			DeviceCapabilities->DeviceState[0] = PowerDeviceD0; /* FIXME */
			for (i = 0; i < PowerSystemMaximum; i++)
				DeviceCapabilities->DeviceState[i] = PowerDeviceD3; /* FIXME */
			//DeviceCapabilities->DeviceWake = PowerDeviceUndefined; /* FIXME */
			DeviceCapabilities->D1Latency = 0; /* FIXME */
			DeviceCapabilities->D2Latency = 0; /* FIXME */
			DeviceCapabilities->D3Latency = 0; /* FIXME */
			Status = STATUS_SUCCESS;
			break;
		}
		case IRP_MN_QUERY_RESOURCES: /* 0x0a */
		{
			/* This IRP is optional; do nothing */
			Information = Irp->IoStatus.Information;
			Status = Irp->IoStatus.Status;
			break;
		}
		case IRP_MN_QUERY_RESOURCE_REQUIREMENTS: /* 0x0b */
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_RESOURCE_REQUIREMENTS\n");
			Status = PciIdeXPdoQueryResourceRequirements(DeviceObject, Irp, &Information);
			break;
		}
		case IRP_MN_QUERY_DEVICE_TEXT: /* 0x0c */
		{
			Status = PciIdeXPdoQueryDeviceText(DeviceObject, Irp, &Information);
			break;
		}
		case IRP_MN_FILTER_RESOURCE_REQUIREMENTS: /* 0x0d */
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_FILTER_RESOURCE_REQUIREMENTS\n");
			Information = Irp->IoStatus.Information;
			Status = Irp->IoStatus.Status;
			break;
		}
		case IRP_MN_QUERY_ID: /* 0x13 */
		{
			Status = PciIdeXPdoQueryId(DeviceObject, Irp, &Information);
			break;
		}
                case IRP_MN_QUERY_PNP_DEVICE_STATE: /* 0x14 */
                {
                        DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_PNP_DEVICE_STATE\n");
                        Information |= PNP_DEVICE_NOT_DISABLEABLE;
                        Status = STATUS_SUCCESS;
                        break;
                }
		case IRP_MN_QUERY_BUS_INFORMATION: /* 0x15 */
		{
			PPNP_BUS_INFORMATION BusInfo;
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_BUS_INFORMATION\n");

			BusInfo = (PPNP_BUS_INFORMATION)ExAllocatePool(PagedPool, sizeof(PNP_BUS_INFORMATION));
			if (!BusInfo)
				Status = STATUS_INSUFFICIENT_RESOURCES;
			else
			{
				/*RtlCopyMemory(
					&BusInfo->BusTypeGuid,
					&GUID_DEVINTERFACE_XXX,
					sizeof(GUID));*/
				BusInfo->LegacyBusType = PNPBus;
				BusInfo->BusNumber = 0; /* FIXME */
				Information = (ULONG_PTR)BusInfo;
				Status = STATUS_SUCCESS;
			}
			break;
		}
		default:
		{
			/* We can't forward request to the lower driver, because
			 * we are a Pdo, so we don't have lower driver... */
			DPRINT1("IRP_MJ_PNP / Unknown minor function 0x%lx\n", MinorFunction);
			ASSERT(FALSE);
			Information = Irp->IoStatus.Information;
			Status = Irp->IoStatus.Status;
		}
	}

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