/*
 * PROJECT:     ReactOS VT100 emulator
 * LICENSE:     GPL - See COPYING in the top level directory
 * FILE:        drivers/base/green/pnp.c
 * PURPOSE:     IRP_MJ_PNP operations
 * PROGRAMMERS: Copyright 2005-2006 Hervé Poussineau (hpoussin@reactos.org)
 */

#include "green.h"

#define NDEBUG
#include <debug.h>

static NTSTATUS
CreateGreenFdo(
	IN PDRIVER_OBJECT DriverObject,
	IN PDEVICE_OBJECT GreenPdo)
{
	PGREEN_DRIVER_EXTENSION DriverExtension = NULL;
	PGREEN_DEVICE_EXTENSION DeviceExtension = NULL;
	OBJECT_ATTRIBUTES ObjectAttributes;
	ULONG Fcr;
	HANDLE LocalHandle = 0;
	ACCESS_MASK DesiredAccess = FILE_ANY_ACCESS;
	NTSTATUS Status;

	DriverExtension = IoGetDriverObjectExtension(DriverObject, DriverObject);

	Status = IoCreateDevice(
		DriverObject,
		sizeof(GREEN_DEVICE_EXTENSION),
		NULL,
		FILE_DEVICE_TERMSRV,
		FILE_DEVICE_SECURE_OPEN,
		FALSE,
		&DriverExtension->GreenMainDO);
	if (!NT_SUCCESS(Status))
	{
		DPRINT("IoCreateDevice() failed with status %08lx\n", Status);
		goto cleanup;
	}

	DeviceExtension = (PGREEN_DEVICE_EXTENSION)DriverExtension->GreenMainDO->DeviceExtension;
	RtlZeroMemory(DeviceExtension, sizeof(GREEN_DEVICE_EXTENSION));
	DeviceExtension->Common.Type = GreenFDO;
	DriverExtension->GreenMainDO->Flags |= DO_POWER_PAGABLE;
	DriverExtension->LowerDevice = IoAttachDeviceToDeviceStack(DriverExtension->GreenMainDO, GreenPdo);

	/* Initialize serial port */
	InitializeObjectAttributes(&ObjectAttributes, &DriverExtension->AttachedDeviceName, OBJ_KERNEL_HANDLE, NULL, NULL);
	Status = ObOpenObjectByName(
		&ObjectAttributes,
		IoFileObjectType,
		KernelMode,
		NULL,
		DesiredAccess,
		NULL,
		&LocalHandle);
	if (!NT_SUCCESS(Status))
	{
		DPRINT("ObOpenObjectByName() failed with status %08lx\n", Status);
		goto cleanup;
	}
	Status = ObReferenceObjectByHandle(
		LocalHandle,
		DesiredAccess,
		NULL,
		KernelMode,
		(PVOID*)&DeviceExtension->Serial,
		NULL);
	if (!NT_SUCCESS(Status))
	{
		DPRINT("ObReferenceObjectByHandle() failed with status %08lx\n", Status);
		goto cleanup;
	}
	Fcr = 0;
	Status = GreenDeviceIoControl(DeviceExtension->Serial, IOCTL_SERIAL_SET_FIFO_CONTROL,
		&Fcr, sizeof(Fcr), NULL, NULL);
	if (!NT_SUCCESS(Status))
	{
		DPRINT("GreenDeviceIoControl() failed with status %08lx\n", Status);
		goto cleanup;
	}
	Status = GreenDeviceIoControl(DeviceExtension->Serial, IOCTL_SERIAL_SET_BAUD_RATE,
		&DriverExtension->SampleRate, sizeof(DriverExtension->SampleRate), NULL, NULL);
	if (!NT_SUCCESS(Status))
	{
		DPRINT("GreenDeviceIoControl() failed with status %08lx\n", Status);
		goto cleanup;
	}
	DeviceExtension->LineControl.WordLength = 8;
	DeviceExtension->LineControl.Parity = NO_PARITY;
	DeviceExtension->LineControl.StopBits = STOP_BIT_1;
	Status = GreenDeviceIoControl(DeviceExtension->Serial, IOCTL_SERIAL_SET_LINE_CONTROL,
		&DeviceExtension->LineControl, sizeof(SERIAL_LINE_CONTROL), NULL, NULL);
	if (!NT_SUCCESS(Status))
	{
		DPRINT("GreenDeviceIoControl() failed with status %08lx\n", Status);
		goto cleanup;
	}
	RtlZeroMemory(&DeviceExtension->Timeouts, sizeof(SERIAL_TIMEOUTS));
	DeviceExtension->Timeouts.ReadIntervalTimeout = 100;
	Status = GreenDeviceIoControl(DeviceExtension->Serial, IOCTL_SERIAL_SET_TIMEOUTS,
		&DeviceExtension->Timeouts, sizeof(SERIAL_TIMEOUTS), NULL, NULL);
	if (!NT_SUCCESS(Status))
	{
		DPRINT("GreenDeviceIoControl() failed with status %08lx\n", Status);
		goto cleanup;
	}

	DriverExtension->GreenMainDO->Flags |= DO_BUFFERED_IO;
	DriverExtension->GreenMainDO->Flags &= ~DO_DEVICE_INITIALIZING;

	Status = STATUS_SUCCESS;

cleanup:
	if (LocalHandle != 0)
		ZwClose(LocalHandle);
	if (!NT_SUCCESS(Status))
	{
		if (DeviceExtension && DeviceExtension->Serial)
			ObDereferenceObject(DeviceExtension->Serial);
		if (DriverExtension)
		{
			if (DriverExtension->LowerDevice)
			{
				IoDetachDevice(DriverExtension->LowerDevice);
				DriverExtension->LowerDevice = NULL;
			}
			if (DriverExtension->GreenMainDO)
			{
				IoDeleteDevice(DriverExtension->GreenMainDO);
				DriverExtension->GreenMainDO = NULL;
			}
		}
	}
	return Status;
}

static NTSTATUS
ReportGreenPdo(
	IN PDRIVER_OBJECT DriverObject,
	IN PGREEN_DRIVER_EXTENSION DriverExtension)
{
	PDEVICE_OBJECT GreenPdo = NULL;
	NTSTATUS Status;

	/* Create green PDO */
	Status = IoReportDetectedDevice(
		DriverObject,
		InterfaceTypeUndefined, -1, -1,
		NULL, NULL, TRUE,
		&GreenPdo);
	if (!NT_SUCCESS(Status))
	{
		DPRINT("IoReportDetectedDevice() failed with status 0x%lx\n", Status);
		goto cleanup;
	}

	/* Create green FDO */
	Status = CreateGreenFdo(DriverObject, GreenPdo);

	IoInvalidateDeviceRelations(GreenPdo, BusRelations);

	/* FIXME: Update registry, set "DeviceReported" to 1 */

	Status = STATUS_SUCCESS;

cleanup:
	if (!NT_SUCCESS(Status))
	{
		if (DriverExtension->GreenMainDO)
			IoDeleteDevice(DriverExtension->GreenMainDO);
	}
	return Status;
}

NTSTATUS NTAPI
GreenAddDevice(
	IN PDRIVER_OBJECT DriverObject,
	IN PDEVICE_OBJECT Pdo)
{
	PGREEN_DRIVER_EXTENSION DriverExtension;

	DPRINT("AddDevice(DriverObject %p, Pdo %p)\n", DriverObject, Pdo);

	DriverExtension = IoGetDriverObjectExtension(DriverObject, DriverObject);

	if (Pdo == NULL)
	{
		if (DriverExtension->DeviceReported)
			/* Green Pdo has already been reported during a previous boot.
			 * We will get another AddDevice call soon.
			 */
			return STATUS_SUCCESS;
		else
			return ReportGreenPdo(DriverObject, DriverExtension);
	}
	else if (DriverExtension->GreenMainDO == NULL)
	{
		return CreateGreenFdo(DriverObject, Pdo);
	}
	else
	{
		PGREEN_DEVICE_EXTENSION GreenDeviceExtension;

		GreenDeviceExtension = (PGREEN_DEVICE_EXTENSION)DriverExtension->GreenMainDO->DeviceExtension;
		if (Pdo == GreenDeviceExtension->KeyboardPdo)
			return KeyboardAddDevice(DriverObject, Pdo);
		else if (Pdo == GreenDeviceExtension->ScreenPdo)
			return ScreenAddDevice(DriverObject, Pdo);
		else
			/* Strange PDO. We don't know it */
			ASSERT(FALSE);
			return STATUS_UNSUCCESSFUL;
	}
}

static NTSTATUS
GreenQueryBusRelations(
	IN PDEVICE_OBJECT DeviceObject,
	OUT PDEVICE_RELATIONS* pDeviceRelations)
{
	PGREEN_DEVICE_EXTENSION DeviceExtension;
	PDEVICE_RELATIONS DeviceRelations = NULL;
	NTSTATUS Status;

	DeviceExtension = (PGREEN_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

	/* Create PDOs for keyboard and screen */
	if (DeviceExtension->KeyboardPdo == NULL)
	{
		Status = IoCreateDevice(
			DeviceObject->DriverObject,
			sizeof(COMMON_DEVICE_EXTENSION),
			NULL,
			FILE_DEVICE_KEYBOARD,
			FILE_AUTOGENERATED_DEVICE_NAME | FILE_DEVICE_SECURE_OPEN,
			FALSE,
			&DeviceExtension->KeyboardPdo);
		if (!NT_SUCCESS(Status))
		{
			DPRINT("IoCreateDevice() failed with status 0x%lx\n", Status);
			goto cleanup;
		}
		((PCOMMON_DEVICE_EXTENSION)DeviceExtension->KeyboardPdo->DeviceExtension)->Type = KeyboardPDO;
		DeviceExtension->KeyboardPdo->Flags |= DO_POWER_PAGABLE | DO_BUS_ENUMERATED_DEVICE;
		DeviceExtension->KeyboardPdo->Flags &= ~DO_DEVICE_INITIALIZING;
	}

	if (DeviceExtension->ScreenPdo == NULL)
	{
		Status = IoCreateDevice(
			DeviceObject->DriverObject,
			sizeof(COMMON_DEVICE_EXTENSION),
			NULL,
			FILE_DEVICE_SCREEN,
			FILE_AUTOGENERATED_DEVICE_NAME | FILE_DEVICE_SECURE_OPEN,
			FALSE,
			&DeviceExtension->ScreenPdo);
		if (!NT_SUCCESS(Status))
		{
			DPRINT("IoCreateDevice() failed with status 0x%lx\n", Status);
			goto cleanup;
		}
		((PCOMMON_DEVICE_EXTENSION)DeviceExtension->ScreenPdo->DeviceExtension)->Type = ScreenPDO;
		DeviceExtension->ScreenPdo->Flags |= DO_POWER_PAGABLE | DO_BUS_ENUMERATED_DEVICE;
		DeviceExtension->ScreenPdo->Flags &= ~DO_DEVICE_INITIALIZING;
	}

	/* Allocate return structure */
	DeviceRelations = (PDEVICE_RELATIONS)ExAllocatePool(
		PagedPool,
		FIELD_OFFSET(DEVICE_RELATIONS, Objects) + 2 * sizeof(PDEVICE_OBJECT));
	if (!DeviceRelations)
		return STATUS_INSUFFICIENT_RESOURCES;

	/* Fill return structure */
	DeviceRelations->Count = 2;
	ObReferenceObject(DeviceExtension->KeyboardPdo);
	ObReferenceObject(DeviceExtension->ScreenPdo);
	DeviceRelations->Objects[0] = DeviceExtension->KeyboardPdo;
	DeviceRelations->Objects[1] = DeviceExtension->ScreenPdo;

	*pDeviceRelations = DeviceRelations;
	Status = STATUS_SUCCESS;

cleanup:
	if (!NT_SUCCESS(Status))
	{
		if (DeviceRelations)
		{
			ULONG i;
			for (i = 0; i < DeviceRelations->Count; i++)
				ObDereferenceObject(DeviceRelations->Objects[i]);
			ExFreePool(DeviceRelations);
		}
		if (DeviceExtension->KeyboardPdo)
		{
			IoDeleteDevice(DeviceExtension->KeyboardPdo);
			DeviceExtension->KeyboardPdo = NULL;
		}
		if (DeviceExtension->ScreenPdo)
		{
			IoDeleteDevice(DeviceExtension->ScreenPdo);
			DeviceExtension->ScreenPdo = NULL;
		}
	}
	return Status;
}

static NTSTATUS
GreenQueryId(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp,
	OUT ULONG_PTR* Information)
{
	GREEN_DEVICE_TYPE Type;
	ULONG IdType;
	NTSTATUS Status = Irp->IoStatus.Status;

	Type = ((PCOMMON_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->Type;
	IdType = IoGetCurrentIrpStackLocation(Irp)->Parameters.QueryId.IdType;

	switch (IdType)
	{
		case BusQueryDeviceID:
		{
			LPCWSTR Source = NULL;

			if (Type == ScreenPDO)
				Source = L"GREEN\\SCREEN";
			else if (Type == KeyboardPDO)
				Source = L"GREEN\\KEYBOARD";
			else
			{
				DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_ID / BusQueryDeviceId / Unknown type 0x%lx\n",
					Type);
				ASSERT(FALSE);
			}

			if (Source)
			{
				UNICODE_STRING SourceU, String;
				RtlInitUnicodeString(&SourceU, Source);
				Status = GreenDuplicateUnicodeString(
					RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE,
					&SourceU,
					&String);
				*Information = (ULONG_PTR)String.Buffer;
			}
			break;
		}
		case BusQueryHardwareIDs:
		{
			UNICODE_STRING SourceU = { 0, };

			if (Type == ScreenPDO)
			{
				RtlInitUnicodeString(&SourceU, L"GREEN\\SCREEN\0");
				/* We can add the two \0 that are at the end of the string */
				SourceU.Length = SourceU.MaximumLength = SourceU.Length + 2 * sizeof(WCHAR);
			}
			else if (Type == KeyboardPDO)
			{
				RtlInitUnicodeString(&SourceU, L"GREEN\\KEYBOARD\0");
				/* We can add the two \0 that are at the end of the string */
				SourceU.Length = SourceU.MaximumLength = SourceU.Length + 2 * sizeof(WCHAR);
			}
			else
			{
				DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_ID / BusQueryHardwareIDs / Unknown type 0x%lx\n",
					Type);
				ASSERT(FALSE);
			}

			if (SourceU.Length)
			{
				UNICODE_STRING String;
				Status = GreenDuplicateUnicodeString(
					RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE,
					&SourceU,
					&String);
				*Information = (ULONG_PTR)String.Buffer;
			}
			break;
		}
		case BusQueryCompatibleIDs:
		{
			/* We don't have any compatible ID */
			break;
		}
		case BusQueryInstanceID:
		{
			/* We don't have any instance ID */
			break;
		}
		default:
		{
			DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_ID / unknown query id type 0x%lx\n", IdType);
		}
	}

	return Status;
}

NTSTATUS
GreenPnp(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp)
{
	GREEN_DEVICE_TYPE Type;
	PIO_STACK_LOCATION Stack;
	ULONG_PTR Information = Irp->IoStatus.Information;
	NTSTATUS Status = Irp->IoStatus.Status;

	Type = ((PCOMMON_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->Type;
	Stack = IoGetCurrentIrpStackLocation(Irp);

	switch (Stack->MinorFunction)
	{
		case IRP_MN_START_DEVICE: /* 0x00 */
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_START_DEVICE\n");
			if (Type == GreenFDO || Type == KeyboardPDO || Type == ScreenPDO)
				Status = STATUS_SUCCESS;
			else
			{
				DPRINT1("IRP_MJ_PNP / IRP_MN_START_DEVICE / Unknown type 0x%lx\n",
					Type);
				ASSERT(FALSE);
			}
			break;
		}
		case IRP_MN_QUERY_DEVICE_RELATIONS: /* 0x07 */
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS\n");
			switch (Stack->Parameters.QueryDeviceRelations.Type)
			{
				case BusRelations:
				{
					if (Type == GreenFDO)
					{
						PDEVICE_RELATIONS DeviceRelations = NULL;
						Status = GreenQueryBusRelations(DeviceObject, &DeviceRelations);
						Information = (ULONG_PTR)DeviceRelations;
					}
					else if (Type == KeyboardPDO || Type == ScreenPDO)
					{
						PDEVICE_RELATIONS DeviceRelations = NULL;
						DeviceRelations = ExAllocatePool(PagedPool, FIELD_OFFSET(DEVICE_RELATIONS, Objects));
						if (!DeviceRelations)
							Status = STATUS_INSUFFICIENT_RESOURCES;
						else
						{
							DeviceRelations->Count = 0;
							Status = STATUS_SUCCESS;
							Information = (ULONG_PTR)DeviceRelations;
						}
					}
					else
					{
						DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / BusRelations / Unknown type 0x%lx\n",
							Type);
						ASSERT(FALSE);
					}
					break;
				}
				default:
				{
					DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / Unknown type 0x%lx\n",
						Stack->Parameters.QueryDeviceRelations.Type);
					break;
				}
			}
			break;
		}
		case IRP_MN_QUERY_RESOURCES: /* 0x0a */
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_RESOURCES\n");
			/* We don't need resources */
			break;
		}
		case IRP_MN_QUERY_RESOURCE_REQUIREMENTS: /* 0x0b */
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_RESOURCE_REQUIREMENTS\n");
			/* We don't need resources */
			break;
		}
		case IRP_MN_QUERY_DEVICE_TEXT: /* 0x0c */
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_TEXT\n");
			switch (Stack->Parameters.QueryDeviceText.DeviceTextType)
			{
				case DeviceTextDescription:
				{
					LPCWSTR Description = NULL;
					if (Type == GreenFDO)
						Description = L"Green device";
					else if (Type == ScreenPDO)
						Description = L"Green screen";
					else if (Type == KeyboardPDO)
						Description = L"Green keyboard";

					if (Description != NULL)
					{
						LPWSTR Destination = ExAllocatePool(PagedPool, wcslen(Description) * sizeof(WCHAR) + sizeof(UNICODE_NULL));
						if (!Destination)
							Status = STATUS_INSUFFICIENT_RESOURCES;
						else
						{
							wcscpy(Destination, Description);
							Information = (ULONG_PTR)Description;
							Status = STATUS_SUCCESS;
						}
					}
					else
					{
						DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_TEXT / DeviceTextDescription / Unknown type 0x%lx\n",
							Type);
						ASSERT(FALSE);
					}
					break;
				}
				case DeviceTextLocationInformation:
				{
					/* We don't have any text location to report,
					 * and this query is optional, so ignore it.
					 */
					break;
				}
				default:
				{
					DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_TEXT / unknown type 0x%lx\n",
						Stack->Parameters.QueryDeviceText.DeviceTextType);
					ASSERT(FALSE);
					break;
				}
			}
			break;
		}
		case IRP_MN_QUERY_ID: /* 0x13 */
		{
			DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_ID\n");
			Status = GreenQueryId(DeviceObject, Irp, &Information);
			break;
		}
		default:
		{
			DPRINT1("IRP_MJ_PNP / unknown minor function 0x%lx\n", Stack->MinorFunction);
			break;
		}
	}

	Irp->IoStatus.Status = Status;
	Irp->IoStatus.Information = Information;
	if (Status != STATUS_PENDING)
		IoCompleteRequest(Irp, IO_NO_INCREMENT);

	return Status;
}