From 0d8e537bc562c39695de25371ba6e8a0adeac5dc Mon Sep 17 00:00:00 2001 From: ruki Date: Sat, 12 May 2018 22:18:30 +0800 Subject: add wdk.mc rule and serial tests --- .gitignore | 1 + tests/projects/wdk/kmdf/serial/error.c | 67 + tests/projects/wdk/kmdf/serial/flush.c | 86 + tests/projects/wdk/kmdf/serial/immediat.c | 458 +++++ tests/projects/wdk/kmdf/serial/initunlo.c | 197 ++ tests/projects/wdk/kmdf/serial/ioctl.c | 2187 +++++++++++++++++++++ tests/projects/wdk/kmdf/serial/isr.c | 1517 +++++++++++++++ tests/projects/wdk/kmdf/serial/log.c | 97 + tests/projects/wdk/kmdf/serial/log.h | 37 + tests/projects/wdk/kmdf/serial/modmflow.c | 1714 ++++++++++++++++ tests/projects/wdk/kmdf/serial/openclos.c | 850 ++++++++ tests/projects/wdk/kmdf/serial/pnp.c | 2804 +++++++++++++++++++++++++++ tests/projects/wdk/kmdf/serial/power.c | 331 ++++ tests/projects/wdk/kmdf/serial/precomp.h | 18 + tests/projects/wdk/kmdf/serial/precompsrc.c | 1 + tests/projects/wdk/kmdf/serial/purge.c | 175 ++ tests/projects/wdk/kmdf/serial/qsfile.c | 180 ++ tests/projects/wdk/kmdf/serial/read.c | 1748 +++++++++++++++++ tests/projects/wdk/kmdf/serial/registry.c | 443 +++++ tests/projects/wdk/kmdf/serial/serial.h | 1757 +++++++++++++++++ tests/projects/wdk/kmdf/serial/serial.inx | Bin 0 -> 6212 bytes tests/projects/wdk/kmdf/serial/serial.rc | 14 + tests/projects/wdk/kmdf/serial/serialp.h | 596 ++++++ tests/projects/wdk/kmdf/serial/serlog.mc | 290 +++ tests/projects/wdk/kmdf/serial/trace.h | 118 ++ tests/projects/wdk/kmdf/serial/utils.c | 1946 +++++++++++++++++++ tests/projects/wdk/kmdf/serial/waitmask.c | 574 ++++++ tests/projects/wdk/kmdf/serial/wmi.c | 295 +++ tests/projects/wdk/kmdf/serial/write.c | 1195 ++++++++++++ tests/projects/wdk/kmdf/serial/xmake.lua | 23 + xmake/rules/wdk/inf/xmake.lua | 81 +- xmake/rules/wdk/mc/xmake.lua | 120 ++ xmake/rules/wdk/xmake.lua | 8 +- 33 files changed, 19899 insertions(+), 29 deletions(-) create mode 100644 tests/projects/wdk/kmdf/serial/error.c create mode 100644 tests/projects/wdk/kmdf/serial/flush.c create mode 100644 tests/projects/wdk/kmdf/serial/immediat.c create mode 100644 tests/projects/wdk/kmdf/serial/initunlo.c create mode 100644 tests/projects/wdk/kmdf/serial/ioctl.c create mode 100644 tests/projects/wdk/kmdf/serial/isr.c create mode 100644 tests/projects/wdk/kmdf/serial/log.c create mode 100644 tests/projects/wdk/kmdf/serial/log.h create mode 100644 tests/projects/wdk/kmdf/serial/modmflow.c create mode 100644 tests/projects/wdk/kmdf/serial/openclos.c create mode 100644 tests/projects/wdk/kmdf/serial/pnp.c create mode 100644 tests/projects/wdk/kmdf/serial/power.c create mode 100644 tests/projects/wdk/kmdf/serial/precomp.h create mode 100644 tests/projects/wdk/kmdf/serial/precompsrc.c create mode 100644 tests/projects/wdk/kmdf/serial/purge.c create mode 100644 tests/projects/wdk/kmdf/serial/qsfile.c create mode 100644 tests/projects/wdk/kmdf/serial/read.c create mode 100644 tests/projects/wdk/kmdf/serial/registry.c create mode 100644 tests/projects/wdk/kmdf/serial/serial.h create mode 100644 tests/projects/wdk/kmdf/serial/serial.inx create mode 100644 tests/projects/wdk/kmdf/serial/serial.rc create mode 100644 tests/projects/wdk/kmdf/serial/serialp.h create mode 100644 tests/projects/wdk/kmdf/serial/serlog.mc create mode 100644 tests/projects/wdk/kmdf/serial/trace.h create mode 100644 tests/projects/wdk/kmdf/serial/utils.c create mode 100644 tests/projects/wdk/kmdf/serial/waitmask.c create mode 100644 tests/projects/wdk/kmdf/serial/wmi.c create mode 100644 tests/projects/wdk/kmdf/serial/write.c create mode 100644 tests/projects/wdk/kmdf/serial/xmake.lua create mode 100644 xmake/rules/wdk/mc/xmake.lua diff --git a/.gitignore b/.gitignore index af6daaa36..77458781a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ *.zip *.gch *.gch.d +*.sys cscope.* gmon.out other diff --git a/tests/projects/wdk/kmdf/serial/error.c b/tests/projects/wdk/kmdf/serial/error.c new file mode 100644 index 000000000..904e54b05 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/error.c @@ -0,0 +1,67 @@ +/*++ +Copyright (c) Microsoft Corporation + +Module Name: + + error.c + +Abstract: + + This module contains the code that is very specific to error + operations in the serial driver + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "error.tmh" +#endif + + +VOID +SerialCommError( + IN WDFDPC Dpc + ) +/*++ + +Routine Description: + + This routine is invoked at dpc level to in response to + a comm error. All comm errors complete all read and writes + +Arguments: + + +Return Value: + + None. + +--*/ +{ + PSERIAL_DEVICE_EXTENSION Extension = NULL; + + Extension = SerialGetDeviceExtension(WdfDpcGetParentObject(Dpc)); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT, + ">SerialCommError(%p)\n", Extension); + + SerialFlushRequests( + Extension->WriteQueue, + &Extension->CurrentWriteRequest + ); + + SerialFlushRequests( + Extension->ReadQueue, + &Extension->CurrentReadRequest + ); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT, + "SerialFlush(%p, %p)\n", Device, Irp); + + PAGED_CODE(); + + WdfIoQueueStopSynchronously(extension->WriteQueue); + // + // Flush is done - restart the queue + // + WdfIoQueueStart(extension->WriteQueue); + + Irp->IoStatus.Information = 0L; + Irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "CurrentImmediateRequest); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, ">SerialStartImmediate(%p)\n", + Extension); + + UseATimer = FALSE; + reqContext->Status = STATUS_PENDING; + + // + // Calculate the timeout value needed for the + // request. Note that the values stored in the + // timeout record are in milliseconds. Note that + // if the timeout values are zero then we won't start + // the timer. + // + + Timeouts = Extension->Timeouts; + + if (Timeouts.WriteTotalTimeoutConstant || + Timeouts.WriteTotalTimeoutMultiplier) { + + UseATimer = TRUE; + + // + // We have some timer values to calculate. + // + + TotalTime.QuadPart + = (LONGLONG)((ULONG)Timeouts.WriteTotalTimeoutMultiplier); + + TotalTime.QuadPart += Timeouts.WriteTotalTimeoutConstant; + + TotalTime.QuadPart *= -10000; + + } + + // + // As the request might be going to the isr, this is a good time + // to initialize the reference count. + // + + SERIAL_INIT_REFERENCE(reqContext); + + // + // We give the request to to the isr to write out. + // We set a cancel routine that knows how to + // grab the current write away from the isr. + // + SerialSetCancelRoutine(Extension->CurrentImmediateRequest, + SerialCancelImmediate); + + if (UseATimer) { + BOOLEAN result; + + result = SerialSetTimer( + Extension->ImmediateTotalTimer, + TotalTime + ); + + if(result == FALSE) { + // + // Since the timer knows about the request we increment + // the reference count. + // + + SERIAL_SET_REFERENCE( + reqContext, + SERIAL_REF_TOTAL_TIMER + ); + } + } + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialGiveImmediateToIsr, + Extension + ); + + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "SerialCompleteImmediate(%p)\n", + Extension); + + SerialTryToCompleteCurrent( + Extension, + NULL, + STATUS_SUCCESS, + &Extension->CurrentImmediateRequest, + NULL, + NULL, + Extension->ImmediateTotalTimer, + NULL, + SerialGetNextImmediate, + SERIAL_REF_ISR + ); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, "SerialTimeoutImmediate(%p)\n", + Extension); + + SerialTryToCompleteCurrent( + Extension, + SerialGrabImmediateFromIsr, + STATUS_TIMEOUT, + &Extension->CurrentImmediateRequest, + NULL, + NULL, + Extension->ImmediateTotalTimer, + NULL, + SerialGetNextImmediate, + SERIAL_REF_TOTAL_TIMER + ); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, "TotalCharsQueued >= 1); + Extension->TotalCharsQueued--; + + *CurrentOpRequest = NULL; + *NewRequest = NULL; + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialProcessEmptyTransmit, + Extension + ); + + SerialCompleteRequest(oldRequest, reqContext->Status, reqContext->Information); +} + +VOID +SerialCancelImmediate( + IN WDFREQUEST Request + ) + +/*++ + +Routine Description: + + This routine is used to cancel a request that is waiting on + a comm event. + +Arguments: + + Request - Pointer to the WDFREQUEST for the current request + +Return Value: + + None. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION Extension = NULL; + WDFDEVICE device = WdfIoQueueGetDevice(WdfRequestGetIoQueue(Request)); + + UNREFERENCED_PARAMETER(Request); + + Extension = SerialGetDeviceExtension(device); + + SerialTryToCompleteCurrent( + Extension, + SerialGrabImmediateFromIsr, + STATUS_CANCELLED, + &Extension->CurrentImmediateRequest, + NULL, + NULL, + Extension->ImmediateTotalTimer, + NULL, + SerialGetNextImmediate, + SERIAL_REF_CANCEL + ); + +} + +BOOLEAN +SerialGiveImmediateToIsr( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) +/*++ + +Routine Description: + + Try to start off the write by slipping it in behind + a transmit immediate char, or if that isn't available + and the transmit holding register is empty, "tickle" + the UART into interrupting with a transmit buffer + empty. + + NOTE: This routine is called by WdfInterruptSynchronize. + + NOTE: This routine assumes that it is called with the + cancel spin lock held. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ +{ + PSERIAL_DEVICE_EXTENSION Extension = Context; + PREQUEST_CONTEXT reqContext; + + UNREFERENCED_PARAMETER(Interrupt); + + reqContext = SerialGetRequestContext(Extension->CurrentImmediateRequest); + + Extension->TransmitImmediate = TRUE; + Extension->ImmediateChar = *((UCHAR *) (reqContext->SystemBuffer)); + + // + // The isr now has a reference to the request. + // + + SERIAL_SET_REFERENCE( + reqContext, + SERIAL_REF_ISR + ); + + // + // Check first to see if a write is going on. If + // there is then we'll just slip in during the write. + // + + if (!Extension->WriteLength) { + + // + // If there is no normal write transmitting then we + // will "re-enable" the transmit holding register empty + // interrupt. The 8250 family of devices will always + // signal a transmit holding register empty interrupt + // *ANY* time this bit is set to one. By doing things + // this way we can simply use the normal interrupt code + // to start off this write. + // + // We've been keeping track of whether the transmit holding + // register is empty so it we only need to do this + // if the register is empty. + // + + if (Extension->HoldingEmpty) { + DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + + } + + } + + return FALSE; + +} + +BOOLEAN +SerialGrabImmediateFromIsr( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + + This routine is used to grab the current request, which could be timing + out or canceling, from the ISR + + NOTE: This routine is being called from WdfInterruptSynchronize. + + NOTE: This routine assumes that the cancel spin lock is held + when this routine is called. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + Always false. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION Extension = Context; + PREQUEST_CONTEXT reqContext; + + UNREFERENCED_PARAMETER(Interrupt); + + reqContext = SerialGetRequestContext(Extension->CurrentImmediateRequest); + + if (Extension->TransmitImmediate) { + + Extension->TransmitImmediate = FALSE; + + // + // Since the isr no longer references this request, we can + // decrement it's reference count. + // + + SERIAL_CLEAR_REFERENCE( + reqContext, + SERIAL_REF_ISR + ); + + } + + return FALSE; + +} + + diff --git a/tests/projects/wdk/kmdf/serial/initunlo.c b/tests/projects/wdk/kmdf/serial/initunlo.c new file mode 100644 index 000000000..07e7fb003 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/initunlo.c @@ -0,0 +1,197 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + initunlo.c + +Abstract: + + This module contains the code that is very specific to initialization + and unload operations in the serial driver + + WDF Version of serial sample doesn't support: + 1) Multiport Serial devices. + 2) Enumeration of Non PNP serial devices that are not detected by BIOS + (IO address range 0x2F0-0x2F7 using IRQ 9) +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "initunlo.tmh" +#endif + +static const PHYSICAL_ADDRESS SerialPhysicalZero = {0}; + +// +// We use this to query into the registry as to whether we +// should break at driver entry. +// + +SERIAL_FIRMWARE_DATA driverDefaults; + +// +// This is exported from the kernel. It is used to point +// to the address that the kernel debugger is using. +// +extern PUCHAR *KdComPortInUse; +// +// INIT - only needed during init and then can be disposed +// PAGESRP0 - always paged / never locked +// PAGESER - must be locked when a device is open, else paged +// +// +// INIT is used for DriverEntry() specific code +// +// PAGESRP0 is used for code that is not often called and has nothing +// to do with I/O performance. An example, passive-level PNP +// support functions +// +// PAGESER is used for code that needs to be locked after an open for both +// performance and IRQL reasons. +// + +ULONG DebugLevel = TRACE_LEVEL_INFORMATION; +ULONG DebugFlag = 0xf;//0x46;//0x4FF; //0x00000006; + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(INIT, DriverEntry) +#pragma alloc_text(PAGE, SerialEvtDriverContextCleanup) +#endif + + + +NTSTATUS +DriverEntry( + IN PDRIVER_OBJECT DriverObject, + IN PUNICODE_STRING RegistryPath + ) +/*++ + +Routine Description: + + The entry point that the system point calls to initialize + any driver. + +Arguments: + + DriverObject - Just what it says, really of little use + to the driver itself, it is something that the IO system + cares more about. + + PathToRegistry - points to the entry for this driver + in the current control set of the registry. + +Return Value: + + Always STATUS_SUCCESS + +--*/ + +{ + WDF_DRIVER_CONFIG config; + WDFDRIVER hDriver; + NTSTATUS status; + WDF_OBJECT_ATTRIBUTES attributes; + + // + // Initialize WPP Tracing + // + WPP_INIT_TRACING( DriverObject, RegistryPath ); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT, + "Serial Sample (WDF Version)\n"); + // + // Register a cleanup callback so that we can call WPP_CLEANUP when + // the framework driver object is deleted during driver unload. + // + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.EvtCleanupCallback = SerialEvtDriverContextCleanup; + + WDF_DRIVER_CONFIG_INIT(&config, SerialEvtDeviceAdd); + + status = WdfDriverCreate(DriverObject, + RegistryPath, + &attributes, + &config, + &hDriver); + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_INIT, + "WdfDriverCreate failed with status 0x%x\n", + status); + // + // Cleanup tracing here because DriverContextCleanup will not be called + // as we have failed to create WDFDRIVER object itself. + // Please note that if your return failure from DriverEntry after the + // WDFDRIVER object is created successfully, you don't have to + // call WPP cleanup because in those cases DriverContextCleanup + // will be executed when the framework deletes the DriverObject. + // + WPP_CLEANUP(DriverObject); + return status; + } + + // + // Call to find out default values to use for all the devices that the + // driver controls, including whether or not to break on entry. + // + + SerialGetConfigDefaults(&driverDefaults, hDriver); + + // + // Break on entry if requested via registry + // + if (driverDefaults.ShouldBreakOnEntry) { + DbgBreakPoint(); + } + + + return status; +} + + +_Use_decl_annotations_ +VOID +SerialEvtDriverContextCleanup( + WDFOBJECT Driver + ) +/*++ +Routine Description: + + Free all the resources allocated in DriverEntry. + +Arguments: + + Driver - handle to a WDF Driver object. + +Return Value: + + VOID. + +--*/ +{ + UNREFERENCED_PARAMETER(Driver); + + PAGED_CODE (); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT, + "--> SerialEvtDriverContextCleanup\n"); + + // + // Stop WPP Tracing + // + WPP_CLEANUP( WdfDriverWdmGetDriverObject(Driver) ); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT, + "<-- SerialEvtDriverContextCleanup\n"); + +} + + + diff --git a/tests/projects/wdk/kmdf/serial/ioctl.c b/tests/projects/wdk/kmdf/serial/ioctl.c new file mode 100644 index 000000000..07ff2b147 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/ioctl.c @@ -0,0 +1,2187 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + ioctl.c + +Abstract: + + This module contains the ioctl dispatcher as well as a couple + of routines that are generally just called in response to + ioctl calls. + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "ioctl.tmh" +#endif + +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGetModemUpdate; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGetCommStatus; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetEscapeChar; + +PCHAR +SerialGetIoctlName( + IN ULONG IoControlCode + ) +/*++ + +Routine Description: + SerialGetIoctlName returns the name of the ioctl + +--*/ +{ + switch (IoControlCode) + { + case IOCTL_SERIAL_SET_BAUD_RATE : return "IOCTL_SERIAL_SET_BAUD_RATE"; + case IOCTL_SERIAL_GET_BAUD_RATE: return "IOCTL_SERIAL_GET_BAUD_RATE"; + case IOCTL_SERIAL_GET_MODEM_CONTROL: return "IOCTL_SERIAL_GET_MODEM_CONTROL"; + case IOCTL_SERIAL_SET_MODEM_CONTROL: return "IOCTL_SERIAL_SET_MODEM_CONTROL"; + case IOCTL_SERIAL_SET_FIFO_CONTROL: return "IOCTL_SERIAL_SET_FIFO_CONTROL"; + case IOCTL_SERIAL_SET_LINE_CONTROL: return "IOCTL_SERIAL_SET_LINE_CONTROL"; + case IOCTL_SERIAL_GET_LINE_CONTROL: return "IOCTL_SERIAL_GET_LINE_CONTROL"; + case IOCTL_SERIAL_SET_TIMEOUTS: return "IOCTL_SERIAL_SET_TIMEOUTS"; + case IOCTL_SERIAL_GET_TIMEOUTS: return "IOCTL_SERIAL_GET_TIMEOUTS"; + case IOCTL_SERIAL_SET_CHARS: return "IOCTL_SERIAL_SET_CHARS"; + case IOCTL_SERIAL_GET_CHARS: return "IOCTL_SERIAL_GET_CHARS"; + case IOCTL_SERIAL_SET_DTR: return "IOCTL_SERIAL_SET_DTR"; + case IOCTL_SERIAL_CLR_DTR: return "IOCTL_SERIAL_SET_DTR"; + case IOCTL_SERIAL_RESET_DEVICE: return "IOCTL_SERIAL_RESET_DEVICE"; + case IOCTL_SERIAL_SET_RTS: return "IOCTL_SERIAL_SET_RTS"; + case IOCTL_SERIAL_CLR_RTS: return "IOCTL_SERIAL_CLR_RTS"; + case IOCTL_SERIAL_SET_XOFF: return "IOCTL_SERIAL_SET_XOFF"; + case IOCTL_SERIAL_SET_XON: return "IOCTL_SERIAL_SET_XON"; + case IOCTL_SERIAL_SET_BREAK_ON: return "IOCTL_SERIAL_SET_BREAK_ON"; + case IOCTL_SERIAL_SET_BREAK_OFF: return "IOCTL_SERIAL_SET_BREAK_OFF"; + case IOCTL_SERIAL_SET_QUEUE_SIZE: return "IOCTL_SERIAL_SET_QUEUE_SIZE"; + case IOCTL_SERIAL_GET_WAIT_MASK: return "IOCTL_SERIAL_GET_WAIT_MASK"; + case IOCTL_SERIAL_SET_WAIT_MASK: return "IOCTL_SERIAL_SET_WAIT_MASK"; + case IOCTL_SERIAL_WAIT_ON_MASK: return "IOCTL_SERIAL_WAIT_ON_MASK"; + case IOCTL_SERIAL_IMMEDIATE_CHAR: return "IOCTL_SERIAL_IMMEDIATE_CHAR"; + case IOCTL_SERIAL_PURGE: return "IOCTL_SERIAL_PURGE"; + case IOCTL_SERIAL_GET_HANDFLOW: return "IOCTL_SERIAL_GET_HANDFLOW"; + case IOCTL_SERIAL_SET_HANDFLOW: return "IOCTL_SERIAL_SET_HANDFLOW"; + case IOCTL_SERIAL_GET_MODEMSTATUS: return "IOCTL_SERIAL_GET_MODEMSTATUS"; + case IOCTL_SERIAL_GET_DTRRTS: return "IOCTL_SERIAL_GET_DTRRTS"; + case IOCTL_SERIAL_GET_COMMSTATUS: return "IOCTL_SERIAL_GET_COMMSTATUS"; + case IOCTL_SERIAL_GET_PROPERTIES: return "IOCTL_SERIAL_GET_PROPERTIES"; + case IOCTL_SERIAL_XOFF_COUNTER: return "IOCTL_SERIAL_XOFF_COUNTER"; + case IOCTL_SERIAL_LSRMST_INSERT: return "IOCTL_SERIAL_LSRMST_INSERT"; + case IOCTL_SERIAL_CONFIG_SIZE: return "IOCTL_SERIAL_CONFIG_SIZE"; + case IOCTL_SERIAL_GET_STATS: return "IOCTL_SERIAL_GET_STATS"; + case IOCTL_SERIAL_CLEAR_STATS: return "IOCTL_SERIAL_CLEAR_STATS"; + default: return "UnKnown ioctl"; + } +} + + + +BOOLEAN +SerialGetStats( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + In sync with the interrpt service routine (which sets the perf stats) + return the perf stats to the caller. + + +Arguments: + + Context - Pointer to a the request. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + PREQUEST_CONTEXT reqContext = (PREQUEST_CONTEXT)Context; + PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension(WdfInterruptGetDevice(Interrupt)); + PSERIALPERF_STATS sp = reqContext->SystemBuffer; + + UNREFERENCED_PARAMETER(Interrupt); + + *sp = extension->PerfStats; + return FALSE; + +} + + +BOOLEAN +SerialClearStats( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + In sync with the interrpt service routine (which sets the perf stats) + clear the perf stats. + + +Arguments: + + Context - Pointer to a the extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + UNREFERENCED_PARAMETER(Interrupt); + + RtlZeroMemory( + &((PSERIAL_DEVICE_EXTENSION)Context)->PerfStats, + sizeof(SERIALPERF_STATS) + ); + + RtlZeroMemory(&((PSERIAL_DEVICE_EXTENSION)Context)->WmiPerfData, + sizeof(SERIAL_WMI_PERF_DATA)); + + return FALSE; +} + + + +BOOLEAN +SerialSetChars( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine is used to set the special characters for the + driver. + +Arguments: + + Context - Pointer to a structure that contains a pointer to + the device extension and a pointer to a special characters + structure. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + UNREFERENCED_PARAMETER(Interrupt); + + ((PSERIAL_IOCTL_SYNC)Context)->Extension->SpecialChars = + *((PSERIAL_CHARS)(((PSERIAL_IOCTL_SYNC)Context)->Data)); + + return FALSE; +} + + +BOOLEAN +SerialSetBaud( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine is used to set the baud rate of the device. + +Arguments: + + Context - Pointer to a structure that contains a pointer to + the device extension and what should be the current + baud rate. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension; + USHORT Appropriate = PtrToUshort(((PSERIAL_IOCTL_SYNC)Context)->Data); + + UNREFERENCED_PARAMETER(Interrupt); + + WRITE_DIVISOR_LATCH( + Extension, + Extension->Controller, + Appropriate + ); + + return FALSE; +} + + +BOOLEAN +SerialSetLineControl( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine is used to set the buad rate of the device. + +Arguments: + + Context - Pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION Extension = Context; + + UNREFERENCED_PARAMETER(Interrupt); + + WRITE_LINE_CONTROL(Extension, + Extension->Controller, + Extension->LineControl + ); + + return FALSE; +} + + +BOOLEAN +SerialGetModemUpdate( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine is simply used to call the interrupt level routine + that handles modem status update. + +Arguments: + + Context - Pointer to a structure that contains a pointer to + the device extension and a pointer to a ulong. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension; + ULONG *Result = (ULONG *)(((PSERIAL_IOCTL_SYNC)Context)->Data); + + UNREFERENCED_PARAMETER(Interrupt); + + *Result = SerialHandleModemUpdate( + Extension, + FALSE + ); + + return FALSE; +} + + + +BOOLEAN +SerialSetMCRContents( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) +/*++ + +Routine Description: + + This routine is simply used to set the contents of the MCR + +Arguments: + + Context - Pointer to a structure that contains a pointer to + the device extension and a pointer to a ulong. + +Return Value: + + This routine always returns FALSE. + +--*/ +{ + PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension; + ULONG *Result = (ULONG *)(((PSERIAL_IOCTL_SYNC)Context)->Data); + + UNREFERENCED_PARAMETER(Interrupt); + + // + // This is severe casting abuse!!! + // + WRITE_MODEM_CONTROL(Extension, Extension->Controller, (UCHAR)PtrToUlong(Result)); + + return FALSE; +} + + + + +BOOLEAN +SerialGetMCRContents( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine is simply used to get the contents of the MCR + +Arguments: + + Context - Pointer to a structure that contains a pointer to + the device extension and a pointer to a ulong. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension; + ULONG *Result = (ULONG *)(((PSERIAL_IOCTL_SYNC)Context)->Data); + + UNREFERENCED_PARAMETER(Interrupt); + + *Result = READ_MODEM_CONTROL(Extension, Extension->Controller); + + return FALSE; +} + + + + +BOOLEAN +SerialSetFCRContents( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) +/*++ + +Routine Description: + + This routine is simply used to set the contents of the FCR + +Arguments: + + Context - Pointer to a structure that contains a pointer to + the device extension and a pointer to a ulong. + +Return Value: + + This routine always returns FALSE. + +--*/ +{ + PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension; + ULONG *Result = (ULONG *)(((PSERIAL_IOCTL_SYNC)Context)->Data); + + UNREFERENCED_PARAMETER(Interrupt); + + // + // This is severe casting abuse!!! + // + WRITE_FIFO_CONTROL(Extension, Extension->Controller, (UCHAR)*Result); + + return FALSE; +} + + + +BOOLEAN +SerialGetCommStatus( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This is used to get the current state of the serial driver. + +Arguments: + + Context - Pointer to a structure that contains a pointer to + the device extension and a pointer to a serial status + record. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION Extension = ((PSERIAL_IOCTL_SYNC)Context)->Extension; + PSERIAL_STATUS Stat = ((PSERIAL_IOCTL_SYNC)Context)->Data; + + UNREFERENCED_PARAMETER(Interrupt); + + Stat->Errors = Extension->ErrorWord; + Extension->ErrorWord = 0; + + // + // Eof isn't supported in binary mode + // + Stat->EofReceived = FALSE; + + Stat->AmountInInQueue = Extension->CharsInInterruptBuffer; + + Stat->AmountInOutQueue = Extension->TotalCharsQueued; + + if (Extension->WriteLength) { + + // + // By definition if we have a writelength the we have + // a current write request. + // + PREQUEST_CONTEXT reqContext = NULL; + + ASSERT(Extension->CurrentWriteRequest); + ASSERT(Stat->AmountInOutQueue >= Extension->WriteLength); + + reqContext = SerialGetRequestContext(Extension->CurrentWriteRequest); + Stat->AmountInOutQueue -= reqContext->Length - (Extension->WriteLength); + + } + + Stat->WaitForImmediate = Extension->TransmitImmediate; + + Stat->HoldReasons = 0; + if (Extension->TXHolding) { + + if (Extension->TXHolding & SERIAL_TX_CTS) { + + Stat->HoldReasons |= SERIAL_TX_WAITING_FOR_CTS; + + } + + if (Extension->TXHolding & SERIAL_TX_DSR) { + + Stat->HoldReasons |= SERIAL_TX_WAITING_FOR_DSR; + + } + + if (Extension->TXHolding & SERIAL_TX_DCD) { + + Stat->HoldReasons |= SERIAL_TX_WAITING_FOR_DCD; + + } + + if (Extension->TXHolding & SERIAL_TX_XOFF) { + + Stat->HoldReasons |= SERIAL_TX_WAITING_FOR_XON; + + } + + if (Extension->TXHolding & SERIAL_TX_BREAK) { + + Stat->HoldReasons |= SERIAL_TX_WAITING_ON_BREAK; + + } + + } + + if (Extension->RXHolding & SERIAL_RX_DSR) { + + Stat->HoldReasons |= SERIAL_RX_WAITING_FOR_DSR; + + } + + if (Extension->RXHolding & SERIAL_RX_XOFF) { + + Stat->HoldReasons |= SERIAL_TX_WAITING_XOFF_SENT; + + } + + return FALSE; +} + + +BOOLEAN +SerialSetEscapeChar( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This is used to set the character that will be used to escape + line status and modem status information when the application + has set up that line status and modem status should be passed + back in the data stream. + +Arguments: + + Context - Pointer to the request that is specify the escape character. + Implicitly - An escape character of 0 means no escaping + will occur. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PREQUEST_CONTEXT reqContext = (PREQUEST_CONTEXT)Context; + PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension(WdfInterruptGetDevice(Interrupt)); + + UNREFERENCED_PARAMETER(Interrupt); + + extension->EscapeChar = *(PUCHAR)reqContext->SystemBuffer; + + return FALSE; +} + +VOID +SerialEvtIoDeviceControl( + IN WDFQUEUE Queue, + IN WDFREQUEST Request, + IN size_t OutputBufferLength, + IN size_t InputBufferLength, + IN ULONG IoControlCode + ) + +/*++ + +Routine Description: + + This routine provides the initial processing for all of the + Ioctrls for the serial device. + +Arguments: + + Request - Pointer to the WDFREQUEST for the current request + +Return Value: + + The function value is the final status of the call + +--*/ + +{ + // + // The status that gets returned to the caller and + // set in the Request. + // + NTSTATUS Status; + + // + // Just what it says. This is the serial specific device + // extension of the device object create for the serial driver. + // + PSERIAL_DEVICE_EXTENSION Extension = NULL; + + PVOID buffer; + PREQUEST_CONTEXT reqContext; + size_t bufSize; + + UNREFERENCED_PARAMETER(OutputBufferLength); + UNREFERENCED_PARAMETER(InputBufferLength); + + reqContext = SerialGetRequestContext(Request); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, "%s for: %p\n", + SerialGetIoctlName(IoControlCode), Request); + + Extension = SerialGetDeviceExtension(WdfIoQueueGetDevice(Queue)); + + // + // We expect to be open so all our pages are locked down. This is, after + // all, an IO operation, so the device should be open first. + // + + if (Extension->DeviceIsOpened != TRUE) { + SerialCompleteRequest(Request, STATUS_INVALID_DEVICE_REQUEST, 0); + return; + } + + + if (SerialCompleteIfError(Extension, Request) != STATUS_SUCCESS) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, + "Information = 0; + reqContext->Status = STATUS_SUCCESS; + reqContext->MajorFunction = IRP_MJ_DEVICE_CONTROL; + + + Status = STATUS_SUCCESS; + + switch (IoControlCode) { + + case IOCTL_SERIAL_SET_BAUD_RATE : { + + ULONG BaudRate; + // + // Will hold the value of the appropriate divisor for + // the requested baud rate. If the baudrate is invalid + // (because the device won't support that baud rate) then + // this value is undefined. + // + // Note: in one sense the concept of a valid baud rate + // is cloudy. We could allow the user to request any + // baud rate. We could then calculate the divisor needed + // for that baud rate. As long as the divisor wasn't less + // than one we would be "ok". (The percentage difference + // between the "true" divisor and the "rounded" value given + // to the hardware might make it unusable, but... ) It would + // really be up to the user to "Know" whether the baud rate + // is suitable. So much for theory, *We* only support a given + // set of baud rates. + // + SHORT AppropriateDivisor; + + Status = WdfRequestRetrieveInputBuffer (Request, sizeof(SERIAL_BAUD_RATE), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + BaudRate = ((PSERIAL_BAUD_RATE)(buffer))->BaudRate; + + + // + // Get the baud rate from the request. We pass it + // to a routine which will set the correct divisor. + // + + Status = SerialGetDivisorFromBaud( + Extension->ClockRate, + BaudRate, + &AppropriateDivisor + ); + + + if (NT_SUCCESS(Status)) { + + SERIAL_IOCTL_SYNC S; + + + Extension->CurrentBaud = BaudRate; + Extension->WmiCommData.BaudRate = BaudRate; + + S.Extension = Extension; + S.Data = (PVOID) (ULONG_PTR) AppropriateDivisor; + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialSetBaud, + &S + ); + + } + + break; + } + + case IOCTL_SERIAL_GET_BAUD_RATE: { + + PSERIAL_BAUD_RATE Br; + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_BAUD_RATE), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + Br = (PSERIAL_BAUD_RATE)buffer; + + Br->BaudRate = Extension->CurrentBaud; + + reqContext->Information = sizeof(SERIAL_BAUD_RATE); + + break; + + } + + case IOCTL_SERIAL_GET_MODEM_CONTROL: { + SERIAL_IOCTL_SYNC S; + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + reqContext->Information = sizeof(ULONG); + + S.Extension = Extension; + S.Data = buffer; + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialGetMCRContents, + &S + ); + + break; + } + case IOCTL_SERIAL_SET_MODEM_CONTROL: { + SERIAL_IOCTL_SYNC S; + + Status = WdfRequestRetrieveInputBuffer (Request, sizeof(ULONG), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + S.Extension = Extension; + S.Data = buffer; + + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialSetMCRContents, + &S + ); + + break; + } + case IOCTL_SERIAL_SET_FIFO_CONTROL: { + SERIAL_IOCTL_SYNC S; + + Status = WdfRequestRetrieveInputBuffer (Request, sizeof(ULONG), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + S.Extension = Extension; + S.Data = buffer; + + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialSetFCRContents, + &S + ); + + break; + } + case IOCTL_SERIAL_SET_LINE_CONTROL: { + + PSERIAL_LINE_CONTROL Lc; + UCHAR LData; + UCHAR LStop; + UCHAR LParity; + UCHAR Mask = 0xff; + + Status = WdfRequestRetrieveInputBuffer (Request, sizeof(SERIAL_LINE_CONTROL), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + // + // Points to the line control record in the Request. + // + Lc = (PSERIAL_LINE_CONTROL)buffer; + + switch (Lc->WordLength) { + case 5: { + + LData = SERIAL_5_DATA; + Mask = 0x1f; + break; + + } + case 6: { + + LData = SERIAL_6_DATA; + Mask = 0x3f; + break; + + } + case 7: { + + LData = SERIAL_7_DATA; + Mask = 0x7f; + break; + + } + case 8: { + + LData = SERIAL_8_DATA; + break; + + } + default: { + + Status = STATUS_INVALID_PARAMETER; + goto DoneWithIoctl; + + } + + } + + Extension->WmiCommData.BitsPerByte = Lc->WordLength; + + switch (Lc->Parity) { + + case NO_PARITY: { + Extension->WmiCommData.Parity = SERIAL_WMI_PARITY_NONE; + LParity = SERIAL_NONE_PARITY; + break; + + } + case EVEN_PARITY: { + Extension->WmiCommData.Parity = SERIAL_WMI_PARITY_EVEN; + LParity = SERIAL_EVEN_PARITY; + break; + + } + case ODD_PARITY: { + Extension->WmiCommData.Parity = SERIAL_WMI_PARITY_ODD; + LParity = SERIAL_ODD_PARITY; + break; + + } + case SPACE_PARITY: { + Extension->WmiCommData.Parity = SERIAL_WMI_PARITY_SPACE; + LParity = SERIAL_SPACE_PARITY; + break; + + } + case MARK_PARITY: { + Extension->WmiCommData.Parity = SERIAL_WMI_PARITY_MARK; + LParity = SERIAL_MARK_PARITY; + break; + + } + default: { + + Status = STATUS_INVALID_PARAMETER; + goto DoneWithIoctl; + break; + } + + } + + switch (Lc->StopBits) { + + case STOP_BIT_1: { + Extension->WmiCommData.StopBits = SERIAL_WMI_STOP_1; + LStop = SERIAL_1_STOP; + break; + } + case STOP_BITS_1_5: { + + if (LData != SERIAL_5_DATA) { + + Status = STATUS_INVALID_PARAMETER; + goto DoneWithIoctl; + } + Extension->WmiCommData.StopBits = SERIAL_WMI_STOP_1_5; + LStop = SERIAL_1_5_STOP; + break; + + } + case STOP_BITS_2: { + + if (LData == SERIAL_5_DATA) { + + Status = STATUS_INVALID_PARAMETER; + goto DoneWithIoctl; + } + Extension->WmiCommData.StopBits = SERIAL_WMI_STOP_2; + LStop = SERIAL_2_STOP; + break; + + } + default: { + + Status = STATUS_INVALID_PARAMETER; + goto DoneWithIoctl; + } + + } + + Extension->LineControl = + (UCHAR)((Extension->LineControl & SERIAL_LCR_BREAK) | + (LData | LParity | LStop)); + Extension->ValidDataMask = Mask; + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialSetLineControl, + Extension + ); + + break; + } + case IOCTL_SERIAL_GET_LINE_CONTROL: { + + PSERIAL_LINE_CONTROL Lc; + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_LINE_CONTROL), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + Lc = (PSERIAL_LINE_CONTROL)buffer; + + RtlZeroMemory(buffer, OutputBufferLength); + + if ((Extension->LineControl & SERIAL_DATA_MASK) == SERIAL_5_DATA) { + Lc->WordLength = 5; + } else if ((Extension->LineControl & SERIAL_DATA_MASK) + == SERIAL_6_DATA) { + Lc->WordLength = 6; + } else if ((Extension->LineControl & SERIAL_DATA_MASK) + == SERIAL_7_DATA) { + Lc->WordLength = 7; + } else if ((Extension->LineControl & SERIAL_DATA_MASK) + == SERIAL_8_DATA) { + Lc->WordLength = 8; + } + + if ((Extension->LineControl & SERIAL_PARITY_MASK) + == SERIAL_NONE_PARITY) { + Lc->Parity = NO_PARITY; + } else if ((Extension->LineControl & SERIAL_PARITY_MASK) + == SERIAL_ODD_PARITY) { + Lc->Parity = ODD_PARITY; + } else if ((Extension->LineControl & SERIAL_PARITY_MASK) + == SERIAL_EVEN_PARITY) { + Lc->Parity = EVEN_PARITY; + } else if ((Extension->LineControl & SERIAL_PARITY_MASK) + == SERIAL_MARK_PARITY) { + Lc->Parity = MARK_PARITY; + } else if ((Extension->LineControl & SERIAL_PARITY_MASK) + == SERIAL_SPACE_PARITY) { + Lc->Parity = SPACE_PARITY; + } + + if (Extension->LineControl & SERIAL_2_STOP) { + if (Lc->WordLength == 5) { + Lc->StopBits = STOP_BITS_1_5; + } else { + Lc->StopBits = STOP_BITS_2; + } + } else { + Lc->StopBits = STOP_BIT_1; + } + + reqContext->Information = sizeof(SERIAL_LINE_CONTROL); + + break; + } + case IOCTL_SERIAL_SET_TIMEOUTS: { + + PSERIAL_TIMEOUTS NewTimeouts; + + Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_TIMEOUTS), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + NewTimeouts =(PSERIAL_TIMEOUTS)buffer; + + if ((NewTimeouts->ReadIntervalTimeout == MAXULONG) && + (NewTimeouts->ReadTotalTimeoutMultiplier == MAXULONG) && + (NewTimeouts->ReadTotalTimeoutConstant == MAXULONG)) { + + Status = STATUS_INVALID_PARAMETER; + break; + + } + + + Extension->Timeouts.ReadIntervalTimeout = + NewTimeouts->ReadIntervalTimeout; + + Extension->Timeouts.ReadTotalTimeoutMultiplier = + NewTimeouts->ReadTotalTimeoutMultiplier; + + Extension->Timeouts.ReadTotalTimeoutConstant = + NewTimeouts->ReadTotalTimeoutConstant; + + Extension->Timeouts.WriteTotalTimeoutMultiplier = + NewTimeouts->WriteTotalTimeoutMultiplier; + + Extension->Timeouts.WriteTotalTimeoutConstant = + NewTimeouts->WriteTotalTimeoutConstant; + + break; + } + case IOCTL_SERIAL_GET_TIMEOUTS: { + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_TIMEOUTS), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + *((PSERIAL_TIMEOUTS)buffer) = Extension->Timeouts; + reqContext->Information = sizeof(SERIAL_TIMEOUTS); + + break; + } + case IOCTL_SERIAL_SET_CHARS: { + + SERIAL_IOCTL_SYNC S; + PSERIAL_CHARS NewChars; + + Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_CHARS), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + NewChars = (PSERIAL_CHARS)buffer; + + // + // The only thing that can be wrong with the chars + // is that the xon and xoff characters are the + // same. + // +#if 0 + if (NewChars->XonChar == NewChars->XoffChar) { + + Status = STATUS_INVALID_PARAMETER; + break; + + } +#endif + + // + // We acquire the control lock so that only + // one request can GET or SET the characters + // at a time. The sets could be synchronized + // by the interrupt spinlock, but that wouldn't + // prevent multiple gets at the same time. + // + + S.Extension = Extension; + S.Data = NewChars; + + // + // Under the protection of the lock, make sure that + // the xon and xoff characters aren't the same as + // the escape character. + // + + if (Extension->EscapeChar) { + + if ((Extension->EscapeChar == NewChars->XonChar) || + (Extension->EscapeChar == NewChars->XoffChar)) { + + Status = STATUS_INVALID_PARAMETER; + break; + + } + + } + + Extension->WmiCommData.XonCharacter = NewChars->XonChar; + Extension->WmiCommData.XoffCharacter = NewChars->XoffChar; + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialSetChars, + &S + ); + + + break; + + } + case IOCTL_SERIAL_GET_CHARS: { + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_CHARS), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + *((PSERIAL_CHARS)buffer) = Extension->SpecialChars; + reqContext->Information = sizeof(SERIAL_CHARS); + + + break; + } + case IOCTL_SERIAL_SET_DTR: + case IOCTL_SERIAL_CLR_DTR: { + + + // + // We acquire the lock so that we can check whether + // automatic dtr flow control is enabled. If it is + // then we return an error since the app is not allowed + // to touch this if it is automatic. + // + + if ((Extension->HandFlow.ControlHandShake & SERIAL_DTR_MASK) + == SERIAL_DTR_HANDSHAKE) { + + Status = STATUS_INVALID_PARAMETER; + + } else { + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + ((IoControlCode == + IOCTL_SERIAL_SET_DTR)? + (SerialSetDTR):(SerialClrDTR)), + Extension + ); + + } + + break; + } + case IOCTL_SERIAL_RESET_DEVICE: { + + break; + } + case IOCTL_SERIAL_SET_RTS: + case IOCTL_SERIAL_CLR_RTS: { + + // + // We acquire the lock so that we can check whether + // automatic rts flow control or transmit toggleing + // is enabled. If it is then we return an error since + // the app is not allowed to touch this if it is automatic + // or toggling. + // + + if (((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) + == SERIAL_RTS_HANDSHAKE) || + ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) + == SERIAL_TRANSMIT_TOGGLE)) { + + Status = STATUS_INVALID_PARAMETER; + + } else { + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + ((IoControlCode == + IOCTL_SERIAL_SET_RTS)? + (SerialSetRTS):(SerialClrRTS)), + Extension + ); + + } + + break; + + } + case IOCTL_SERIAL_SET_XOFF: { + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialPretendXoff, + Extension + ); + + break; + + } + case IOCTL_SERIAL_SET_XON: { + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialPretendXon, + Extension + ); + + break; + + } + case IOCTL_SERIAL_SET_BREAK_ON: { + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialTurnOnBreak, + Extension + ); + + break; + } + case IOCTL_SERIAL_SET_BREAK_OFF: { + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialTurnOffBreak, + Extension + ); + + break; + } + case IOCTL_SERIAL_SET_QUEUE_SIZE: { + + // + // Type ahead buffer is fixed, so we just validate + // the the users request is not bigger that our + // own internal buffer size. + // + + PSERIAL_QUEUE_SIZE Rs; + + Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_QUEUE_SIZE), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + ASSERT(Extension->InterruptReadBuffer); + + Rs = (PSERIAL_QUEUE_SIZE)buffer; + + reqContext->SystemBuffer = buffer; + + // + // We have to allocate the memory for the new + // buffer while we're still in the context of the + // caller. We don't even try to protect this + // with a lock because the value could be stale + // as soon as we release the lock - The only time + // we will know for sure is when we actually try + // to do the resize. + // + + if (Rs->InSize <= Extension->BufferSize) { + + Status = STATUS_SUCCESS; + break; + + } + + reqContext->Type3InputBuffer = + ExAllocatePoolWithQuotaTag( + NonPagedPoolNx | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE, + Rs->InSize, + POOL_TAG + ); + + if (!reqContext->Type3InputBuffer) { + + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + + } + + // + // Well the data passed was big enough. Do the request. + // + // There are two reason we place it in the read queue: + // + // 1) We want to serialize these resize requests so that + // they don't contend with each other. + // + // 2) We want to serialize these requests with reads since + // we don't want reads and resizes contending over the + // read buffer. + // + + + SerialStartOrQueue( + Extension, + Request, + Extension->ReadQueue, + &Extension->CurrentReadRequest, + SerialStartRead + ); + + return; + } + case IOCTL_SERIAL_GET_WAIT_MASK: { + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + // + // Simple scalar read. No reason to acquire a lock. + // + + reqContext->Information = sizeof(ULONG); + + *((ULONG *)buffer) = Extension->IsrWaitMask; + + break; + + } + case IOCTL_SERIAL_SET_WAIT_MASK: { + + ULONG NewMask; + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "In Ioctl processing for set mask\n"); + + Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + NewMask = *((ULONG *)buffer); + reqContext->SystemBuffer = buffer; + + // + // Make sure that the mask only contains valid + // waitable events. + // + + if (NewMask & ~(SERIAL_EV_RXCHAR | + SERIAL_EV_RXFLAG | + SERIAL_EV_TXEMPTY | + SERIAL_EV_CTS | + SERIAL_EV_DSR | + SERIAL_EV_RLSD | + SERIAL_EV_BREAK | + SERIAL_EV_ERR | + SERIAL_EV_RING | + SERIAL_EV_PERR | + SERIAL_EV_RX80FULL | + SERIAL_EV_EVENT1 | + SERIAL_EV_EVENT2)) { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Unknown mask %x\n", NewMask); + + Status = STATUS_INVALID_PARAMETER; + break; + + } + + // + // Either start this request or put it on the + // queue. + // + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Starting or queuing set mask request %p" + "\n", Request); + + SerialStartOrQueue(Extension, Request, Extension->MaskQueue, + &Extension->CurrentMaskRequest, + SerialStartMask); + return; + + } + case IOCTL_SERIAL_WAIT_ON_MASK: { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "In Ioctl processing for wait mask\n"); + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + reqContext->SystemBuffer = buffer; + + // + // Either start this request or put it on the + // queue. + // + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Starting or queuing wait mask request" + "%p\n", Request); + + SerialStartOrQueue( + Extension, + Request, + Extension->MaskQueue, + &Extension->CurrentMaskRequest, + SerialStartMask + ); + return; + } + case IOCTL_SERIAL_IMMEDIATE_CHAR: { + + Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(UCHAR), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + reqContext->SystemBuffer = buffer; + + if (Extension->CurrentImmediateRequest) { + + Status = STATUS_INVALID_PARAMETER; + + } else { + + // + // We can queue the char. We need to set + // a cancel routine because flow control could + // keep the char from transmitting. Make sure + // that the request hasn't already been canceled. + // + + Extension->CurrentImmediateRequest = Request; + Extension->TotalCharsQueued++; + SerialStartImmediate(Extension); + return; + + } + + break; + + } + case IOCTL_SERIAL_PURGE: { + + ULONG Mask; + + Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + // + // Check to make sure that the mask only has + // 0 or the other appropriate values. + // + + Mask = *((ULONG *)(buffer)); + + if ((!Mask) || (Mask & (~(SERIAL_PURGE_TXABORT | + SERIAL_PURGE_RXABORT | + SERIAL_PURGE_TXCLEAR | + SERIAL_PURGE_RXCLEAR + ) + ) + )) { + + Status = STATUS_INVALID_PARAMETER; + break; + + } + + reqContext->SystemBuffer = buffer; + + // + // Either start this request or put it on the + // queue. + // + + SerialStartOrQueue( + Extension, + Request, + Extension->PurgeQueue, + &Extension->CurrentPurgeRequest, + SerialStartPurge + ); + return; + } + case IOCTL_SERIAL_GET_HANDFLOW: { + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_HANDFLOW), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + reqContext->Information = sizeof(SERIAL_HANDFLOW); + + *((PSERIAL_HANDFLOW)buffer) = Extension->HandFlow; + + break; + + } + case IOCTL_SERIAL_SET_HANDFLOW: { + + SERIAL_IOCTL_SYNC S; + PSERIAL_HANDFLOW HandFlow; + + // + // Make sure that the hand shake and control is the + // right size. + // + + Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_HANDFLOW), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + HandFlow = (PSERIAL_HANDFLOW)buffer; + + // + // Make sure that there are no invalid bits set in + // the control and handshake. + // + + if (HandFlow->ControlHandShake & SERIAL_CONTROL_INVALID) { + + Status = STATUS_INVALID_PARAMETER; + break; + + } + + if (HandFlow->FlowReplace & SERIAL_FLOW_INVALID) { + + Status = STATUS_INVALID_PARAMETER; + break; + + } + + // + // Make sure that the app hasn't set an invlid DTR mode. + // + + if ((HandFlow->ControlHandShake & SERIAL_DTR_MASK) == + SERIAL_DTR_MASK) { + + Status = STATUS_INVALID_PARAMETER; + break; + + } + + // + // Make sure that haven't set totally invalid xon/xoff + // limits. + // + + if ((HandFlow->XonLimit < 0) || + ((ULONG)HandFlow->XonLimit > Extension->BufferSize)) { + + Status = STATUS_INVALID_PARAMETER; + break; + + } + + if ((HandFlow->XoffLimit < 0) || + ((ULONG)HandFlow->XoffLimit > Extension->BufferSize)) { + + Status = STATUS_INVALID_PARAMETER; + break; + + } + + S.Extension = Extension; + S.Data = HandFlow; + + // + // Under the protection of the lock, make sure that + // we aren't turning on error replacement when we + // are doing line status/modem status insertion. + // + + if (Extension->EscapeChar) { + + if (HandFlow->FlowReplace & SERIAL_ERROR_CHAR) { + + Status = STATUS_INVALID_PARAMETER; + break; + + } + + } + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialSetHandFlow, + &S + ); + + break; + + } + case IOCTL_SERIAL_GET_MODEMSTATUS: { + + SERIAL_IOCTL_SYNC S; + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + reqContext->Information = sizeof(ULONG); + + S.Extension = Extension; + S.Data = buffer; + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialGetModemUpdate, + &S + ); + + break; + + } + case IOCTL_SERIAL_GET_DTRRTS: { + + ULONG ModemControl; + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + reqContext->Information = sizeof(ULONG); + reqContext->Status = STATUS_SUCCESS; + + // + // Reading this hardware has no effect on the device. + // + + ModemControl = READ_MODEM_CONTROL(Extension, Extension->Controller); + + ModemControl &= SERIAL_DTR_STATE | SERIAL_RTS_STATE; + + *(PULONG)buffer = ModemControl; + + break; + + } + case IOCTL_SERIAL_GET_COMMSTATUS: { + + SERIAL_IOCTL_SYNC S; + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_STATUS), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + reqContext->Information = sizeof(SERIAL_STATUS); + + S.Extension = Extension; + S.Data = buffer; + + // + // Acquire the cancel spin lock so nothing much + // changes while were getting the state. + // + + //IoAcquireCancelSpinLock(&OldIrql); + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialGetCommStatus, + &S + ); + + //IoReleaseCancelSpinLock(OldIrql); + + break; + + } + case IOCTL_SERIAL_GET_PROPERTIES: { + + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_COMMPROP), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + // + // No synchronization is required since this information + // is "static". + // + + SerialGetProperties( + Extension, + buffer + ); + + reqContext->Information = sizeof(SERIAL_COMMPROP); + reqContext->Status = STATUS_SUCCESS; + + break; + } + case IOCTL_SERIAL_XOFF_COUNTER: { + + PSERIAL_XOFF_COUNTER Xc; + + Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_XOFF_COUNTER), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + Xc = (PSERIAL_XOFF_COUNTER)buffer; + + if (Xc->Counter <= 0) { + + Status = STATUS_INVALID_PARAMETER; + break; + + } + reqContext->SystemBuffer = buffer; + + // + // There is no output, so make that clear now + // + + reqContext->Information = 0; + + // + // So far so good. Put the request onto the write queue. + // + + SerialStartOrQueue( + Extension, + Request, + Extension->WriteQueue, + &Extension->CurrentWriteRequest, + SerialStartWrite + ); + return; + + } + case IOCTL_SERIAL_LSRMST_INSERT: { + + PUCHAR escapeChar; + SERIAL_IOCTL_SYNC S; + + // + // Make sure we get a byte. + // + Status = WdfRequestRetrieveInputBuffer ( Request, sizeof(UCHAR), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + reqContext->SystemBuffer = buffer; + + escapeChar = (PUCHAR)buffer; + + if (*escapeChar) { + + // + // We've got some escape work to do. We will make sure that + // the character is not the same as the Xon or Xoff character, + // or that we are already doing error replacement. + // + + if ((*escapeChar == Extension->SpecialChars.XoffChar) || + (*escapeChar == Extension->SpecialChars.XonChar) || + (Extension->HandFlow.FlowReplace & SERIAL_ERROR_CHAR)) { + + Status = STATUS_INVALID_PARAMETER; + + break; + + } + + } + + S.Extension = Extension; + S.Data = buffer; + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialSetEscapeChar, + reqContext + ); + + break; + + } + case IOCTL_SERIAL_CONFIG_SIZE: { + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(ULONG), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + reqContext->Information = sizeof(ULONG); + reqContext->Status = STATUS_SUCCESS; + + *(PULONG)buffer = 0; + + break; + } + case IOCTL_SERIAL_GET_STATS: { + + Status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIALPERF_STATS), &buffer, &bufSize ); + if( !NT_SUCCESS(Status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", Status); + break; + } + + reqContext->SystemBuffer = buffer; + + reqContext->Information = sizeof(SERIALPERF_STATS); + reqContext->Status = STATUS_SUCCESS; + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialGetStats, + reqContext + ); + + break; + } + case IOCTL_SERIAL_CLEAR_STATS: { + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialClearStats, + Extension + ); + break; + } + default: { + + Status = STATUS_INVALID_PARAMETER; + break; + } + } + +DoneWithIoctl:; + + reqContext->Status = Status; + + SerialCompleteRequest(Request, Status, reqContext->Information); + + return; + +} + + +VOID +SerialGetProperties( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN PSERIAL_COMMPROP Properties + ) + +/*++ + +Routine Description: + + This function returns the capabilities of this particular + serial device. + +Arguments: + + Extension - The serial device extension. + + Properties - The structure used to return the properties + +Return Value: + + None. + +--*/ + +{ + + + RtlZeroMemory( + Properties, + sizeof(SERIAL_COMMPROP) + ); + + Properties->PacketLength = sizeof(SERIAL_COMMPROP); + Properties->PacketVersion = 2; + Properties->ServiceMask = SERIAL_SP_SERIALCOMM; + Properties->MaxTxQueue = 0; + Properties->MaxRxQueue = 0; + + Properties->MaxBaud = SERIAL_BAUD_USER; + Properties->SettableBaud = Extension->SupportedBauds; + + Properties->ProvSubType = SERIAL_SP_RS232; + Properties->ProvCapabilities = SERIAL_PCF_DTRDSR | + SERIAL_PCF_RTSCTS | + SERIAL_PCF_CD | + SERIAL_PCF_PARITY_CHECK | + SERIAL_PCF_XONXOFF | + SERIAL_PCF_SETXCHAR | + SERIAL_PCF_TOTALTIMEOUTS | + SERIAL_PCF_INTTIMEOUTS; + Properties->SettableParams = SERIAL_SP_PARITY | + SERIAL_SP_BAUD | + SERIAL_SP_DATABITS | + SERIAL_SP_STOPBITS | + SERIAL_SP_HANDSHAKING | + SERIAL_SP_PARITY_CHECK | + SERIAL_SP_CARRIER_DETECT; + + + Properties->SettableData = SERIAL_DATABITS_5 | + SERIAL_DATABITS_6 | + SERIAL_DATABITS_7 | + SERIAL_DATABITS_8; + Properties->SettableStopParity = SERIAL_STOPBITS_10 | + SERIAL_STOPBITS_15 | + SERIAL_STOPBITS_20 | + SERIAL_PARITY_NONE | + SERIAL_PARITY_ODD | + SERIAL_PARITY_EVEN | + SERIAL_PARITY_MARK | + SERIAL_PARITY_SPACE; + Properties->CurrentTxQueue = 0; + Properties->CurrentRxQueue = Extension->BufferSize; + +} + +VOID +SerialEvtIoInternalDeviceControl( + IN WDFQUEUE Queue, + IN WDFREQUEST Request, + IN size_t OutputBufferLength, + IN size_t InputBufferLength, + IN ULONG IoControlCode +) +/*++ + +Routine Description: + + This routine provides the initial processing for all of the + internal Ioctrls for the serial device. + +Arguments: + + PDevObj - Pointer to the device object for this device + + PIrp - Pointer to the WDFREQUEST for the current request + +Return Value: + + The function value is the final status of the call + +--*/ + +{ + NTSTATUS status; + PSERIAL_DEVICE_EXTENSION pDevExt = NULL; + PVOID buffer; + PREQUEST_CONTEXT reqContext; + WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS wakeSettings; + size_t bufSize; + + UNREFERENCED_PARAMETER(OutputBufferLength); + UNREFERENCED_PARAMETER(InputBufferLength); + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "SerialEvtIoInternalDeviceControl for: %p\n", Request); + + pDevExt = SerialGetDeviceExtension(WdfIoQueueGetDevice(Queue)); + + if (SerialCompleteIfError(pDevExt, Request) != STATUS_SUCCESS) { + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "Information = 0; + reqContext->Status = STATUS_SUCCESS; + reqContext->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + + switch (IoControlCode) { + + case IOCTL_SERIAL_INTERNAL_DO_WAIT_WAKE: + // + // Init wait-wake policy structure. + // + WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS_INIT(&wakeSettings); + // + // Override the default settings from allow user control to do not allow. + // + wakeSettings.UserControlOfWakeSettings = IdleDoNotAllowUserControl; + status = WdfDeviceAssignSxWakeSettings(pDevExt->WdfDevice, &wakeSettings); + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDeviceAssignSxWakeSettings failed %x \n", status); + break; + } + + pDevExt->IsWakeEnabled = TRUE; + status = STATUS_SUCCESS; + break; + + case IOCTL_SERIAL_INTERNAL_CANCEL_WAIT_WAKE: + + WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS_INIT(&wakeSettings); + // + // Override the default settings. + // + wakeSettings.Enabled = WdfFalse; // Disables wait-wake + wakeSettings.UserControlOfWakeSettings = IdleDoNotAllowUserControl; + status = WdfDeviceAssignSxWakeSettings(pDevExt->WdfDevice, &wakeSettings); + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDeviceAssignSxWakeSettings failed %x \n", status); + break; + } + + pDevExt->IsWakeEnabled = FALSE; + status = STATUS_SUCCESS; + break; + + + // + // Put the serial port in a "filter-driver" appropriate state + // + // WARNING: This code assumes it is being called by a trusted kernel + // entity and no checking is done on the validity of the settings + // passed to IOCTL_SERIAL_INTERNAL_RESTORE_SETTINGS + // + // If validity checking is desired, the regular ioctl's should be used + // + + case IOCTL_SERIAL_INTERNAL_BASIC_SETTINGS: + case IOCTL_SERIAL_INTERNAL_RESTORE_SETTINGS: { + + SERIAL_BASIC_SETTINGS basic; + PSERIAL_BASIC_SETTINGS pBasic; + SERIAL_IOCTL_SYNC S; + + if (IoControlCode == IOCTL_SERIAL_INTERNAL_BASIC_SETTINGS) { + + + // + // Check the buffer size + // + status = WdfRequestRetrieveOutputBuffer ( Request, sizeof(SERIAL_BASIC_SETTINGS), &buffer, &bufSize ); + if( !NT_SUCCESS(status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", status); + break; + } + + reqContext->SystemBuffer = buffer; + + // + // Everything is 0 -- timeouts and flow control and fifos. If + // We add additional features, this zero memory method + // may not work. + // + + RtlZeroMemory(&basic, sizeof(SERIAL_BASIC_SETTINGS)); + + basic.TxFifo = 1; + basic.RxFifo = SERIAL_1_BYTE_HIGH_WATER; + + reqContext->Information = sizeof(SERIAL_BASIC_SETTINGS); + pBasic = (PSERIAL_BASIC_SETTINGS)buffer; + + // + // Save off the old settings + // + + RtlCopyMemory(&pBasic->Timeouts, &pDevExt->Timeouts, + sizeof(SERIAL_TIMEOUTS)); + + RtlCopyMemory(&pBasic->HandFlow, &pDevExt->HandFlow, + sizeof(SERIAL_HANDFLOW)); + + pBasic->RxFifo = pDevExt->RxFifoTrigger; + pBasic->TxFifo = pDevExt->TxFifoAmount; + + // + // Point to our new settings + // + + pBasic = &basic; + } else { // restoring settings + + status = WdfRequestRetrieveInputBuffer ( Request, sizeof(SERIAL_BASIC_SETTINGS), &buffer, &bufSize ); + if( !NT_SUCCESS(status) ) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Could not get request memory buffer %X\n", status); + break; + } + + pBasic = (PSERIAL_BASIC_SETTINGS)buffer; + } + + // + // Set the timeouts + // + + RtlCopyMemory(&pDevExt->Timeouts, &pBasic->Timeouts, + sizeof(SERIAL_TIMEOUTS)); + + // + // Set flowcontrol + // + + S.Extension = pDevExt; + S.Data = &pBasic->HandFlow; + WdfInterruptSynchronize(pDevExt->WdfInterrupt, SerialSetHandFlow, &S); + + if (pDevExt->FifoPresent) { + pDevExt->TxFifoAmount = pBasic->TxFifo; + pDevExt->RxFifoTrigger = (UCHAR)pBasic->RxFifo; + + WRITE_FIFO_CONTROL(pDevExt, pDevExt->Controller, (UCHAR)0); + READ_RECEIVE_BUFFER(pDevExt, pDevExt->Controller); + WRITE_FIFO_CONTROL(pDevExt, pDevExt->Controller, + (UCHAR)(SERIAL_FCR_ENABLE | pDevExt->RxFifoTrigger + | SERIAL_FCR_RCVR_RESET + | SERIAL_FCR_TXMT_RESET)); + } else { + pDevExt->TxFifoAmount = pDevExt->RxFifoTrigger = 0; + WRITE_FIFO_CONTROL(pDevExt, pDevExt->Controller, (UCHAR)0); + } + + + break; + } + + default: + status = STATUS_INVALID_PARAMETER; + break; + + } + + reqContext->Status = status; + + SerialCompleteRequest(Request, reqContext->Status, reqContext->Information); + + return; +} + + + diff --git a/tests/projects/wdk/kmdf/serial/isr.c b/tests/projects/wdk/kmdf/serial/isr.c new file mode 100644 index 000000000..806164a61 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/isr.c @@ -0,0 +1,1517 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + isr.c + +Abstract: + + This module contains the interrupt service routine for the + serial driver. + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "isr.tmh" +#endif + + +NTSTATUS +SerialEvtInterruptEnable( + IN WDFINTERRUPT Interrupt, + IN WDFDEVICE AssociatedDevice + ) +/*++ + +Routine Description: + + This event is called when the Framework moves the device to D0, and after + EvtDeviceD0Entry. The driver should enable its interrupt here. + + This function will be called at the device's assigned interrupt + IRQL (DIRQL.) + +Arguments: + + Interrupt - Handle to a Framework interrupt object. + + AssociatedDevice - Handle to a Framework device object. + +Return Value: + + BOOLEAN - TRUE indicates that the interrupt was successfully enabled. + +--*/ +{ + UNREFERENCED_PARAMETER(Interrupt); + UNREFERENCED_PARAMETER(AssociatedDevice); + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "--> SerialEvtInterruptEnable\n"); + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "<-- SerialEvtInterruptEnable\n"); + + return STATUS_SUCCESS; +} + +NTSTATUS +SerialEvtInterruptDisable( + IN WDFINTERRUPT Interrupt, + IN WDFDEVICE AssociatedDevice + ) +/*++ + +Routine Description: + + This event is called before the Framework moves the device to D1, D2 or D3 + and before EvtDeviceD0Exit. The driver should disable its interrupt here. + + This function will be called at the device's assigned interrupt + IRQL (DIRQL.) + +Arguments: + + Interrupt - Handle to a Framework interrupt object. + + AssociatedDevice - Handle to a Framework device object. + +Return Value: + + BOOLEAN - TRUE indicates that the interrupt was successfully disabled. + +--*/ +{ + UNREFERENCED_PARAMETER(Interrupt); + UNREFERENCED_PARAMETER(AssociatedDevice); + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "--> SerialEvtInterruptDisable\n"); + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "<-- SerialEvtInterruptDisable\n"); + + return STATUS_SUCCESS; +} + +BOOLEAN +SerialISR( + IN WDFINTERRUPT Interrupt, + IN ULONG MessageID + ) + +/*++ + +Routine Description: + + This is the interrupt service routine for the serial port driver. + It will determine whether the serial port is the source of this + interrupt. If it is, then this routine will do the minimum of + processing to quiet the interrupt. It will store any information + necessary for later processing. + +Arguments: + + InterruptObject - Points to the interrupt object declared for this + device. We *do not* use this parameter. + + +Return Value: + + This function will return TRUE if the serial port is the source + of this interrupt, FALSE otherwise. + +--*/ + +{ + // + // Holds the information specific to handling this device. + // + PSERIAL_DEVICE_EXTENSION Extension = NULL; + + // + // Holds the contents of the interrupt identification record. + // A low bit of zero in this register indicates that there is + // an interrupt pending on this device. + // + UCHAR InterruptIdReg; + + // + // Will hold whether we've serviced any interrupt causes in this + // routine. + // + BOOLEAN ServicedAnInterrupt; + + UCHAR tempLSR; + PREQUEST_CONTEXT reqContext = NULL; + + UNREFERENCED_PARAMETER(MessageID); + + Extension = SerialGetDeviceExtension(WdfInterruptGetDevice(Interrupt)); + + // + // Make sure we have an interrupt pending. If we do then + // we need to make sure that the device is open. If the + // device isn't open or powered down then quiet the device. Note that + // if the device isn't opened when we enter this routine + // it can't open while we're in it. + // + + InterruptIdReg = READ_INTERRUPT_ID_REG(Extension, Extension->Controller); + + if ((InterruptIdReg & SERIAL_IIR_NO_INTERRUPT_PENDING)) { + + ServicedAnInterrupt = FALSE; + + } else if (!Extension->DeviceIsOpened/* + || (Extension->PowerState != PowerDeviceD0)*/) { + + + // + // We got an interrupt with the device being closed or when the + // device is supposed to be powered down. This + // is not unlikely with a serial device. We just quietly + // keep servicing the causes until it calms down. + // + + ServicedAnInterrupt = TRUE; + do { + + InterruptIdReg &= (~SERIAL_IIR_FIFOS_ENABLED); + switch (InterruptIdReg) { + + case SERIAL_IIR_RLS: { + + READ_LINE_STATUS(Extension, Extension->Controller); + + break; + + } + + case SERIAL_IIR_RDA: + case SERIAL_IIR_CTI: { + + READ_RECEIVE_BUFFER(Extension, Extension->Controller); + + break; + + } + + case SERIAL_IIR_THR: { + + // + // Alread clear from reading the iir. + // + // We want to keep close track of whether + // the holding register is empty. + // + + Extension->HoldingEmpty = TRUE; + break; + + } + + case SERIAL_IIR_MS: { + + READ_MODEM_STATUS(Extension, Extension->Controller); + break; + + } + + default: { + + ASSERT(FALSE); + break; + + } + + } + + } while (!((InterruptIdReg = + READ_INTERRUPT_ID_REG(Extension, Extension->Controller)) + & SERIAL_IIR_NO_INTERRUPT_PENDING)); + + } else { + + ServicedAnInterrupt = TRUE; + do { + + // + // We only care about bits that can denote an interrupt. + // + + InterruptIdReg &= SERIAL_IIR_RLS | SERIAL_IIR_RDA | + SERIAL_IIR_CTI | SERIAL_IIR_THR | + SERIAL_IIR_MS; + + // + // We have an interrupt. We look for interrupt causes + // in priority order. The presence of a higher interrupt + // will mask out causes of a lower priority. When we service + // and quiet a higher priority interrupt we then need to check + // the interrupt causes to see if a new interrupt cause is + // present. + // + + switch (InterruptIdReg) { + + case SERIAL_IIR_RLS: { + + SerialProcessLSR(Extension); + + break; + + } + + case SERIAL_IIR_RDA: + case SERIAL_IIR_CTI: + + { + + // + // Reading the receive buffer will quiet this interrupt. + // + // It may also reveal a new interrupt cause. + // + UCHAR ReceivedChar; + + do { + + ReceivedChar = + READ_RECEIVE_BUFFER(Extension, Extension->Controller); + Extension->PerfStats.ReceivedCount++; + Extension->WmiPerfData.ReceivedCount++; + + ReceivedChar &= Extension->ValidDataMask; + + if (!ReceivedChar && + (Extension->HandFlow.FlowReplace & + SERIAL_NULL_STRIPPING)) { + + // + // If what we got is a null character + // and we're doing null stripping, then + // we simply act as if we didn't see it. + // + + goto ReceiveDoLineStatus; + + } + + if ((Extension->HandFlow.FlowReplace & + SERIAL_AUTO_TRANSMIT) && + ((ReceivedChar == + Extension->SpecialChars.XonChar) || + (ReceivedChar == + Extension->SpecialChars.XoffChar))) { + + // + // No matter what happens this character + // will never get seen by the app. + // + + if (ReceivedChar == + Extension->SpecialChars.XoffChar) { + + Extension->TXHolding |= SERIAL_TX_XOFF; + + if ((Extension->HandFlow.FlowReplace & + SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE) { + + SerialInsertQueueDpc( + Extension->StartTimerLowerRTSDpc + )?Extension->CountOfTryingToLowerRTS++:0; + + } + + + } else { + + if (Extension->TXHolding & SERIAL_TX_XOFF) { + + // + // We got the xon char **AND*** we + // were being held up on transmission + // by xoff. Clear that we are holding + // due to xoff. Transmission will + // automatically restart because of + // the code outside the main loop that + // catches problems chips like the + // SMC and the Winbond. + // + + Extension->TXHolding &= ~SERIAL_TX_XOFF; + + } + + } + + goto ReceiveDoLineStatus; + + } + + // + // Check to see if we should note + // the receive character or special + // character event. + // + + if (Extension->IsrWaitMask) { + + if (Extension->IsrWaitMask & + SERIAL_EV_RXCHAR) { + + Extension->HistoryMask |= SERIAL_EV_RXCHAR; + + } + + if ((Extension->IsrWaitMask & + SERIAL_EV_RXFLAG) && + (Extension->SpecialChars.EventChar == + ReceivedChar)) { + + Extension->HistoryMask |= SERIAL_EV_RXFLAG; + + } + + if (Extension->IrpMaskLocation && + Extension->HistoryMask) { + + *Extension->IrpMaskLocation = + Extension->HistoryMask; + Extension->IrpMaskLocation = NULL; + Extension->HistoryMask = 0; + reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest); + reqContext->Information = sizeof(ULONG); + SerialInsertQueueDpc( + Extension->CommWaitDpc + ); + + } + + } + + SerialPutChar( + Extension, + ReceivedChar + ); + + // + // If we're doing line status and modem + // status insertion then we need to insert + // a zero following the character we just + // placed into the buffer to mark that this + // was reception of what we are using to + // escape. + // + + if (Extension->EscapeChar && + (Extension->EscapeChar == + ReceivedChar)) { + + SerialPutChar( + Extension, + SERIAL_LSRMST_ESCAPE + ); + + } + + +ReceiveDoLineStatus: ; + // + // This reads the interrupt ID register and detemines if bits are 0 + // If either of the reserved bits are 1, we stop servicing interrupts + // Since this detection method is not guarenteed this is enabled via + // a registry entry "UartDetectRemoval" and intialized on DriverEntry. + // This is disabled by default and will only be enabled on Stratus systems + // that allow hot replacement of serial cards + // + if(Extension->UartRemovalDetect) + { + UCHAR DetectRemoval; + + DetectRemoval = READ_INTERRUPT_ID_REG(Extension, Extension->Controller); + + if(DetectRemoval & SERIAL_IIR_MUST_BE_ZERO) + { + // break out of this loop and stop processing interrupts + break; + } + } + + if (!((tempLSR = SerialProcessLSR(Extension)) & + SERIAL_LSR_DR)) { + + // + // No more characters, get out of the + // loop. + // + + break; + + } + + if ((tempLSR & ~(SERIAL_LSR_THRE | SERIAL_LSR_TEMT | + SERIAL_LSR_DR)) && + Extension->EscapeChar) { + + // + // An error was indicated and inserted into the + // stream, get out of the loop. + // + + break; + } + + } WHILE (TRUE); + + break; + + } + + case SERIAL_IIR_THR: { + +doTrasmitStuff:; + Extension->HoldingEmpty = TRUE; + + if (Extension->WriteLength || + Extension->TransmitImmediate || + Extension->SendXoffChar || + Extension->SendXonChar) { + + // + // Even though all of the characters being + // sent haven't all been sent, this variable + // will be checked when the transmit queue is + // empty. If it is still true and there is a + // wait on the transmit queue being empty then + // we know we finished transmitting all characters + // following the initiation of the wait since + // the code that initiates the wait will set + // this variable to false. + // + // One reason it could be false is that + // the writes were cancelled before they + // actually started, or that the writes + // failed due to timeouts. This variable + // basically says a character was written + // by the isr at some point following the + // initiation of the wait. + // + + Extension->EmptiedTransmit = TRUE; + + // + // If we have output flow control based on + // the modem status lines, then we have to do + // all the modem work before we output each + // character. (Otherwise we might miss a + // status line change.) + // + + if (Extension->HandFlow.ControlHandShake & + SERIAL_OUT_HANDSHAKEMASK) { + + SerialHandleModemUpdate( + Extension, + TRUE + ); + + } + + // + // We can only send the xon character if + // the only reason we are holding is because + // of the xoff. (Hardware flow control or + // sending break preclude putting a new character + // on the wire.) + // + + if (Extension->SendXonChar && + !(Extension->TXHolding & ~SERIAL_TX_XOFF)) { + + if ((Extension->HandFlow.FlowReplace & + SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE) { + + // + // We have to raise if we're sending + // this character. + // + + SerialSetRTS(Extension->WdfInterrupt, Extension); + + Extension->PerfStats.TransmittedCount++; + Extension->WmiPerfData.TransmittedCount++; + + WRITE_TRANSMIT_HOLDING(Extension, Extension->Controller, + Extension->SpecialChars.XonChar); + SerialInsertQueueDpc( + Extension->StartTimerLowerRTSDpc + )?Extension->CountOfTryingToLowerRTS++:0; + + + } else { + + Extension->PerfStats.TransmittedCount++; + Extension->WmiPerfData.TransmittedCount++; + + WRITE_TRANSMIT_HOLDING(Extension, + Extension->Controller, + Extension->SpecialChars.XonChar); + } + + + Extension->SendXonChar = FALSE; + Extension->HoldingEmpty = FALSE; + + // + // If we send an xon, by definition we + // can't be holding by Xoff. + // + + Extension->TXHolding &= ~SERIAL_TX_XOFF; + + // + // If we are sending an xon char then + // by definition we can't be "holding" + // up reception by Xoff. + // + + Extension->RXHolding &= ~SERIAL_RX_XOFF; + + } else if (Extension->SendXoffChar && + !Extension->TXHolding) { + + if ((Extension->HandFlow.FlowReplace & + SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE) { + + // + // We have to raise if we're sending + // this character. + // + + SerialSetRTS(Extension->WdfInterrupt, Extension); + + Extension->PerfStats.TransmittedCount++; + Extension->WmiPerfData.TransmittedCount++; + WRITE_TRANSMIT_HOLDING(Extension, + Extension->Controller, + Extension->SpecialChars.XoffChar); + + SerialInsertQueueDpc( + Extension->StartTimerLowerRTSDpc + )?Extension->CountOfTryingToLowerRTS++:0; + + } else { + + Extension->PerfStats.TransmittedCount++; + Extension->WmiPerfData.TransmittedCount++; + WRITE_TRANSMIT_HOLDING(Extension, + Extension->Controller, + Extension->SpecialChars.XoffChar); + + } + + // + // We can't be sending an Xoff character + // if the transmission is already held + // up because of Xoff. Therefore, if we + // are holding then we can't send the char. + // + + // + // If the application has set xoff continue + // mode then we don't actually stop sending + // characters if we send an xoff to the other + // side. + // + + if (!(Extension->HandFlow.FlowReplace & + SERIAL_XOFF_CONTINUE)) { + + Extension->TXHolding |= SERIAL_TX_XOFF; + + if ((Extension->HandFlow.FlowReplace & + SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE) { + + SerialInsertQueueDpc( + Extension->StartTimerLowerRTSDpc + )?Extension->CountOfTryingToLowerRTS++:0; + + } + + } + + Extension->SendXoffChar = FALSE; + Extension->HoldingEmpty = FALSE; + + // + // Even if transmission is being held + // up, we should still transmit an immediate + // character if all that is holding us + // up is xon/xoff (OS/2 rules). + // + + } else if (Extension->TransmitImmediate && + (!Extension->TXHolding || + (Extension->TXHolding == SERIAL_TX_XOFF) + )) { + + Extension->TransmitImmediate = FALSE; + + if ((Extension->HandFlow.FlowReplace & + SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE) { + + // + // We have to raise if we're sending + // this character. + // + + SerialSetRTS(Extension->WdfInterrupt, Extension); + + Extension->PerfStats.TransmittedCount++; + Extension->WmiPerfData.TransmittedCount++; + WRITE_TRANSMIT_HOLDING(Extension, + Extension->Controller, + Extension->ImmediateChar); + + SerialInsertQueueDpc( + Extension->StartTimerLowerRTSDpc + )?Extension->CountOfTryingToLowerRTS++:0; + + } else { + + Extension->PerfStats.TransmittedCount++; + Extension->WmiPerfData.TransmittedCount++; + WRITE_TRANSMIT_HOLDING(Extension, + Extension->Controller, + Extension->ImmediateChar); + + } + + Extension->HoldingEmpty = FALSE; + + SerialInsertQueueDpc( + Extension->CompleteImmediateDpc + ); + + } else if (!Extension->TXHolding) { + + ULONG amountToWrite; + + if (Extension->FifoPresent) { + + amountToWrite = (Extension->TxFifoAmount < + Extension->WriteLength)? + Extension->TxFifoAmount: + Extension->WriteLength; + + } else { + + amountToWrite = 1; + + } + if ((Extension->HandFlow.FlowReplace & + SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE) { + + // + // We have to raise if we're sending + // this character. + // + + SerialSetRTS(Extension->WdfInterrupt, Extension); + + if (amountToWrite == 1) { + + Extension->PerfStats.TransmittedCount++; + Extension->WmiPerfData.TransmittedCount++; + WRITE_TRANSMIT_HOLDING(Extension, + Extension->Controller, + *(Extension->WriteCurrentChar)); + + } else { + + Extension->PerfStats.TransmittedCount += + amountToWrite; + Extension->WmiPerfData.TransmittedCount += + amountToWrite; + WRITE_TRANSMIT_FIFO_HOLDING(Extension, + Extension->Controller, + Extension->WriteCurrentChar, + amountToWrite); + } + + SerialInsertQueueDpc( + Extension->StartTimerLowerRTSDpc + )?Extension->CountOfTryingToLowerRTS++:0; + + } else { + + if (amountToWrite == 1) { + + Extension->PerfStats.TransmittedCount++; + Extension->WmiPerfData.TransmittedCount++; + WRITE_TRANSMIT_HOLDING(Extension, + Extension->Controller, + *(Extension->WriteCurrentChar)); + + } else { + + Extension->PerfStats.TransmittedCount += + amountToWrite; + Extension->WmiPerfData.TransmittedCount += + amountToWrite; + WRITE_TRANSMIT_FIFO_HOLDING(Extension, + Extension->Controller, + Extension->WriteCurrentChar, + amountToWrite); + + } + + } + + Extension->HoldingEmpty = FALSE; + Extension->WriteCurrentChar += amountToWrite; + Extension->WriteLength -= amountToWrite; + + if (!Extension->WriteLength) { + + // + // No More characters left. This + // write is complete. Take care + // when updating the information field, + // we could have an xoff counter masquerading + // as a write request. + // + reqContext = SerialGetRequestContext(Extension->CurrentWriteRequest); + + reqContext->Information = + (reqContext->MajorFunction == IRP_MJ_WRITE)? + (reqContext->Length): (1); + + SerialInsertQueueDpc( + Extension->CompleteWriteDpc + ); + + } + + } + + } + + break; + + } + + case SERIAL_IIR_MS: { + + SerialHandleModemUpdate( + Extension, + FALSE + ); + + break; + + } + + } + + } while (!((InterruptIdReg = + READ_INTERRUPT_ID_REG(Extension, Extension->Controller)) + & SERIAL_IIR_NO_INTERRUPT_PENDING)); + + // + // Besides catching the WINBOND and SMC chip problems this + // will also cause transmission to restart incase of an xon + // char being received. Don't remove. + // + + if (SerialProcessLSR(Extension) & SERIAL_LSR_THRE) { + + if (!Extension->TXHolding && + (Extension->WriteLength || + Extension->TransmitImmediate)) { + + goto doTrasmitStuff; + + } + + } + + } + + return ServicedAnInterrupt; + +} + +VOID +SerialPutChar( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN UCHAR CharToPut + ) + +/*++ + +Routine Description: + + This routine, which only runs at device level, takes care of + placing a character into the typeahead (receive) buffer. + +Arguments: + + Extension - The serial device extension. + +Return Value: + + None. + +--*/ + +{ + PREQUEST_CONTEXT reqContext = NULL; + + // + // If we have dsr sensitivity enabled then + // we need to check the modem status register + // to see if it has changed. + // + + if (Extension->HandFlow.ControlHandShake & + SERIAL_DSR_SENSITIVITY) { + + SerialHandleModemUpdate( + Extension, + FALSE + ); + + if (Extension->RXHolding & SERIAL_RX_DSR) { + + // + // We simply act as if we haven't + // seen the character if we have dsr + // sensitivity and the dsr line is low. + // + + return; + + } + + } + + // + // If the xoff counter is non-zero then decrement it. + // If the counter then goes to zero, complete that request. + // + + if (Extension->CountSinceXoff) { + + Extension->CountSinceXoff--; + + if (!Extension->CountSinceXoff) { + reqContext = SerialGetRequestContext(Extension->CurrentXoffRequest); + reqContext->Status = STATUS_SUCCESS; + reqContext->Information = 0; + SerialInsertQueueDpc( + Extension->XoffCountCompleteDpc + ); + + } + + } + + // + // Check to see if we are copying into the + // users buffer or into the interrupt buffer. + // + // If we are copying into the user buffer + // then we know there is always room for one more. + // (We know this because if there wasn't room + // then that read would have completed and we + // would be using the interrupt buffer.) + // + // If we are copying into the interrupt buffer + // then we will need to check if we have enough + // room. + // + + if (Extension->ReadBufferBase != + Extension->InterruptReadBuffer) { + + // + // Increment the following value so + // that the interval timer (if one exists + // for this read) can know that a character + // has been read. + // + + Extension->ReadByIsr++; + + // + // We are in the user buffer. Place the + // character into the buffer. See if the + // read is complete. + // + + *Extension->CurrentCharSlot = CharToPut; + + if (Extension->CurrentCharSlot == + Extension->LastCharSlot) { + + // + // We've filled up the users buffer. + // Switch back to the interrupt buffer + // and send off a DPC to Complete the read. + // + // It is inherent that when we were using + // a user buffer that the interrupt buffer + // was empty. + // + + Extension->ReadBufferBase = + Extension->InterruptReadBuffer; + Extension->CurrentCharSlot = + Extension->InterruptReadBuffer; + Extension->FirstReadableChar = + Extension->InterruptReadBuffer; + Extension->LastCharSlot = + Extension->InterruptReadBuffer + + (Extension->BufferSize - 1); + Extension->CharsInInterruptBuffer = 0; + reqContext = SerialGetRequestContext(Extension->CurrentReadRequest); + reqContext->Information = reqContext->Length; + + SerialInsertQueueDpc( + Extension->CompleteReadDpc + ); + + } else { + + // + // Not done with the users read. + // + + Extension->CurrentCharSlot++; + + } + + } else { + + // + // We need to see if we reached our flow + // control threshold. If we have then + // we turn on whatever flow control the + // owner has specified. If no flow + // control was specified, well..., we keep + // trying to receive characters and hope that + // we have enough room. Note that no matter + // what flow control protocol we are using, it + // will not prevent us from reading whatever + // characters are available. + // + + if ((Extension->HandFlow.ControlHandShake + & SERIAL_DTR_MASK) == + SERIAL_DTR_HANDSHAKE) { + + // + // If we are already doing a + // dtr hold then we don't have + // to do anything else. + // + + if (!(Extension->RXHolding & + SERIAL_RX_DTR)) { + + if ((Extension->BufferSize - + Extension->HandFlow.XoffLimit) + <= (Extension->CharsInInterruptBuffer+1)) { + + Extension->RXHolding |= SERIAL_RX_DTR; + + SerialClrDTR(Extension->WdfInterrupt, Extension); + + } + + } + + } + + if ((Extension->HandFlow.FlowReplace + & SERIAL_RTS_MASK) == + SERIAL_RTS_HANDSHAKE) { + + // + // If we are already doing a + // rts hold then we don't have + // to do anything else. + // + + if (!(Extension->RXHolding & + SERIAL_RX_RTS)) { + + if ((Extension->BufferSize - + Extension->HandFlow.XoffLimit) + <= (Extension->CharsInInterruptBuffer+1)) { + + Extension->RXHolding |= SERIAL_RX_RTS; + + SerialClrRTS(Extension->WdfInterrupt, Extension); + + } + + } + + } + + if (Extension->HandFlow.FlowReplace & + SERIAL_AUTO_RECEIVE) { + + // + // If we are already doing a + // xoff hold then we don't have + // to do anything else. + // + + if (!(Extension->RXHolding & + SERIAL_RX_XOFF)) { + + if ((Extension->BufferSize - + Extension->HandFlow.XoffLimit) + <= (Extension->CharsInInterruptBuffer+1)) { + + Extension->RXHolding |= SERIAL_RX_XOFF; + + // + // If necessary cause an + // off to be sent. + // + + SerialProdXonXoff( + Extension, + FALSE + ); + + } + + } + + } + + if (Extension->CharsInInterruptBuffer < + Extension->BufferSize) { + + *Extension->CurrentCharSlot = CharToPut; + Extension->CharsInInterruptBuffer++; + + // + // If we've become 80% full on this character + // and this is an interesting event, note it. + // + + if (Extension->CharsInInterruptBuffer == + Extension->BufferSizePt8) { + + if (Extension->IsrWaitMask & + SERIAL_EV_RX80FULL) { + + Extension->HistoryMask |= SERIAL_EV_RX80FULL; + + if (Extension->IrpMaskLocation) { + + *Extension->IrpMaskLocation = + Extension->HistoryMask; + Extension->IrpMaskLocation = NULL; + Extension->HistoryMask = 0; + + reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest); + reqContext->Information = sizeof(ULONG); + SerialInsertQueueDpc( + Extension->CommWaitDpc + ); + + } + + } + + } + + // + // Point to the next available space + // for a received character. Make sure + // that we wrap around to the beginning + // of the buffer if this last character + // received was placed at the last slot + // in the buffer. + // + + if (Extension->CurrentCharSlot == + Extension->LastCharSlot) { + + Extension->CurrentCharSlot = + Extension->InterruptReadBuffer; + + } else { + + Extension->CurrentCharSlot++; + + } + + } else { + + // + // We have a new character but no room for it. + // + + Extension->PerfStats.BufferOverrunErrorCount++; + Extension->WmiPerfData.BufferOverrunErrorCount++; + Extension->ErrorWord |= SERIAL_ERROR_QUEUEOVERRUN; + + if (Extension->HandFlow.FlowReplace & + SERIAL_ERROR_CHAR) { + + // + // Place the error character into the last + // valid place for a character. Be careful!, + // that place might not be the previous location! + // + + if (Extension->CurrentCharSlot == + Extension->InterruptReadBuffer) { + + *(Extension->InterruptReadBuffer+ + (Extension->BufferSize-1)) = + Extension->SpecialChars.ErrorChar; + + } else { + + *(Extension->CurrentCharSlot-1) = + Extension->SpecialChars.ErrorChar; + + } + + } + + // + // If the application has requested it, abort all reads + // and writes on an error. + // + + if (Extension->HandFlow.ControlHandShake & + SERIAL_ERROR_ABORT) { + + SerialInsertQueueDpc( + Extension->CommErrorDpc + ); + + } + + } + + } + +} + +UCHAR +SerialProcessLSR( + IN PSERIAL_DEVICE_EXTENSION Extension + ) + +/*++ + +Routine Description: + + This routine, which only runs at device level, reads the + ISR and totally processes everything that might have + changed. + +Arguments: + + Extension - The serial device extension. + +Return Value: + + The value of the line status register. + +--*/ + +{ + PREQUEST_CONTEXT reqContext = NULL; + + UCHAR LineStatus = READ_LINE_STATUS(Extension, Extension->Controller); + + + Extension->HoldingEmpty = (LineStatus & SERIAL_LSR_THRE) ? TRUE : FALSE; + + // + // If the line status register is just the fact that + // the trasmit registers are empty or a character is + // received then we want to reread the interrupt + // identification register so that we just pick up that. + // + + if (LineStatus & ~(SERIAL_LSR_THRE | SERIAL_LSR_TEMT + | SERIAL_LSR_DR)) { + + // + // We have some sort of data problem in the receive. + // For any of these errors we may abort all current + // reads and writes. + // + // + // If we are inserting the value of the line status + // into the data stream then we should put the escape + // character in now. + // + + if (Extension->EscapeChar) { + + SerialPutChar( + Extension, + Extension->EscapeChar + ); + + SerialPutChar( + Extension, + (UCHAR)((LineStatus & SERIAL_LSR_DR)? + (SERIAL_LSRMST_LSR_DATA):(SERIAL_LSRMST_LSR_NODATA)) + ); + + SerialPutChar( + Extension, + LineStatus + ); + + if (LineStatus & SERIAL_LSR_DR) { + + Extension->PerfStats.ReceivedCount++; + Extension->WmiPerfData.ReceivedCount++; + SerialPutChar( + Extension, + READ_RECEIVE_BUFFER(Extension, Extension->Controller) + ); + + } + + } + + if (LineStatus & SERIAL_LSR_OE) { + + Extension->PerfStats.SerialOverrunErrorCount++; + Extension->WmiPerfData.SerialOverrunErrorCount++; + Extension->ErrorWord |= SERIAL_ERROR_OVERRUN; + + if (Extension->HandFlow.FlowReplace & + SERIAL_ERROR_CHAR) { + + SerialPutChar( + Extension, + Extension->SpecialChars.ErrorChar + ); + + if (LineStatus & SERIAL_LSR_DR) { + + Extension->PerfStats.ReceivedCount++; + Extension->WmiPerfData.ReceivedCount++; + READ_RECEIVE_BUFFER(Extension, Extension->Controller); + + } + + } else { + + if (LineStatus & SERIAL_LSR_DR) { + + Extension->PerfStats.ReceivedCount++; + Extension->WmiPerfData.ReceivedCount++; + SerialPutChar( + Extension, + READ_RECEIVE_BUFFER(Extension, + Extension->Controller + ) + ); + + } + + } + + } + + if (LineStatus & SERIAL_LSR_BI) { + + Extension->ErrorWord |= SERIAL_ERROR_BREAK; + + if (Extension->HandFlow.FlowReplace & + SERIAL_BREAK_CHAR) { + + SerialPutChar( + Extension, + Extension->SpecialChars.BreakChar + ); + + } + + } else { + + // + // Framing errors only count if they + // occur exclusive of a break being + // received. + // + + if (LineStatus & SERIAL_LSR_PE) { + + Extension->PerfStats.ParityErrorCount++; + Extension->WmiPerfData.ParityErrorCount++; + Extension->ErrorWord |= SERIAL_ERROR_PARITY; + + if (Extension->HandFlow.FlowReplace & + SERIAL_ERROR_CHAR) { + + SerialPutChar( + Extension, + Extension->SpecialChars.ErrorChar + ); + + if (LineStatus & SERIAL_LSR_DR) { + + Extension->PerfStats.ReceivedCount++; + Extension->WmiPerfData.ReceivedCount++; + READ_RECEIVE_BUFFER(Extension, Extension->Controller); + + } + + } + + } + + if (LineStatus & SERIAL_LSR_FE) { + + Extension->PerfStats.FrameErrorCount++; + Extension->WmiPerfData.FrameErrorCount++; + Extension->ErrorWord |= SERIAL_ERROR_FRAMING; + + if (Extension->HandFlow.FlowReplace & + SERIAL_ERROR_CHAR) { + + SerialPutChar( + Extension, + Extension->SpecialChars.ErrorChar + ); + if (LineStatus & SERIAL_LSR_DR) { + + Extension->PerfStats.ReceivedCount++; + Extension->WmiPerfData.ReceivedCount++; + READ_RECEIVE_BUFFER(Extension, Extension->Controller); + + } + + } + + } + + } + + // + // If the application has requested it, + // abort all the reads and writes + // on an error. + // + + if (Extension->HandFlow.ControlHandShake & + SERIAL_ERROR_ABORT) { + + SerialInsertQueueDpc( + Extension->CommErrorDpc + ); + + } + + // + // Check to see if we have a wait + // pending on the comm error events. If we + // do then we schedule a dpc to satisfy + // that wait. + // + + if (Extension->IsrWaitMask) { + + if ((Extension->IsrWaitMask & SERIAL_EV_ERR) && + (LineStatus & (SERIAL_LSR_OE | + SERIAL_LSR_PE | + SERIAL_LSR_FE))) { + + Extension->HistoryMask |= SERIAL_EV_ERR; + + } + + if ((Extension->IsrWaitMask & SERIAL_EV_BREAK) && + (LineStatus & SERIAL_LSR_BI)) { + + Extension->HistoryMask |= SERIAL_EV_BREAK; + + } + + if (Extension->IrpMaskLocation && + Extension->HistoryMask) { + + *Extension->IrpMaskLocation = + Extension->HistoryMask; + Extension->IrpMaskLocation = NULL; + Extension->HistoryMask = 0; + reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest); + reqContext->Information = sizeof(ULONG); + SerialInsertQueueDpc( + Extension->CommWaitDpc + ); + + } + + } + + if (LineStatus & SERIAL_LSR_THRE) { + + // + // There is a hardware bug in some versions + // of the 16450 and 550. If THRE interrupt + // is pending, but a higher interrupt comes + // in it will only return the higher and + // *forget* about the THRE. + // + // A suitable workaround - whenever we + // are *all* done reading line status + // of the device we check to see if the + // transmit holding register is empty. If it is + // AND we are currently transmitting data + // enable the interrupts which should cause + // an interrupt indication which we quiet + // when we read the interrupt id register. + // + + if (Extension->WriteLength | + Extension->TransmitImmediate) { + + DISABLE_ALL_INTERRUPTS(Extension, + Extension->Controller + ); + ENABLE_ALL_INTERRUPTS(Extension, + Extension->Controller + ); + } + + } + + } + + return LineStatus; +} + + diff --git a/tests/projects/wdk/kmdf/serial/log.c b/tests/projects/wdk/kmdf/serial/log.c new file mode 100644 index 000000000..285c8b2f8 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/log.c @@ -0,0 +1,97 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + log.c + +Abstract: + + Debug log Code for serial. + +Environment: + + kernel mode only + +--*/ + +#include "precomp.h" + +extern ULONG DebugLevel; +extern ULONG DebugFlag; + +#if !defined(EVENT_TRACING) + +VOID +SerialDbgPrintEx ( + IN ULONG TraceEventsLevel, + IN ULONG TraceEventsFlag, + IN PCCHAR DebugMessage, + ... + ) + +/*++ + +Routine Description: + + Debug print for the sample driver. + +Arguments: + + TraceEventsLevel - print level between 0 and 3, with 3 the most verbose + +Return Value: + + None. + + --*/ + { +#if DBG + +#define TEMP_BUFFER_SIZE 1024 + + va_list list; + CHAR debugMessageBuffer [TEMP_BUFFER_SIZE]; + NTSTATUS status; + + va_start(list, DebugMessage); + + if (DebugMessage) { + + // + // Using new safe string functions instead of _vsnprintf. + // This function takes care of NULL terminating if the message + // is longer than the buffer. + // + status = RtlStringCbVPrintfA( debugMessageBuffer, + sizeof(debugMessageBuffer), + DebugMessage, + list ); + if(!NT_SUCCESS(status)) { + + KdPrint((_DRIVER_NAME_": RtlStringCbVPrintfA failed %x\n", status)); + return; + } + if (TraceEventsLevel < TRACE_LEVEL_INFORMATION || + (TraceEventsLevel <= DebugLevel && + ((TraceEventsFlag & DebugFlag) == TraceEventsFlag))) { + + KdPrint((debugMessageBuffer)); + } + } + va_end(list); + + return; + +#else + + UNREFERENCED_PARAMETER(TraceEventsLevel); + UNREFERENCED_PARAMETER(TraceEventsFlag); + UNREFERENCED_PARAMETER(DebugMessage); + +#endif +} + +#endif + diff --git a/tests/projects/wdk/kmdf/serial/log.h b/tests/projects/wdk/kmdf/serial/log.h new file mode 100644 index 000000000..eedcd08f3 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/log.h @@ -0,0 +1,37 @@ +/*++ + +Copyright (c) 1993 Microsoft Corporation +:ts=4 + +Module Name: + + log.h + +Abstract: + + debug macros + +Environment: + + Kernel & user mode + +--*/ + +#ifndef __LOG_H__ +#define __LOG_H__ + +#if !defined(EVENT_TRACING) + +VOID +SerialDbgPrintEx ( + IN ULONG DebugPrintLevel, + IN ULONG DebugPrintFlag, + IN PCCHAR DebugMessage, + ... + ); + +#endif + +#endif // __LOG_H__ + + diff --git a/tests/projects/wdk/kmdf/serial/modmflow.c b/tests/projects/wdk/kmdf/serial/modmflow.c new file mode 100644 index 000000000..1c71ae3fd --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/modmflow.c @@ -0,0 +1,1714 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + modmflow.c + +Abstract: + + This module contains *MOST* of the code used to manipulate + the modem control and status registers. The vast majority + of the remainder of flow control is concentrated in the + Interrupt service routine. A very small amount resides + in the read code that pull characters out of the interrupt + buffer. + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "modmflow.tmh" +#endif + + +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialDecrementRTSCounter; + +BOOLEAN +SerialSetDTR( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine which is only called at interrupt level is used + to set the DTR in the modem control register. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION Extension = Context; + UCHAR ModemControl; + + UNREFERENCED_PARAMETER(Interrupt); + + ModemControl = READ_MODEM_CONTROL(Extension, Extension->Controller); + + ModemControl |= SERIAL_MCR_DTR; + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, + "Setting DTR for %p\n", Extension->Controller); + + WRITE_MODEM_CONTROL(Extension, Extension->Controller, ModemControl); + + return FALSE; + +} + +BOOLEAN +SerialClrDTR( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine which is only called at interrupt level is used + to clear the DTR in the modem control register. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + UCHAR ModemControl; + + UNREFERENCED_PARAMETER(Interrupt); + + ModemControl = READ_MODEM_CONTROL(Extension, Extension->Controller); + + ModemControl &= ~SERIAL_MCR_DTR; + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Clearing DTR for %p\n", Extension->Controller); + + WRITE_MODEM_CONTROL(Extension, Extension->Controller, ModemControl); + + return FALSE; + +} + +BOOLEAN +SerialSetRTS( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine which is only called at interrupt level is used + to set the RTS in the modem control register. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + UCHAR ModemControl; + + UNREFERENCED_PARAMETER(Interrupt); + + ModemControl = READ_MODEM_CONTROL(Extension, Extension->Controller); + + ModemControl |= SERIAL_MCR_RTS; + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Setting Rts for %p\n", Extension->Controller); + + WRITE_MODEM_CONTROL(Extension, Extension->Controller, ModemControl); + + return FALSE; + +} + +BOOLEAN +SerialClrRTS( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine which is only called at interrupt level is used + to clear the RTS in the modem control register. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + UCHAR ModemControl; + + UNREFERENCED_PARAMETER(Interrupt); + + ModemControl = READ_MODEM_CONTROL(Extension, Extension->Controller); + + ModemControl &= ~SERIAL_MCR_RTS; + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Clearing Rts for %p\n", Extension->Controller); + + WRITE_MODEM_CONTROL(Extension, Extension->Controller, ModemControl); + + return FALSE; + +} + +BOOLEAN +SerialSetupNewHandFlow( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN PSERIAL_HANDFLOW NewHandFlow + ) + +/*++ + +Routine Description: + + This routine adjusts the flow control based on new + control flow. + +Arguments: + + Extension - A pointer to the serial device extension. + + NewHandFlow - A pointer to a serial handflow structure + that is to become the new setup for flow + control. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + SERIAL_HANDFLOW New = *NewHandFlow; + + // + // If the Extension->DeviceIsOpened is FALSE that means + // we are entering this routine in response to an open request. + // If that is so, then we always proceed with the work regardless + // of whether things have changed. + // + + // + // First we take care of the DTR flow control. We only + // do work if something has changed. + // + + if ((!Extension->DeviceIsOpened) || + ((Extension->HandFlow.ControlHandShake & SERIAL_DTR_MASK) != + (New.ControlHandShake & SERIAL_DTR_MASK))) { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Processing DTR flow for %p\n", + Extension->Controller); + + if (New.ControlHandShake & SERIAL_DTR_MASK) { + + // + // Well we might want to set DTR. + // + // Before we do, we need to check whether we are doing + // dtr flow control. If we are then we need to check + // if then number of characters in the interrupt buffer + // exceeds the XoffLimit. If it does then we don't + // enable DTR AND we set the RXHolding to record that + // we are holding because of the dtr. + // + + if ((New.ControlHandShake & SERIAL_DTR_MASK) + == SERIAL_DTR_HANDSHAKE) { + + if ((Extension->BufferSize - New.XoffLimit) > + Extension->CharsInInterruptBuffer) { + + // + // However if we are already holding we don't want + // to turn it back on unless we exceed the Xon + // limit. + // + + if (Extension->RXHolding & SERIAL_RX_DTR) { + + // + // We can assume that its DTR line is already low. + // + + if (Extension->CharsInInterruptBuffer > + (ULONG)New.XonLimit) { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Removing DTR block on " + "reception for %p\n", + Extension->Controller); + + Extension->RXHolding &= ~SERIAL_RX_DTR; + SerialSetDTR(Extension->WdfInterrupt, Extension); + + } + + } else { + + SerialSetDTR(Extension->WdfInterrupt, Extension); + + } + + } else { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Setting DTR block on reception " + "for %p\n", Extension->Controller); + Extension->RXHolding |= SERIAL_RX_DTR; + SerialClrDTR(Extension->WdfInterrupt, Extension); + + } + + } else { + + // + // Note that if we aren't currently doing dtr flow control then + // we MIGHT have been. So even if we aren't currently doing + // DTR flow control, we should still check if RX is holding + // because of DTR. If it is, then we should clear the holding + // of this bit. + // + + if (Extension->RXHolding & SERIAL_RX_DTR) { + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Removing dtr block of reception " + "for %p\n", Extension->Controller); + Extension->RXHolding &= ~SERIAL_RX_DTR; + } + + SerialSetDTR(Extension->WdfInterrupt, Extension); + + } + + } else { + + // + // The end result here will be that DTR is cleared. + // + // We first need to check whether reception is being held + // up because of previous DTR flow control. If it is then + // we should clear that reason in the RXHolding mask. + // + + if (Extension->RXHolding & SERIAL_RX_DTR) { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "removing dtr block of reception for" + " %p\n", Extension->Controller); + Extension->RXHolding &= ~SERIAL_RX_DTR; + + } + + SerialClrDTR(Extension->WdfInterrupt, Extension); + + } + + } + + // + // Time to take care of the RTS Flow control. + // + // First we only do work if something has changed. + // + + if ((!Extension->DeviceIsOpened) || + ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) != + (New.FlowReplace & SERIAL_RTS_MASK))) { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Processing RTS flow %p\n", + Extension->Controller); + + if ((New.FlowReplace & SERIAL_RTS_MASK) == + SERIAL_RTS_HANDSHAKE) { + + // + // Well we might want to set RTS. + // + // Before we do, we need to check whether we are doing + // rts flow control. If we are then we need to check + // if then number of characters in the interrupt buffer + // exceeds the XoffLimit. If it does then we don't + // enable RTS AND we set the RXHolding to record that + // we are holding because of the rts. + // + + if ((Extension->BufferSize - New.XoffLimit) > + Extension->CharsInInterruptBuffer) { + + // + // However if we are already holding we don't want + // to turn it back on unless we exceed the Xon + // limit. + // + + if (Extension->RXHolding & SERIAL_RX_RTS) { + + // + // We can assume that its RTS line is already low. + // + + if (Extension->CharsInInterruptBuffer > + (ULONG)New.XonLimit) { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Removing rts block of " + "reception for %p\n", + Extension->Controller); + Extension->RXHolding &= ~SERIAL_RX_RTS; + SerialSetRTS(Extension->WdfInterrupt, Extension); + + } + + } else { + + SerialSetRTS(Extension->WdfInterrupt, Extension); + + } + + } else { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Setting rts block of reception for " + "%p\n", Extension->Controller); + Extension->RXHolding |= SERIAL_RX_RTS; + SerialClrRTS(Extension->WdfInterrupt, Extension); + + } + + } else if ((New.FlowReplace & SERIAL_RTS_MASK) == + SERIAL_RTS_CONTROL) { + + // + // Note that if we aren't currently doing rts flow control then + // we MIGHT have been. So even if we aren't currently doing + // RTS flow control, we should still check if RX is holding + // because of RTS. If it is, then we should clear the holding + // of this bit. + // + + if (Extension->RXHolding & SERIAL_RX_RTS) { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Clearing rts block of reception for " + "%p\n", Extension->Controller); + Extension->RXHolding &= ~SERIAL_RX_RTS; + + } + + SerialSetRTS(Extension->WdfInterrupt, Extension); + + } else if ((New.FlowReplace & SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE) { + + // + // We first need to check whether reception is being held + // up because of previous RTS flow control. If it is then + // we should clear that reason in the RXHolding mask. + // + + if (Extension->RXHolding & SERIAL_RX_RTS) { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "TOGGLE Clearing rts block of " + "reception for %p\n", Extension->Controller); + Extension->RXHolding &= ~SERIAL_RX_RTS; + + } + + // + // We have to place the rts value into the Extension + // now so that the code that tests whether the + // rts line should be lowered will find that we + // are "still" doing transmit toggling. The code + // for lowering can be invoked later by a timer so + // it has to test whether it still needs to do its + // work. + // + + Extension->HandFlow.FlowReplace &= ~SERIAL_RTS_MASK; + Extension->HandFlow.FlowReplace |= SERIAL_TRANSMIT_TOGGLE; + + // + // The order of the tests is very important below. + // + // If there is a break then we should turn on the RTS. + // + // If there isn't a break but there are characters in + // the hardware, then turn on the RTS. + // + // If there are writes pending that aren't being held + // up, then turn on the RTS. + // + + if ((Extension->TXHolding & SERIAL_TX_BREAK) || + ((SerialProcessLSR(Extension) & (SERIAL_LSR_THRE | + SERIAL_LSR_TEMT)) != + (SERIAL_LSR_THRE | + SERIAL_LSR_TEMT)) || + (Extension->CurrentWriteRequest || Extension->TransmitImmediate || + (!IsQueueEmpty(Extension->WriteQueue)) && + (!Extension->TXHolding))) { + + SerialSetRTS(Extension->WdfInterrupt, Extension); + + } else { + + // + // This routine will check to see if it is time + // to lower the RTS because of transmit toggle + // being on. If it is ok to lower it, it will, + // if it isn't ok, it will schedule things so + // that it will get lowered later. + // + + Extension->CountOfTryingToLowerRTS++; + SerialPerhapsLowerRTS(Extension->WdfInterrupt, Extension); + + } + + } else { + + // + // The end result here will be that RTS is cleared. + // + // We first need to check whether reception is being held + // up because of previous RTS flow control. If it is then + // we should clear that reason in the RXHolding mask. + // + + if (Extension->RXHolding & SERIAL_RX_RTS) { + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "Clearing rts block of reception for" + " %p\n", Extension->Controller); + Extension->RXHolding &= ~SERIAL_RX_RTS; + + } + + SerialClrRTS(Extension->WdfInterrupt, Extension); + + } + + } + + // + // We now take care of automatic receive flow control. + // We only do work if things have changed. + // + + if ((!Extension->DeviceIsOpened) || + ((Extension->HandFlow.FlowReplace & SERIAL_AUTO_RECEIVE) != + (New.FlowReplace & SERIAL_AUTO_RECEIVE))) { + + if (New.FlowReplace & SERIAL_AUTO_RECEIVE) { + + // + // We wouldn't be here if it had been on before. + // + // We should check to see whether we exceed the turn + // off limits. + // + // Note that since we are following the OS/2 flow + // control rules we will never send an xon if + // when enabling xon/xoff flow control we discover that + // we could receive characters but we are held up do + // to a previous Xoff. + // + + if ((Extension->BufferSize - New.XoffLimit) <= + Extension->CharsInInterruptBuffer) { + + // + // Cause the Xoff to be sent. + // + + Extension->RXHolding |= SERIAL_RX_XOFF; + + SerialProdXonXoff( + Extension, + FALSE + ); + + } + + } else { + + // + // The app has disabled automatic receive flow control. + // + // If transmission was being held up because of + // an automatic receive Xoff, then we should + // cause an Xon to be sent. + // + + if (Extension->RXHolding & SERIAL_RX_XOFF) { + + Extension->RXHolding &= ~SERIAL_RX_XOFF; + + // + // Cause the Xon to be sent. + // + + SerialProdXonXoff( + Extension, + TRUE + ); + + } + + } + + } + + // + // We now take care of automatic transmit flow control. + // We only do work if things have changed. + // + + if ((!Extension->DeviceIsOpened) || + ((Extension->HandFlow.FlowReplace & SERIAL_AUTO_TRANSMIT) != + (New.FlowReplace & SERIAL_AUTO_TRANSMIT))) { + + if (New.FlowReplace & SERIAL_AUTO_TRANSMIT) { + + // + // We wouldn't be here if it had been on before. + // + // There is some belief that if autotransmit + // was just enabled, I should go look in what we + // already received, and if we find the xoff character + // then we should stop transmitting. I think this + // is an application bug. For now we just care about + // what we see in the future. + // + + ; + + } else { + + // + // The app has disabled automatic transmit flow control. + // + // If transmission was being held up because of + // an automatic transmit Xoff, then we should + // cause an Xon to be sent. + // + + if (Extension->TXHolding & SERIAL_TX_XOFF) { + + Extension->TXHolding &= ~SERIAL_TX_XOFF; + + // + // Cause the Xon to be sent. + // + + SerialProdXonXoff( + Extension, + TRUE + ); + + } + + } + + } + + // + // At this point we can simply make sure that entire + // handflow structure in the extension is updated. + // + + Extension->HandFlow = New; + + return FALSE; + +} + +BOOLEAN +SerialSetHandFlow( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine is used to set the handshake and control + flow in the device extension. + +Arguments: + + Context - Pointer to a structure that contains a pointer to + the device extension and a pointer to a handflow + structure.. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_IOCTL_SYNC S = Context; + PSERIAL_DEVICE_EXTENSION Extension = S->Extension; + PSERIAL_HANDFLOW HandFlow = S->Data; + + UNREFERENCED_PARAMETER(Interrupt); + + SerialSetupNewHandFlow( + Extension, + HandFlow + ); + + SerialHandleModemUpdate( + Extension, + FALSE + ); + + return FALSE; + +} + +BOOLEAN +SerialTurnOnBreak( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine will turn on break in the hardware and + record the fact the break is on, in the extension variable + that holds reasons that transmission is stopped. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + + UCHAR OldLineControl; + + UNREFERENCED_PARAMETER(Interrupt); + + if ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE) { + + SerialSetRTS(Extension->WdfInterrupt, Extension); + + } + + OldLineControl = READ_LINE_CONTROL(Extension, Extension->Controller); + + OldLineControl |= SERIAL_LCR_BREAK; + + WRITE_LINE_CONTROL(Extension, + Extension->Controller, + OldLineControl + ); + + Extension->TXHolding |= SERIAL_TX_BREAK; + + return FALSE; + +} + +BOOLEAN +SerialTurnOffBreak( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine will turn off break in the hardware and + record the fact the break is off, in the extension variable + that holds reasons that transmission is stopped. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + + UCHAR OldLineControl; + + UNREFERENCED_PARAMETER(Interrupt); + + if (Extension->TXHolding & SERIAL_TX_BREAK) { + + // + // We actually have a good reason for testing if transmission + // is holding instead of blindly clearing the bit. + // + // If transmission actually was holding and the result of + // clearing the bit is that we should restart transmission + // then we will poke the interrupt enable bit, which will + // cause an actual interrupt and transmission will then + // restart on its own. + // + // If transmission wasn't holding and we poked the bit + // then we would interrupt before a character actually made + // it out and we could end up over writing a character in + // the transmission hardware. + + OldLineControl = READ_LINE_CONTROL(Extension, Extension->Controller); + + OldLineControl &= ~SERIAL_LCR_BREAK; + + WRITE_LINE_CONTROL(Extension, + Extension->Controller, + OldLineControl + ); + + Extension->TXHolding &= ~SERIAL_TX_BREAK; + + if (!Extension->TXHolding && + (Extension->TransmitImmediate || + Extension->WriteLength) && + Extension->HoldingEmpty) { + + DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + + } else { + + // + // The following routine will lower the rts if we + // are doing transmit toggleing and there is no + // reason to keep it up. + // + + Extension->CountOfTryingToLowerRTS++; + SerialPerhapsLowerRTS(Extension->WdfInterrupt, Extension); + + } + + } + + return FALSE; + +} + +BOOLEAN +SerialPretendXoff( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine is used to process the Ioctl that request the + driver to act as if an Xoff was received. Even if the + driver does not have automatic Xoff/Xon flowcontrol - This + still will stop the transmission. This is the OS/2 behavior + and is not well specified for Windows. Therefore we adopt + the OS/2 behavior. + + Note: If the driver does not have automatic Xoff/Xon enabled + then the only way to restart transmission is for the + application to request we "act" as if we saw the xon. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + + UNREFERENCED_PARAMETER(Interrupt); + + Extension->TXHolding |= SERIAL_TX_XOFF; + + if ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE) { + + SerialInsertQueueDpc( + Extension->StartTimerLowerRTSDpc + )?Extension->CountOfTryingToLowerRTS++:0; + + } + + return FALSE; + +} + +BOOLEAN +SerialPretendXon( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine is used to process the Ioctl that request the + driver to act as if an Xon was received. + + Note: If the driver does not have automatic Xoff/Xon enabled + then the only way to restart transmission is for the + application to request we "act" as if we saw the xon. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + + UNREFERENCED_PARAMETER(Interrupt); + + if (Extension->TXHolding) { + + // + // We actually have a good reason for testing if transmission + // is holding instead of blindly clearing the bit. + // + // If transmission actually was holding and the result of + // clearing the bit is that we should restart transmission + // then we will poke the interrupt enable bit, which will + // cause an actual interrupt and transmission will then + // restart on its own. + // + // If transmission wasn't holding and we poked the bit + // then we would interrupt before a character actually made + // it out and we could end up over writing a character in + // the transmission hardware. + + Extension->TXHolding &= ~SERIAL_TX_XOFF; + + if (!Extension->TXHolding && + (Extension->TransmitImmediate || + Extension->WriteLength) && + Extension->HoldingEmpty) { + + DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + + } + + } + + return FALSE; + +} + +VOID +SerialHandleReducedIntBuffer( + IN PSERIAL_DEVICE_EXTENSION Extension + ) + +/*++ + +Routine Description: + + This routine is called to handle a reduction in the number + of characters in the interrupt (typeahead) buffer. It + will check the current output flow control and re-enable transmission + as needed. + + NOTE: This routine assumes that it is working at interrupt level. + +Arguments: + + Extension - A pointer to the device extension. + +Return Value: + + None. + +--*/ + +{ + + + // + // If we are doing receive side flow control and we are + // currently "holding" then because we've emptied out + // some characters from the interrupt buffer we need to + // see if we can "re-enable" reception. + // + + if (Extension->RXHolding) { + + if (Extension->CharsInInterruptBuffer <= + (ULONG)Extension->HandFlow.XonLimit) { + + if (Extension->RXHolding & SERIAL_RX_DTR) { + + Extension->RXHolding &= ~SERIAL_RX_DTR; + SerialSetDTR(Extension->WdfInterrupt, Extension); + + } + + if (Extension->RXHolding & SERIAL_RX_RTS) { + + Extension->RXHolding &= ~SERIAL_RX_RTS; + SerialSetRTS(Extension->WdfInterrupt, Extension); + + } + + if (Extension->RXHolding & SERIAL_RX_XOFF) { + + // + // Prod the transmit code to send xon. + // + + SerialProdXonXoff( + Extension, + TRUE + ); + + } + + } + + } + +} + +VOID +SerialProdXonXoff( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN BOOLEAN SendXon + ) + +/*++ + +Routine Description: + + This routine will set up the SendXxxxChar variables if + necessary and determine if we are going to be interrupting + because of current transmission state. It will cause an + interrupt to occur if neccessary, to send the xon/xoff char. + + NOTE: This routine assumes that it is called at interrupt + level. + +Arguments: + + Extension - A pointer to the serial device extension. + + SendXon - If a character is to be send, this indicates whether + it should be an Xon or an Xoff. + +Return Value: + + None. + +--*/ + +{ + + // + // We assume that if the prodding is called more than + // once that the last prod has set things up appropriately. + // + // We could get called before the character is sent out + // because the send of the character was blocked because + // of hardware flow control (or break). + // + + if (!Extension->SendXonChar && !Extension->SendXoffChar + && Extension->HoldingEmpty) { + + DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + + } + + if (SendXon) { + + Extension->SendXonChar = TRUE; + Extension->SendXoffChar = FALSE; + + } else { + + Extension->SendXonChar = FALSE; + Extension->SendXoffChar = TRUE; + + } + +} + +ULONG +SerialHandleModemUpdate( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN BOOLEAN DoingTX + ) + +/*++ + +Routine Description: + + This routine will be to check on the modem status, and + handle any appropriate event notification as well as + any flow control appropriate to modem status lines. + + NOTE: This routine assumes that it is called at interrupt + level. + +Arguments: + + Extension - A pointer to the serial device extension. + + DoingTX - This boolean is used to indicate that this call + came from the transmit processing code. If this + is true then there is no need to cause a new interrupt + since the code will be trying to send the next + character as soon as this call finishes. + +Return Value: + + This returns the old value of the modem status register + (extended into a ULONG). + +--*/ + +{ + + // + // We keep this local so that after we are done + // examining the modem status and we've updated + // the transmission holding value, we know whether + // we've changed from needing to hold up transmission + // to transmission being able to proceed. + // + ULONG OldTXHolding = Extension->TXHolding; + + // + // Holds the value in the mode status register. + // + UCHAR ModemStatus; + PREQUEST_CONTEXT reqContext; + + ModemStatus = + READ_MODEM_STATUS(Extension, Extension->Controller); + + + // + // If we are placeing the modem status into the data stream + // on every change, we should do it now. + // + + if (Extension->EscapeChar) { + + if (ModemStatus & (SERIAL_MSR_DCTS | + SERIAL_MSR_DDSR | + SERIAL_MSR_TERI | + SERIAL_MSR_DDCD)) { + + SerialPutChar( + Extension, + Extension->EscapeChar + ); + SerialPutChar( + Extension, + SERIAL_LSRMST_MST + ); + SerialPutChar( + Extension, + ModemStatus + ); + + } + + } + + + // + // Take care of input flow control based on sensitivity + // to the DSR. This is done so that the application won't + // see spurious data generated by odd devices. + // + // Basically, if we are doing dsr sensitivity then the + // driver should only accept data when the dsr bit is + // set. + // + + if (Extension->HandFlow.ControlHandShake & SERIAL_DSR_SENSITIVITY) { + + if (ModemStatus & SERIAL_MSR_DSR) { + + // + // The line is high. Simply make sure that + // RXHolding does't have the DSR bit. + // + + Extension->RXHolding &= ~SERIAL_RX_DSR; + + } else { + + Extension->RXHolding |= SERIAL_RX_DSR; + + } + + } else { + + // + // We don't have sensitivity due to DSR. Make sure we + // arn't holding. (We might have been, but the app just + // asked that we don't hold for this reason any more.) + // + + Extension->RXHolding &= ~SERIAL_RX_DSR; + + } + + // + // Check to see if we have a wait + // pending on the modem status events. If we + // do then we schedule a dpc to satisfy + // that wait. + // + + if (Extension->IsrWaitMask) { + + if ((Extension->IsrWaitMask & SERIAL_EV_CTS) && + (ModemStatus & SERIAL_MSR_DCTS)) { + + Extension->HistoryMask |= SERIAL_EV_CTS; + + } + + if ((Extension->IsrWaitMask & SERIAL_EV_DSR) && + (ModemStatus & SERIAL_MSR_DDSR)) { + + Extension->HistoryMask |= SERIAL_EV_DSR; + + } + + if ((Extension->IsrWaitMask & SERIAL_EV_RING) && + (ModemStatus & SERIAL_MSR_TERI)) { + + Extension->HistoryMask |= SERIAL_EV_RING; + + } + + if ((Extension->IsrWaitMask & SERIAL_EV_RLSD) && + (ModemStatus & SERIAL_MSR_DDCD)) { + + Extension->HistoryMask |= SERIAL_EV_RLSD; + + } + + if (Extension->IrpMaskLocation && + Extension->HistoryMask) { + + *Extension->IrpMaskLocation = + Extension->HistoryMask; + Extension->IrpMaskLocation = NULL; + Extension->HistoryMask = 0; + + reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest); + reqContext->Information = sizeof(ULONG); + SerialInsertQueueDpc( + Extension->CommWaitDpc + ); + + } + + } + + // + // If the app has modem line flow control then + // we check to see if we have to hold up transmission. + // + + if (Extension->HandFlow.ControlHandShake & + SERIAL_OUT_HANDSHAKEMASK) { + + if (Extension->HandFlow.ControlHandShake & + SERIAL_CTS_HANDSHAKE) { + + if (ModemStatus & SERIAL_MSR_CTS) { + + Extension->TXHolding &= ~SERIAL_TX_CTS; + + } else { + + Extension->TXHolding |= SERIAL_TX_CTS; + + } + + } else { + + Extension->TXHolding &= ~SERIAL_TX_CTS; + + } + + if (Extension->HandFlow.ControlHandShake & + SERIAL_DSR_HANDSHAKE) { + + if (ModemStatus & SERIAL_MSR_DSR) { + + Extension->TXHolding &= ~SERIAL_TX_DSR; + + } else { + + Extension->TXHolding |= SERIAL_TX_DSR; + + } + + } else { + + Extension->TXHolding &= ~SERIAL_TX_DSR; + + } + + if (Extension->HandFlow.ControlHandShake & + SERIAL_DCD_HANDSHAKE) { + + if (ModemStatus & SERIAL_MSR_DCD) { + + Extension->TXHolding &= ~SERIAL_TX_DCD; + + } else { + + Extension->TXHolding |= SERIAL_TX_DCD; + + } + + } else { + + Extension->TXHolding &= ~SERIAL_TX_DCD; + + } + + // + // If we hadn't been holding, and now we are then + // queue off a dpc that will lower the RTS line + // if we are doing transmit toggling. + // + + if (!OldTXHolding && Extension->TXHolding && + ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE)) { + + SerialInsertQueueDpc( + Extension->StartTimerLowerRTSDpc + )?Extension->CountOfTryingToLowerRTS++:0; + + } + + // + // We've done any adjusting that needed to be + // done to the holding mask given updates + // to the modem status. If the Holding mask + // is clear (and it wasn't clear to start) + // and we have "write" work to do set things + // up so that the transmission code gets invoked. + // + + if (!DoingTX && OldTXHolding && !Extension->TXHolding) { + + if (!Extension->TXHolding && + (Extension->TransmitImmediate || + Extension->WriteLength) && + Extension->HoldingEmpty) { + + DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + } + + } + + } else { + + // + // We need to check if transmission is holding + // up because of modem status lines. What + // could have occured is that for some strange + // reason, the app has asked that we no longer + // stop doing output flow control based on + // the modem status lines. If however, we + // *had* been held up because of the status lines + // then we need to clear up those reasons. + // + + if (Extension->TXHolding & (SERIAL_TX_DCD | + SERIAL_TX_DSR | + SERIAL_TX_CTS)) { + + Extension->TXHolding &= ~(SERIAL_TX_DCD | + SERIAL_TX_DSR | + SERIAL_TX_CTS); + + + if (!DoingTX && OldTXHolding && !Extension->TXHolding) { + + if (!Extension->TXHolding && + (Extension->TransmitImmediate || + Extension->WriteLength) && + Extension->HoldingEmpty) { + + DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + } + + } + + } + + } + + return ((ULONG)ModemStatus); +} + +BOOLEAN +SerialPerhapsLowerRTS( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine checks that the software reasons for lowering + the RTS lines are present. If so, it will then cause the + line status register to be read (and any needed processing + implied by the status register to be done), and if the + shift register is empty it will lower the line. If the + shift register isn't empty, this routine will queue off + a dpc that will start a timer, that will basically call + us back to try again. + + NOTE: This routine assumes that it is called at interrupt + level. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + Always FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + + UNREFERENCED_PARAMETER(Interrupt); + + // + // We first need to test if we are actually still doing + // transmit toggle flow control. If we aren't then + // we have no reason to try be here. + // + + if ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE) { + + // + // The order of the tests is very important below. + // + // If there is a break then we should leave on the RTS, + // because when the break is turned off, it will submit + // the code to shut down the RTS. + // + // If there are writes pending that aren't being held + // up, then leave on the RTS, because the end of the write + // code will cause this code to be reinvoked. If the writes + // are being held up, its ok to lower the RTS because the + // upon trying to write the first character after transmission + // is restarted, we will raise the RTS line. + // + + if ((Extension->TXHolding & SERIAL_TX_BREAK) || + (Extension->CurrentWriteRequest || Extension->TransmitImmediate || + (!IsQueueEmpty(Extension->WriteQueue)) && + (!Extension->TXHolding))) { + + NOTHING; + + } else { + + // + // Looks good so far. Call the line status check and processing + // code, it will return the "current" line status value. If + // the holding and shift register are clear, lower the RTS line, + // if they aren't clear, queue of a dpc that will cause a timer + // to reinvoke us later. We do this code here because no one + // but this routine cares about the characters in the hardware, + // so no routine by this routine will bother invoking to test + // if the hardware is empty. + // + + if ((SerialProcessLSR(Extension) & + (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) != + (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) { + + // + // Well it's not empty, try again later. + // + + SerialInsertQueueDpc( + Extension->StartTimerLowerRTSDpc + )?Extension->CountOfTryingToLowerRTS++:0; + + + } else { + + // + // Nothing in the hardware, Lower the RTS. + // + + SerialClrRTS(Extension->WdfInterrupt, Extension); + + + } + + } + + } + + // + // We decement the counter to indicate that we've reached + // the end of the execution path that is trying to push + // down the RTS line. + // + + Extension->CountOfTryingToLowerRTS--; + + return FALSE; +} + +VOID +SerialStartTimerLowerRTS( + IN WDFDPC Dpc + ) + +/*++ + +Routine Description: + + This routine starts a timer that when it expires will start + a dpc that will check if it can lower the rts line because + there are no characters in the hardware. + +Arguments: + + Dpc - Not Used. + + DeferredContext - Really points to the device extension. + + SystemContext1 - Not Used. + + SystemContext2 - Not Used. + +Return Value: + + None. + +--*/ + +{ + LARGE_INTEGER CharTime; + PSERIAL_DEVICE_EXTENSION Extension = NULL; + + Extension = SerialGetDeviceExtension(WdfDpcGetParentObject(Dpc)); + + + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, ">SerialStartTimerLowerRTS(%p)\n", + Extension); + + + // + // Since all the callbacks into the driver are serialized, we don't have + // synchronize the access to any of the Extension variables. + // + + CharTime = SerialGetCharTime(Extension); + + CharTime.QuadPart = -CharTime.QuadPart; + + if (SerialSetTimer( + Extension->LowerRTSTimer, + CharTime + )) { + + // + // The timer was already in the timer queue. This implies + // that one path of execution that was trying to lower + // the RTS has "died". Synchronize with the ISR so that + // we can lower the count. + // + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialDecrementRTSCounter, + Extension + ); + + } + + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, "WdfInterrupt, + SerialPerhapsLowerRTS, + Extension + ); + +} + +BOOLEAN +SerialDecrementRTSCounter( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine checks that the software reasons for lowering + the RTS lines are present. If so, it will then cause the + line status register to be read (and any needed processing + implied by the status register to be done), and if the + shift register is empty it will lower the line. If the + shift register isn't empty, this routine will queue off + a dpc that will start a timer, that will basically call + us back to try again. + + NOTE: This routine assumes that it is called at interrupt + level. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + Always FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + + UNREFERENCED_PARAMETER(Interrupt); + + Extension->CountOfTryingToLowerRTS--; + + return FALSE; + +} + + diff --git a/tests/projects/wdk/kmdf/serial/openclos.c b/tests/projects/wdk/kmdf/serial/openclos.c new file mode 100644 index 000000000..477f07ac1 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/openclos.c @@ -0,0 +1,850 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + openclos.c + +Abstract: + + This module contains the code that is very specific to + opening, closing, and cleaning up in the serial driver. + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "openclos.tmh" +#endif + + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGESER,SerialGetCharTime) +#pragma alloc_text(PAGESER,SerialEvtFileClose) +#pragma alloc_text(PAGESER,SerialDrainUART) +#pragma alloc_text(PAGESRP0,SerialEvtDeviceFileCreate) +#pragma alloc_text(PAGESRP0,SerialCreateTimersAndDpcs) +#endif // ALLOC_PRAGMA + + + +VOID +SerialEvtDeviceFileCreate ( + IN WDFDEVICE Device, + IN WDFREQUEST Request, + IN WDFFILEOBJECT FileObject + ) +/*++ + +Routine Description: + + The framework calls a driver's EvtDeviceFileCreate callback + when the framework receives an IRP_MJ_CREATE request. + The system sends this request when a user application opens the + device to perform an I/O operation, such as reading or writing a file. + This callback is called synchronously, in the context of the thread + that created the IRP_MJ_CREATE request. + +Arguments: + + Device - Handle to a framework device object. + FileObject - Pointer to fileobject that represents the open handle. + CreateParams - Copy of the Create IO_STACK_LOCATION + +Return Value: + + VOID. + +--*/ +{ + NTSTATUS status; + PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension (Device); + + UNREFERENCED_PARAMETER(FileObject); + + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_CREATE_CLOSE, + "SerialEvtDeviceFileCreate %wZ\n", &extension->DeviceName); + + status = SerialDeviceFileCreateWorker(Device); + + // + // Complete the WDF request. + // + WdfRequestComplete(Request, status); + + return; + +} + + +NTSTATUS +SerialWdmDeviceFileCreate ( + IN WDFDEVICE Device, + IN PIRP Irp + ) +/*++ + +Routine Description: + + This is the dispatch routine for IRP_MJ_CREATE. The system sends this + request when a user application opens the device to perform an I/O + operation, such as reading or writing a file. + +Arguments: + + DeviceObject - Pointer to the device object for this device + Irp - Pointer to the IRP for the current request + +Return Value: + + NT status code + +--*/ +{ + NTSTATUS status; + PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension (Device); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_CREATE_CLOSE, + "SerialWdmDeviceFileCreate %wZ\n", &extension->DeviceName); + + status = SerialDeviceFileCreateWorker(Device); + + // + // Complete the WDM request. + // + Irp->IoStatus.Information = 0L; + Irp->IoStatus.Status = status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return status; +} + + +NTSTATUS +SerialDeviceFileCreateWorker ( + IN WDFDEVICE Device + ) +{ + NTSTATUS status; + PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension (Device); + + // + // Create a buffer for the RX data when no reads are outstanding. + // + + extension->InterruptReadBuffer = NULL; + extension->BufferSize = 0; + + switch (MmQuerySystemSize()) { + + case MmLargeSystem: { + + extension->BufferSize = 4096; + extension->InterruptReadBuffer = ExAllocatePoolWithTag( + NonPagedPoolNx, + extension->BufferSize, + POOL_TAG + ); + + if (extension->InterruptReadBuffer) { + break; + } + + } + + case MmMediumSystem: { + + extension->BufferSize = 1024; + extension->InterruptReadBuffer = ExAllocatePoolWithTag( + NonPagedPoolNx, + extension->BufferSize, + POOL_TAG + ); + + if (extension->InterruptReadBuffer) { + break; + } + + } + + case MmSmallSystem: { + + extension->BufferSize = 128; + extension->InterruptReadBuffer = ExAllocatePoolWithTag( + NonPagedPoolNx, + extension->BufferSize, + POOL_TAG + ); + + } + + } + + if (!extension->InterruptReadBuffer) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + // + // By taking a power reference by calling WdfDeviceStopIdle, we prevent the + // framework from powering down our device due to idle timeout when there + // is an open handle. Power reference also moves the device to D0 if we are + // idled out. If you fail create anywhere later in this routine, do make sure + // drop the reference. + // + status = WdfDeviceStopIdle(Device, TRUE); + if (!NT_SUCCESS(status)) { + return status; + } + + // + // wakeup is not currently enabled + // + + extension->IsWakeEnabled = FALSE; + + // + // On a new open we "flush" the read queue by initializing the + // count of characters. + // + + extension->CharsInInterruptBuffer = 0; + extension->LastCharSlot = extension->InterruptReadBuffer + + (extension->BufferSize - 1); + + extension->ReadBufferBase = extension->InterruptReadBuffer; + extension->CurrentCharSlot = extension->InterruptReadBuffer; + extension->FirstReadableChar = extension->InterruptReadBuffer; + + extension->TotalCharsQueued = 0; + + // + // We set up the default xon/xoff limits. + // + + extension->HandFlow.XoffLimit = extension->BufferSize >> 3; + extension->HandFlow.XonLimit = extension->BufferSize >> 1; + + extension->WmiCommData.XoffXmitThreshold = extension->HandFlow.XoffLimit; + extension->WmiCommData.XonXmitThreshold = extension->HandFlow.XonLimit; + + extension->BufferSizePt8 = ((3*(extension->BufferSize>>2))+ + (extension->BufferSize>>4)); + + // + // Mark the device as busy for WMI + // + + extension->WmiCommData.IsBusy = TRUE; + + extension->IrpMaskLocation = NULL; + extension->HistoryMask = 0; + extension->IsrWaitMask = 0; + + extension->SendXonChar = FALSE; + extension->SendXoffChar = FALSE; + +#if !DBG + // + // Clear out the statistics. + // + + WdfInterruptSynchronize( + extension->WdfInterrupt, + SerialClearStats, + extension + ); +#endif + + // + // The escape char replacement must be reset upon every open. + // + + extension->EscapeChar = 0; + + // + // We don't want the device to be removed or stopped when there is an handle + // + // Note to anyone copying this sample as a starting point: + // + // This works in this driver simply because this driver supports exactly + // one open handle at a time. If it supported more, then it would need + // counting logic to determine when all the reasons for failing Stop/Remove + // were gone. + // + WdfDeviceSetStaticStopRemove(Device, FALSE); + + // + // Synchronize with the ISR and let it know that the device + // has been successfully opened. + // + + WdfInterruptSynchronize( + extension->WdfInterrupt, + SerialMarkOpen, + extension + ); + + return STATUS_SUCCESS; + +} + + +VOID +SerialEvtFileClose( + IN WDFFILEOBJECT FileObject + ) + +/*++ + + EvtFileClose is called when all the handles represented by the FileObject + is closed and all the references to FileObject is removed. This callback + may get called in an arbitrary thread context instead of the thread that + called CloseHandle. If you want to delete any per FileObject context that + must be done in the context of the user thread that made the Create call, + you should do that in the EvtDeviceCleanp callback. + +Arguments: + + FileObject - Pointer to fileobject that represents the open handle. + +Return Value: + + VOID + +--*/ + +{ + PAGED_CODE(); + + SerialFileCloseWorker(WdfFileObjectGetDevice(FileObject)); + return; +} + + +NTSTATUS +SerialWdmFileClose ( + IN WDFDEVICE Device, + IN PIRP Irp + ) +/*++ + +Routine Description: + + This is the dispatch routine for IRP_MJ_CLOSE. This is called when all the + handles represented by the FileObject is closed and all the references to + the FileObject is removed. + +Arguments: + + DeviceObject - Pointer to the device object for this device + Irp - Pointer to the IRP for the current request + +Return Value: + + NT status code + +--*/ +{ + SerialFileCloseWorker(Device); + + Irp->IoStatus.Information = 0L; + Irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return STATUS_SUCCESS; +} + + +VOID +SerialFileCloseWorker( + IN WDFDEVICE Device + ) +{ + ULONG flushCount; + + // + // This "timer value" is used to wait 10 character times + // after the hardware is empty before we actually "run down" + // all of the flow control/break junk. + // + LARGE_INTEGER tenCharDelay; + + // + // Holds a character time. + // + LARGE_INTEGER charTime; + + PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension(Device); + PSERIAL_INTERRUPT_CONTEXT interruptContext = SerialGetInterruptContext(extension->WdfInterrupt); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_CREATE_CLOSE, "In SerialEvtFileClose %wZ\n", + &extension->DeviceName); + + // + // Acquire the interrupt state lock. + // + WdfWaitLockAcquire(interruptContext->InterruptStateLock, NULL); + + // + // If the Interrupts are connected, then the hardware state has to be + // cleaned up now. Note that the EvtFileClose callback gets called for + // an open file object even though the interrupts have been disabled + // possibly due to a Surprise Remove PNP event. In such a case, the + // Interrupt object should not be used. + // + if (interruptContext->IsInterruptConnected) { + + charTime.QuadPart = -SerialGetCharTime(extension).QuadPart; + + // + // Do this now so that if the isr gets called it won't do anything + // to cause more chars to get sent. We want to run down the hardware. + // + + SetDeviceIsOpened(extension, FALSE, FALSE); + + // + // Synchronize with the isr to turn off break if it + // is already on. + // + + WdfInterruptSynchronize( + extension->WdfInterrupt, + SerialTurnOffBreak, + extension + ); + + // + // Wait a reasonable amount of time (20 * fifodepth) until all characters + // have been emptied out of the hardware. + // + + for (flushCount = (20 * 16); flushCount != 0; flushCount--) { + if ((READ_LINE_STATUS(extension, extension->Controller) & + (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) != + (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) { + + KeDelayExecutionThread(KernelMode, FALSE, &charTime); + } else { + break; + } + } + + if (flushCount == 0) { + SerialMarkHardwareBroken(extension); + } + + // + // Synchronize with the ISR to let it know that interrupts are + // no longer important. + // + + WdfInterruptSynchronize( + extension->WdfInterrupt, + SerialMarkClose, + extension + ); + + + // + // If the driver has automatically transmitted an Xoff in + // the context of automatic receive flow control then we + // should transmit an Xon. + // + + if (extension->RXHolding & SERIAL_RX_XOFF) { + + // + // Loop until the holding register is empty. + // + while (!(READ_LINE_STATUS(extension, extension->Controller) & + SERIAL_LSR_THRE)) { + KeDelayExecutionThread( + KernelMode, + FALSE, + &charTime + ); + + } + + WRITE_TRANSMIT_HOLDING(extension, + extension->Controller, + extension->SpecialChars.XonChar + ); + + // + // Wait a reasonable amount of time for the characters + // to be emptied out of the hardware. + // + + for (flushCount = (20 * 16); flushCount != 0; flushCount--) { + if ((READ_LINE_STATUS(extension, extension->Controller) & + (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) != + (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) { + KeDelayExecutionThread(KernelMode, FALSE, &charTime); + } else { + break; + } + } + + if (flushCount == 0) { + SerialMarkHardwareBroken(extension); + } + } + + + // + // The hardware is empty. Delay 10 character times before + // shut down all the flow control. + // + + tenCharDelay.QuadPart = charTime.QuadPart * 10; + + KeDelayExecutionThread( + KernelMode, + TRUE, + &tenCharDelay + ); + +#pragma prefast(suppress: __WARNING_INFERRED_IRQ_TOO_LOW, "This warning is because we are calling interrupt synchronize routine directly.") + SerialClrDTR(extension->WdfInterrupt, extension); + + // + // We have to be very careful how we clear the RTS line. + // Transmit toggling might have been on at some point. + // + // We know that there is nothing left that could start + // out the "polling" execution path. We need to + // check the counter that indicates that the execution + // path is active. If it is then we loop delaying one + // character time. After each delay we check to see if + // the counter has gone to zero. When it has we know that + // the execution path should be just about finished. We + // make sure that we still aren't in the routine that + // synchronized execution with the ISR by synchronizing + // ourselve with the ISR. + // + + if (extension->CountOfTryingToLowerRTS) { + + do { +#pragma prefast(suppress: __WARNING_INFERRED_IRQ_TOO_HIGH, "This warning is due to suppressing the previous one.") + KeDelayExecutionThread( + KernelMode, + FALSE, + &charTime + ); + + } while (extension->CountOfTryingToLowerRTS); + + // + // The execution path should no longer exist that + // is trying to push down the RTS. Well just + // make sure it's down by falling through to + // code that forces it down. + // + + } + +#pragma prefast(suppress: __WARNING_INFERRED_IRQ_TOO_LOW, "This warning is because we are calling interrupt synchronize routine directly.") + SerialClrRTS(extension->WdfInterrupt, extension); + + // + // Clean out the holding reasons (since we are closed). + // + + extension->RXHolding = 0; + extension->TXHolding = 0; + + // + // Mark device as not busy for WMI + // + + extension->WmiCommData.IsBusy = FALSE; + + } + + // + // Release the Interrupt state lock. + // + WdfWaitLockRelease(interruptContext->InterruptStateLock); + + // + // All is done. The port has been disabled from interrupting + // so there is no point in keeping the memory around. + // + + extension->BufferSize = 0; + if (extension->InterruptReadBuffer != NULL) { + ExFreePool(extension->InterruptReadBuffer); + } + extension->InterruptReadBuffer = NULL; + + // + // Make sure the wake is disabled. + // + ASSERT(!extension->IsWakeEnabled); + + SerialDrainTimersAndDpcs(extension); + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_CREATE_CLOSE, "DPC's drained:\n"); + + // + // It's fine for the device to be powered off if there are no open handles. + // + WdfDeviceResumeIdle(Device); + + // + // It's okay to allow the device to be stopped or removed. + // + // Note to anyone copying this sample as a starting point: + // + // This works in this driver simply because this driver supports exactly + // one open handle at a time. If it supported more, then it would need + // counting logic to determine when all the reasons for failing Stop/Remove + // were gone. + // + WdfDeviceSetStaticStopRemove(Device, TRUE); + + return; + +} + +BOOLEAN +SerialMarkOpen( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine merely sets a boolean to true to mark the fact that + somebody opened the device and its worthwhile to pay attention + to interrupts. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION extension = Context; + + UNREFERENCED_PARAMETER(Interrupt); + + SerialReset(extension->WdfInterrupt, extension); + + // + // Prepare for the opening by re-enabling interrupts. + // + // We do this my modifying the OUT2 line in the modem control. + // In PC's this bit is "anded" with the interrupt line. + // + + WRITE_MODEM_CONTROL(extension, + extension->Controller, + (UCHAR)(READ_MODEM_CONTROL(extension, extension->Controller) | SERIAL_MCR_OUT2) + ); + + extension->DeviceIsOpened = TRUE; + extension->ErrorWord = 0; + + return FALSE; + +} + +VOID +SerialDrainUART(IN PSERIAL_DEVICE_EXTENSION PDevExt, + IN PLARGE_INTEGER PDrainTime) +{ + PAGED_CODE(); + + // + // Wait until all characters have been emptied out of the hardware. + // + + while ((READ_LINE_STATUS(PDevExt, PDevExt->Controller) & + (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) + != (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) { + KeDelayExecutionThread(KernelMode, FALSE, PDrainTime); + } +} + +VOID +SerialDisableUART(IN PVOID Context) + +/*++ + +Routine Description: + + This routine disables the UART and puts it in a "safe" state when + not in use (like a close or powerdown). + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION extension = Context; + + // + // Prepare for the closing by stopping interrupts. + // + // We do this by adjusting the OUT2 line in the modem control. + // In PC's this bit is "anded" with the interrupt line. + // + + WRITE_MODEM_CONTROL(extension, extension->Controller, + (UCHAR)(READ_MODEM_CONTROL(extension, extension->Controller) + & ~SERIAL_MCR_OUT2)); + + if (extension->FifoPresent) { + WRITE_FIFO_CONTROL(extension, extension->Controller, (UCHAR)0); + } +} + + + +BOOLEAN +SerialMarkClose( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine merely sets a boolean to false to mark the fact that + somebody closed the device and it's no longer worthwhile to pay attention + to interrupts. It also disables the UART. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION extension = Context; + + UNREFERENCED_PARAMETER(Interrupt); + + SerialDisableUART(Context); + extension->DeviceIsOpened = FALSE; + extension->DeviceState.Reopen = FALSE; + + return FALSE; + +} + +LARGE_INTEGER +SerialGetCharTime( + IN PSERIAL_DEVICE_EXTENSION Extension + ) + +/*++ + +Routine Description: + + This function will return the number of 100 nanosecond intervals + there are in one character time (based on the present form + of flow control. + +Arguments: + + Extension - Just what it says. + +Return Value: + + 100 nanosecond intervals in a character time. + +--*/ + +{ + ULONG dataSize = 0; + ULONG paritySize; + ULONG stopSize; + ULONG charTime; + ULONG bitTime; + LARGE_INTEGER tmp; + + PAGED_CODE(); + + if ((Extension->LineControl & SERIAL_DATA_MASK) == SERIAL_5_DATA) { + dataSize = 5; + } else if ((Extension->LineControl & SERIAL_DATA_MASK) + == SERIAL_6_DATA) { + dataSize = 6; + } else if ((Extension->LineControl & SERIAL_DATA_MASK) + == SERIAL_7_DATA) { + dataSize = 7; + } else if ((Extension->LineControl & SERIAL_DATA_MASK) + == SERIAL_8_DATA) { + dataSize = 8; + } + + paritySize = 1; + if ((Extension->LineControl & SERIAL_PARITY_MASK) + == SERIAL_NONE_PARITY) { + + paritySize = 0; + + } + + if (Extension->LineControl & SERIAL_2_STOP) { + + // + // Even if it is 1.5, for sanities sake were going + // to say 2. + // + + stopSize = 2; + + } else { + + stopSize = 1; + + } + + // + // First we calculate the number of 100 nanosecond intervals + // are in a single bit time (Approximately). + // + + bitTime = (10000000+(Extension->CurrentBaud-1))/Extension->CurrentBaud; + charTime = bitTime + ((dataSize+paritySize+stopSize)*bitTime); + + tmp.QuadPart = charTime; + return tmp; + +} + + diff --git a/tests/projects/wdk/kmdf/serial/pnp.c b/tests/projects/wdk/kmdf/serial/pnp.c new file mode 100644 index 000000000..8773f30ac --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/pnp.c @@ -0,0 +1,2804 @@ +/*++ + +Copyright (c) 1991, 1992, 1993 - 1997 Microsoft Corporation + +Module Name: + + pnp.c + +Abstract: + + This module contains the code that handles the plug and play + IRPs for the serial driver. + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" +#include +#include +#include + +#if defined(EVENT_TRACING) +#include "pnp.tmh" +#endif + +static const PHYSICAL_ADDRESS SerialPhysicalZero = {0}; +static const SUPPORTED_BAUD_RATES SupportedBaudRates[] = { + {75, SERIAL_BAUD_075}, + {110, SERIAL_BAUD_110}, + {135, SERIAL_BAUD_134_5}, + {150, SERIAL_BAUD_150}, + {300, SERIAL_BAUD_300}, + {600, SERIAL_BAUD_600}, + {1200, SERIAL_BAUD_1200}, + {1800, SERIAL_BAUD_1800}, + {2400, SERIAL_BAUD_2400}, + {4800, SERIAL_BAUD_4800}, + {7200, SERIAL_BAUD_7200}, + {9600, SERIAL_BAUD_9600}, + {14400, SERIAL_BAUD_14400}, + {19200, SERIAL_BAUD_19200}, + {38400, SERIAL_BAUD_38400}, + {56000, SERIAL_BAUD_56K}, + {57600, SERIAL_BAUD_57600}, + {115200, SERIAL_BAUD_115200}, + {128000, SERIAL_BAUD_128K}, + {SERIAL_BAUD_INVALID, SERIAL_BAUD_USER} + }; + + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGESRP0, SerialEvtDeviceAdd) +#pragma alloc_text(PAGESRP0, SerialEvtPrepareHardware) +#pragma alloc_text(PAGESRP0, SerialEvtReleaseHardware) +#pragma alloc_text(PAGESRP0, SerialEvtDeviceD0ExitPreInterruptsDisabled) +#pragma alloc_text(PAGESRP0, SerialMapHWResources) +#pragma alloc_text(PAGESRP0, SerialUnmapHWResources) +#pragma alloc_text(PAGESRP0, SerialEvtDeviceContextCleanup) +#pragma alloc_text(PAGESRP0, SerialDoExternalNaming) +#pragma alloc_text(PAGESRP0, SerialReportMaxBaudRate) +#pragma alloc_text(PAGESRP0, SerialUndoExternalNaming) +#pragma alloc_text(PAGESRP0, SerialInitController) +#pragma alloc_text(PAGESRP0, SerialGetMappedAddress) +#pragma alloc_text(PAGESRP0, SerialSetPowerPolicy) +#pragma alloc_text(PAGESRP0, SerialReadSymName) + +#endif // ALLOC_PRAGMA + +PVOID LocalMmMapIoSpace( + _In_ PHYSICAL_ADDRESS PhysicalAddress, + _In_ SIZE_T NumberOfBytes + ) +{ + typedef + PVOID + (*PFN_MM_MAP_IO_SPACE_EX) ( + _In_ PHYSICAL_ADDRESS PhysicalAddress, + _In_ SIZE_T NumberOfBytes, + _In_ ULONG Protect + ); + + UNICODE_STRING name; + PFN_MM_MAP_IO_SPACE_EX pMmMapIoSpaceEx; + + RtlInitUnicodeString(&name, L"MmMapIoSpaceEx"); + pMmMapIoSpaceEx = (PFN_MM_MAP_IO_SPACE_EX) (ULONG_PTR)MmGetSystemRoutineAddress(&name); + + if (pMmMapIoSpaceEx != NULL){ + // + // Call WIN10 API if available + // + return pMmMapIoSpaceEx(PhysicalAddress, + NumberOfBytes, + PAGE_READWRITE | PAGE_NOCACHE); + } + + // + // Supress warning that MmMapIoSpace allocates executable memory. + // This function is only used if the preferred API, MmMapIoSpaceEx + // is not present. MmMapIoSpaceEx is available starting in WIN10. + // + #pragma warning(suppress: 30029) + return MmMapIoSpace(PhysicalAddress, NumberOfBytes, MmNonCached); +} + +NTSTATUS +SerialEvtDeviceAdd( + IN WDFDRIVER Driver, + IN PWDFDEVICE_INIT DeviceInit + ) +/*++ + +Routine Description: + + EvtDeviceAdd is called by the framework in response to AddDevice + call from the PnP manager. + + +Arguments: + + Driver - Handle to a framework driver object created in DriverEntry + + DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure. + +Return Value: + + NTSTATUS + +--*/ + +{ + NTSTATUS status; + PSERIAL_DEVICE_EXTENSION pDevExt; + static ULONG currentInstance = 0; + WDF_FILEOBJECT_CONFIG fileobjectConfig; + WDFDEVICE device; + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_OBJECT_ATTRIBUTES attributes; + WDF_IO_QUEUE_CONFIG queueConfig; + WDFQUEUE defaultqueue; + ULONG isMulti; + PULONG countSoFar; + WDF_INTERRUPT_CONFIG interruptConfig; + PSERIAL_INTERRUPT_CONTEXT interruptContext; + ULONG relinquishPowerPolicy; + + DECLARE_UNICODE_STRING_SIZE(deviceName, DEVICE_OBJECT_NAME_LENGTH); + + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "-->SerialEvtDeviceAdd\n"); + + status = RtlUnicodeStringPrintf(&deviceName, L"%ws%u", + L"\\Device\\Serial", + currentInstance++); + + + if (!NT_SUCCESS(status)) { + return status; + } + + status = WdfDeviceInitAssignName(DeviceInit,& deviceName); + if (!NT_SUCCESS(status)) { + return status; + } + + WdfDeviceInitSetExclusive(DeviceInit, TRUE); + WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_SERIAL_PORT); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, REQUEST_CONTEXT); + + WdfDeviceInitSetRequestAttributes(DeviceInit, &attributes); + + // + // Zero out the PnpPowerCallbacks structure. + // + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + + // + // Set Callbacks for any of the functions we are interested in. + // If no callback is set, Framework will take the default action + // by itself. These next two callbacks set up and tear down hardware state, + // specifically that which only has to be done once. + // + + pnpPowerCallbacks.EvtDevicePrepareHardware = SerialEvtPrepareHardware; + pnpPowerCallbacks.EvtDeviceReleaseHardware = SerialEvtReleaseHardware; + + // + // These two callbacks set up and tear down hardware state that must be + // done every time the device moves in and out of the D0-working state. + // + + pnpPowerCallbacks.EvtDeviceD0Entry = SerialEvtDeviceD0Entry; + pnpPowerCallbacks.EvtDeviceD0Exit = SerialEvtDeviceD0Exit; + + // + // Specify the callback for monitoring when the device's interrupt are + // enabled or about to be disabled. + // + + pnpPowerCallbacks.EvtDeviceD0EntryPostInterruptsEnabled = SerialEvtDeviceD0EntryPostInterruptsEnabled; + pnpPowerCallbacks.EvtDeviceD0ExitPreInterruptsDisabled = SerialEvtDeviceD0ExitPreInterruptsDisabled; + + // + // Register the PnP and power callbacks. + // + WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); + + if ( !NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, + "WdfDeviceInitSetPnpPowerEventCallbacks failed %!STATUS!\n", + status); + return status; + } + + // + // Find out if we own power policy + // + SerialGetFdoRegistryKeyValue( DeviceInit, + L"SerialRelinquishPowerPolicy", + &relinquishPowerPolicy ); + + if(relinquishPowerPolicy) { + // + // FDO's are assumed to be power policy owner by default. So tell + // the framework explicitly to relinquish the power policy ownership. + // + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, + "RelinquishPowerPolicy due to registry settings\n"); + + WdfDeviceInitSetPowerPolicyOwnership(DeviceInit, FALSE); + } + + // + // For Windows XP and below, we will register for the WDM Preprocess callback + // for IRP_MJ_CREATE. This is done because, the Serenum filter doesn't handle + // creates that are marked pending. Since framework always marks the IRP pending, + // we are registering this WDM preprocess handler so that we can bypass the + // framework and handle the create and close ourself. This workaround is need + // only if you intend to install the Serenum as an upper filter. + // + if (RtlIsNtDdiVersionAvailable(NTDDI_VISTA) == FALSE) { + + status = WdfDeviceInitAssignWdmIrpPreprocessCallback( + DeviceInit, + SerialWdmDeviceFileCreate, + IRP_MJ_CREATE, + NULL, // pointer minor function table + 0); // number of entries in the table + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, + "WdfDeviceInitAssignWdmIrpPreprocessCallback failed %!STATUS!\n", + status); + return status; + } + + status = WdfDeviceInitAssignWdmIrpPreprocessCallback( + DeviceInit, + SerialWdmFileClose, + IRP_MJ_CLOSE, + NULL, // pointer minor function table + 0); // number of entries in the table + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, + "WdfDeviceInitAssignWdmIrpPreprocessCallback failed %!STATUS!\n", + status); + return status; + } + + } else { + + // + // FileEvents can opt for Device level synchronization only if the ExecutionLevel + // of the Device is passive. Since we can't choose passive execution-level for + // device because we have chose to synchronize timers & dpcs with the device, + // we will opt out of synchonization with the device for fileobjects. + // Note: If the driver has to synchronize Create with the other I/O events, + // it can create a queue and configure-dispatch create requests to the queue. + // + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.SynchronizationScope = WdfSynchronizationScopeNone; + + // + // Set Entry points for Create and Close.. + // + WDF_FILEOBJECT_CONFIG_INIT( + &fileobjectConfig, + SerialEvtDeviceFileCreate, + SerialEvtFileClose, + WDF_NO_EVENT_CALLBACK // Cleanup + ); + + WdfDeviceInitSetFileObjectConfig( + DeviceInit, + &fileobjectConfig, + &attributes + ); + } + + + // + // Since framework queues doesn't handle IRP_MJ_FLUSH_BUFFERS, + // IRP_MJ_QUERY_INFORMATION and IRP_MJ_SET_INFORMATION requests, + // we will register a preprocess callback to handle them. + // + status = WdfDeviceInitAssignWdmIrpPreprocessCallback( + DeviceInit, + SerialFlush, + IRP_MJ_FLUSH_BUFFERS, + NULL, // pointer minor function table + 0); // number of entries in the table + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, + "WdfDeviceInitAssignWdmIrpPreprocessCallback failed %!STATUS!\n", + status); + return status; + } + + status = WdfDeviceInitAssignWdmIrpPreprocessCallback( + DeviceInit, + SerialQueryInformationFile, + IRP_MJ_QUERY_INFORMATION, + NULL, // pointer minor function table + 0); // number of entries in the table + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, + "WdfDeviceInitAssignWdmIrpPreprocessCallback failed %!STATUS!\n", + status); + return status; + } + status = WdfDeviceInitAssignWdmIrpPreprocessCallback( + DeviceInit, + SerialSetInformationFile, + IRP_MJ_SET_INFORMATION, + NULL, // pointer minor function table + 0); // number of entries in the table + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, + "WdfDeviceInitAssignWdmIrpPreprocessCallback failed %!STATUS!\n", + status); + return status; + } + + + // + // Create a device + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE (&attributes, + SERIAL_DEVICE_EXTENSION); + // + // Provide a callback to cleanup the context. This will be called + // when the device is removed. + // + attributes.EvtCleanupCallback = SerialEvtDeviceContextCleanup; + // + // By opting for SynchronizationScopeDevice, we tell the framework to + // synchronize callbacks events of all the objects directly associated + // with the device. In this driver, we will associate queues, dpcs, + // and timers. By doing that we don't have to worrry about synchronizing + // access to device-context by Io Events, cancel-routine, timer and dpc + // callbacks. + // + attributes.SynchronizationScope = WdfSynchronizationScopeDevice; + + status = WdfDeviceCreate(&DeviceInit, &attributes, &device); + if (!NT_SUCCESS(status)) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, + "SerialAddDevice - WdfDeviceCreate failed %!STATUS!\n", + status); + return status; + } + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, + "Created device (%p) %wZ\n", device, &deviceName); + + pDevExt = SerialGetDeviceExtension (device); + + pDevExt->DriverObject = WdfDriverWdmGetDriverObject(Driver); + + // + // This sample doesn't support multiport serial devices. + // Multiport devices allow other pseudo-serial devices with extra + // resources to specify another range of I/O ports. + // + if(!SerialGetRegistryKeyValue(device, L"MultiportDevice", &isMulti)) { + isMulti = 0; + } + + if(isMulti) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, + "This sample doesn't support multiport devices\n"); + return STATUS_DEVICE_CONFIGURATION_ERROR; + } + + // + // Set up the device extension. + // + + pDevExt = SerialGetDeviceExtension (device); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, + "AddDevice PDO(0x%p) FDO(0x%p), Lower(0x%p) DevExt (0x%p)\n", + WdfDeviceWdmGetPhysicalDevice (device), + WdfDeviceWdmGetDeviceObject (device), + WdfDeviceWdmGetAttachedDevice(device), + pDevExt); + + pDevExt->DeviceIsOpened = FALSE; + pDevExt->DeviceObject = WdfDeviceWdmGetDeviceObject(device); + pDevExt->WdfDevice = device; + + pDevExt->TxFifoAmount = driverDefaults.TxFIFODefault; + pDevExt->UartRemovalDetect = driverDefaults.UartRemovalDetect; + pDevExt->CreatedSymbolicLink = FALSE; + pDevExt->OwnsPowerPolicy = relinquishPowerPolicy ? FALSE : TRUE; + + status = SerialSetPowerPolicy(pDevExt); + if(!NT_SUCCESS(status)){ + return status; + } + + // + // We create four manual queues below. + // Read Queue..(how about using serial queue for read). Since requests + // jump from queue to queue, we cannot configure the queues to receive a + // particular type of request. For example, some of the IOCTLs end up + // in read and write queue. + // + WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, + WdfIoQueueDispatchManual); + + queueConfig.EvtIoStop = SerialEvtIoStop; + queueConfig.EvtIoResume = SerialEvtIoResume; + queueConfig.EvtIoCanceledOnQueue = SerialEvtCanceledOnQueue; + + status = WdfIoQueueCreate (device, + &queueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &pDevExt->ReadQueue + ); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, " WdfIoQueueCreate for Read failed %!STATUS!\n", status); + return status; + } + + // + // Write Queue.. + // + WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, + WdfIoQueueDispatchManual); + + queueConfig.EvtIoStop = SerialEvtIoStop; + queueConfig.EvtIoResume = SerialEvtIoResume; + queueConfig.EvtIoCanceledOnQueue = SerialEvtCanceledOnQueue; + + status = WdfIoQueueCreate (device, + &queueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &pDevExt->WriteQueue + ); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, " WdfIoQueueCreate for Write failed %!STATUS!\n", status); + return status; + } + + // + // Mask Queue... + // + WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, + WdfIoQueueDispatchManual + ); + + queueConfig.EvtIoCanceledOnQueue = SerialEvtCanceledOnQueue; + + queueConfig.EvtIoStop = SerialEvtIoStop; + queueConfig.EvtIoResume = SerialEvtIoResume; + + status = WdfIoQueueCreate (device, + &queueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &pDevExt->MaskQueue + ); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, " WdfIoQueueCreate for Mask failed %!STATUS!\n", status); + return status; + } + + // + // Purge Queue.. + // + WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, + WdfIoQueueDispatchManual + ); + + queueConfig.EvtIoCanceledOnQueue = SerialEvtCanceledOnQueue; + + queueConfig.EvtIoStop = SerialEvtIoStop; + queueConfig.EvtIoResume = SerialEvtIoResume; + + status = WdfIoQueueCreate (device, + &queueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &pDevExt->PurgeQueue + ); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, " WdfIoQueueCreate for Purge failed %!STATUS!\n", status); + return status; + } + + // + // All the incoming I/O requests are routed to the default queue and dispatch to the + // appropriate callback events. These callback event will check to see if another + // request is currently active. If so then it will forward it to other manual queues. + // All the queues are auto managed by the framework in response to the PNP + // and Power events. + // + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE( + &queueConfig, + WdfIoQueueDispatchParallel + ); + queueConfig.EvtIoRead = SerialEvtIoRead; + queueConfig.EvtIoWrite = SerialEvtIoWrite; + queueConfig.EvtIoDeviceControl = SerialEvtIoDeviceControl; + queueConfig.EvtIoInternalDeviceControl = SerialEvtIoInternalDeviceControl; + queueConfig.EvtIoCanceledOnQueue = SerialEvtCanceledOnQueue; + + queueConfig.EvtIoStop = SerialEvtIoStop; + queueConfig.EvtIoResume = SerialEvtIoResume; + + status = WdfIoQueueCreate(device, + &queueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &defaultqueue + ); + if (!NT_SUCCESS(status)) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfIoQueueCreate failed %!STATUS!\n", status); + return status; + } + + // + // Create WDFINTERRUPT object. Let us leave the ShareVector to default value and + // let the framework decide whether to share the interrupt or not based on the + // ShareDisposition provided by the bus driver in the resource descriptor. + // + + WDF_INTERRUPT_CONFIG_INIT(&interruptConfig, + SerialISR, + NULL); + + interruptConfig.EvtInterruptDisable = SerialEvtInterruptDisable; + interruptConfig.EvtInterruptEnable = SerialEvtInterruptEnable; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, SERIAL_INTERRUPT_CONTEXT); + + status = WdfInterruptCreate(device, + &interruptConfig, + &attributes, + &pDevExt->WdfInterrupt); + + if (!NT_SUCCESS(status)) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Couldn't create interrupt for %wZ\n", + &pDevExt->DeviceName); + return status; + } + + // + // Interrupt state wait lock... + // + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = pDevExt->WdfInterrupt; + + interruptContext = SerialGetInterruptContext(pDevExt->WdfInterrupt); + + status = WdfWaitLockCreate(&attributes, + &interruptContext->InterruptStateLock + ); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, " WdfWaitLockCreate for InterruptStateLock failed %!STATUS!\n", status); + return status; + } + + // + // Set interrupt policy + // + SerialSetInterruptPolicy(pDevExt->WdfInterrupt); + + // + // Timers and DPCs... + // + status = SerialCreateTimersAndDpcs(pDevExt); + if (!NT_SUCCESS(status)) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "SerialCreateTimersAndDpcs failed %x\n", status); + return status; + } + + // + // Register with WMI. + // + status = SerialWmiRegistration(device); + if(!NT_SUCCESS (status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "SerialWmiRegistration failed %!STATUS!\n", status); + return status; + + } + + // + // Upto this point, if we fail, we don't have to worry about freeing any resource because + // framework will free all the objects. + // + // + // Do the external naming. + // + + status = SerialDoExternalNaming(pDevExt); + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "External Naming Failed - Status %!STATUS!\n", + status); + return status; + } + + // + // Finally increment the global system configuration that keeps track of number of serial ports. + // + countSoFar = &IoGetConfigurationInformation()->SerialCount; + (*countSoFar)++; + pDevExt->IsSystemConfigInfoUpdated = TRUE; + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "<--SerialEvtDeviceAdd\n"); + + return status; + +} +#pragma warning(push) +#pragma warning(disable:28118) // this callback will run at IRQL=PASSIVE_LEVEL +_Use_decl_annotations_ +VOID +SerialEvtDeviceContextCleanup ( + WDFOBJECT Device + ) +/*++ + +Routine Description: + + EvtDeviceContextCleanup event callback cleans up anything done in + EvtDeviceAdd, except those things that are automatically cleaned + up by the Framework. + + In a driver derived from this sample, it's quite likely that this function could + be deleted. + +Arguments: + + Device - Handle to a framework device object. + +Return Value: + + VOID + +--*/ +{ + PSERIAL_DEVICE_EXTENSION deviceExtension; + PULONG countSoFar; + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "--> SerialDeviceContextCleanup\n"); + + PAGED_CODE(); + + deviceExtension = SerialGetDeviceExtension (Device); + + if (deviceExtension->InterruptReadBuffer != NULL) { + ExFreePool(deviceExtension->InterruptReadBuffer); + deviceExtension->InterruptReadBuffer = NULL; + } + + // + // Update the global configuration count for serial device. + // + if(deviceExtension->IsSystemConfigInfoUpdated) { + countSoFar = &IoGetConfigurationInformation()->SerialCount; + (*countSoFar)--; + } + + SerialUndoExternalNaming(deviceExtension); + + return; +} +#pragma warning(pop) // enable 28118 again + +NTSTATUS +SerialEvtPrepareHardware( + WDFDEVICE Device, + WDFCMRESLIST Resources, + WDFCMRESLIST ResourcesTranslated + ) +/*++ + +Routine Description: + + SerialEvtPrepareHardware event callback performs operations that are necessary + to make the device operational. The framework calls the driver's + SerialEvtPrepareHardware callback when the PnP manager sends an IRP_MN_START_DEVICE + request to the driver stack. + +Arguments: + + Device - Handle to a framework device object. + + Resources - Handle to a collection of framework resource objects. + This collection identifies the raw (bus-relative) hardware + resources that have been assigned to the device. + + ResourcesTranslated - Handle to a collection of framework resource objects. + This collection identifies the translated (system-physical) + hardware resources that have been assigned to the device. + The resources appear from the CPU's point of view. + Use this list of resources to map I/O space and + device-accessible memory into virtual address space + +Return Value: + + WDF status code + +--*/ +{ + PSERIAL_DEVICE_EXTENSION pDevExt; + NTSTATUS status; + CONFIG_DATA config; + PCONFIG_DATA pConfig = &config; + ULONG defaultClockRate = 1843200; + + PAGED_CODE(); + + SerialDbgPrintEx (TRACE_LEVEL_INFORMATION, DBG_PNP, "--> SerialEvtPrepareHardware\n"); + // + // Get the Device Extension.. + // + pDevExt = SerialGetDeviceExtension (Device); + + RtlZeroMemory(pConfig, sizeof(CONFIG_DATA)); + + // + // Initialize a config data structure with default values for those that + // may not already be initialized. + // + + pConfig->LogFifo = driverDefaults.LogFifoDefault; + + + // + // Get the hw resources for the device. + // + + status = SerialMapHWResources(Device, Resources, ResourcesTranslated, pConfig); + + if (!NT_SUCCESS(status)) { + goto End; + } + + // + // Open the "Device Parameters" section of registry for this device and get parameters. + // + + if(!SerialGetRegistryKeyValue (Device, + L"DisablePort", + &pConfig->DisablePort)){ + pConfig->DisablePort = 0; + } + + if(!SerialGetRegistryKeyValue (Device, + L"ForceFifoEnable", + &pConfig->ForceFifoEnable)){ + pConfig->ForceFifoEnable = driverDefaults.ForceFifoEnableDefault; + } + + if(!SerialGetRegistryKeyValue (Device, + L"RxFIFO", + &pConfig->RxFIFO)){ + pConfig->RxFIFO = driverDefaults.RxFIFODefault; + } + + if(!SerialGetRegistryKeyValue (Device, + L"TxFIFO", + &pConfig->TxFIFO)){ + pConfig->TxFIFO = driverDefaults.TxFIFODefault; + } + + if(!SerialGetRegistryKeyValue (Device, + L"Share System Interrupt", + &pConfig->PermitShare)){ + pConfig->PermitShare = driverDefaults.PermitShareDefault; + } + + if(!SerialGetRegistryKeyValue (Device, + L"ClockRate", + &pConfig->ClockRate)) { + pConfig->ClockRate = defaultClockRate; + } + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "Com Port ClockRate: %x\n", + pConfig->ClockRate); + + if(!SerialGetRegistryKeyValue(Device, + L"TL16C550C Auto Flow Control", + &pConfig->TL16C550CAFC)){ + pConfig->TL16C550CAFC = 0; + } + + status = SerialInitController(pDevExt, pConfig); + + if (NT_SUCCESS(status)) { + } +End: + + SerialDbgPrintEx (TRACE_LEVEL_INFORMATION, DBG_PNP, "<-- SerialEvtPrepareHardware 0x%x\n", status); + + return status; +} + +NTSTATUS +SerialEvtReleaseHardware( + IN WDFDEVICE Device, + IN WDFCMRESLIST ResourcesTranslated + ) +/*++ + +Routine Description: + + EvtDeviceReleaseHardware is called by the framework whenever the PnP manager + is revoking ownership of our resources. This may be in response to either + IRP_MN_STOP_DEVICE or IRP_MN_REMOVE_DEVICE. The callback is made before + passing down the IRP to the lower driver. + + In this callback, do anything necessary to free those resources. + In this driver, we will not receive this callback when there is open handle to + the device. We explicitly tell the framework (WdfDeviceSetStaticStopRemove) to + fail stop and query-remove when handle is open. + +Arguments: + + Device - Handle to a framework device object. + + ResourcesTranslated - Handle to a collection of framework resource objects. + This collection identifies the translated (system-physical) + hardware resources that have been assigned to the device. + The resources appear from the CPU's point of view. + Use this list of resources to map I/O space and + device-accessible memory into virtual address space + +Return Value: + + NTSTATUS - Failures will be logged, but not acted on. + +--*/ +{ + PSERIAL_DEVICE_EXTENSION pDevExt; + + UNREFERENCED_PARAMETER(ResourcesTranslated); + + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, + "--> SerialEvtReleaseHardware\n"); + + pDevExt = SerialGetDeviceExtension (Device); + + // + // Reset and put the device into a known initial state before releasing the hw resources. + // In this driver we can recieve this callback only when there is no handle open because + // we tell the framework to disable stop by calling WdfDeviceSetStaticStopRemove. + // Since we have already reset the device in our close handler, we don't have to + // do anything other than unmapping the I/O resources. + // + + // + // Unmap any Memory-Mapped registers. Disconnecting from the interrupt will + // be done automatically by the framework. + // + SerialUnmapHWResources(pDevExt); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, + "<-- SerialEvtReleaseHardware\n"); + + return STATUS_SUCCESS; +} + + +NTSTATUS +SerialEvtDeviceD0EntryPostInterruptsEnabled( + IN WDFDEVICE Device, + IN WDF_POWER_DEVICE_STATE PreviousState + ) +/*++ + +Routine Description: + + EvtDeviceD0EntryPostInterruptsEnabled is called by the framework after the + driver has enabled the device's hardware interrupts. + + This function is not marked pageable because this function is in the + device power up path. When a function is marked pagable and the code + section is paged out, it will generate a page fault which could impact + the fast resume behavior because the client driver will have to wait + until the system drivers can service this page fault. + +Arguments: + + Device - Handle to a framework device object. + + PreviousState - A WDF_POWER_DEVICE_STATE-typed enumerator that identifies + the previous device power state. + +Return Value: + + NTSTATUS - Failures will be logged, but not acted on. + +--*/ +{ + PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension(Device); + PSERIAL_INTERRUPT_CONTEXT interruptContext = SerialGetInterruptContext(extension->WdfInterrupt); + WDF_INTERRUPT_INFO info; + + UNREFERENCED_PARAMETER(PreviousState); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, + "--> SerialEvtDeviceD0EntryPostInterruptsEnabled\n"); + // + // The following lines of code show how to call WdfInterruptGetInfo. + // + WDF_INTERRUPT_INFO_INIT(&info); + WdfInterruptGetInfo(extension->WdfInterrupt, &info); + + WdfWaitLockAcquire(interruptContext->InterruptStateLock, NULL); + interruptContext->IsInterruptConnected = TRUE; + WdfWaitLockRelease(interruptContext->InterruptStateLock); + + return STATUS_SUCCESS; +} + + +NTSTATUS +SerialEvtDeviceD0ExitPreInterruptsDisabled( + IN WDFDEVICE Device, + IN WDF_POWER_DEVICE_STATE TargetState + ) +/*++ + +Routine Description: + + EvtDeviceD0ExitPreInterruptsDisabled is called by the framework before the + driver disables the device's hardware interrupts. + +Arguments: + + Device - Handle to a framework device object. + + TargetState - A WDF_POWER_DEVICE_STATE-typed enumerator that identifies the + device power state that the device is about to enter. + +Return Value: + + NTSTATUS - Failures will be logged, but not acted on. + +--*/ +{ + PSERIAL_DEVICE_EXTENSION extension = SerialGetDeviceExtension(Device); + PSERIAL_INTERRUPT_CONTEXT interruptContext = SerialGetInterruptContext(extension->WdfInterrupt); + + UNREFERENCED_PARAMETER(TargetState); + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, + "--> SerialEvtDeviceD0ExitPreInterruptsDisabled\n"); + + WdfWaitLockAcquire(interruptContext->InterruptStateLock, NULL); + interruptContext->IsInterruptConnected = FALSE; + WdfWaitLockRelease(interruptContext->InterruptStateLock); + + return STATUS_SUCCESS; +} + + +NTSTATUS +SerialSetPowerPolicy( + IN PSERIAL_DEVICE_EXTENSION DeviceExtension + ) +{ + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings; + //WDF_POWER_POLICY_EVENT_CALLBACKS powerPolicyCallbacks; + NTSTATUS status = STATUS_SUCCESS; + WDFDEVICE hDevice = DeviceExtension->WdfDevice; + ULONG powerOnClose; + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, + "--> SerialSetPowerPolicy\n"); + + PAGED_CODE(); + + // + // Find out whether we want to power down the device when there no handles open. + // + SerialGetRegistryKeyValue(hDevice, L"EnablePowerManagement", &powerOnClose); + DeviceExtension->RetainPowerOnClose = powerOnClose ? TRUE : FALSE; + + // + // In some drivers, the device must be specifically programmed to enable + // wake signals. UARTs were designed long, long before such a concept. So + // this driver, which just drives UARTs, doesn't register wake arm/disarm + // callbacks. Arming or disarming for UARTs has to be handled by side-band + // code that controls hardware designed more recently. In this case, ACPI + // is handling it. If one were to write a driver which implemented a more + // modern serial device, one might need to use these callbacks. + // + + // + // Init the power policy callbacks + // + //WDF_POWER_POLICY_EVENT_CALLBACKS_INIT(&powerPolicyCallbacks); + + // + // This group of three callbacks allows this sample driver to manage + // arming the device for wake from the S0 state. + // + + //powerPolicyCallbacks.EvtDeviceArmWakeFromS0 = SerialEvtDeviceWakeArmS0; + //powerPolicyCallbacks.EvtDeviceDisarmWakeFromS0 = SerialEvtDeviceWakeDisarmS0; + //powerPolicyCallbacks.EvtDeviceWakeFromS0Triggered = SerialEvtDeviceWakeTriggeredS0; + + // + // This group of three callbacks allows the device to be armed for wake + // from Sx (S1, S2, S3 or S4.) Networking devices can optionally be put + // into a state where a packet sent to them will cause the device's wake + // signal to be triggered, which causes the machine to wake, moving back + // into the S0 state. + // + + //powerPolicyCallbacks.EvtDeviceArmWakeFromSx = SerialEvtDeviceWakeArmSx; + //powerPolicyCallbacks.EvtDeviceDisarmWakeFromSx = SerialEvtDeviceWakeDisarmSx; + //powerPolicyCallbacks.EvtDeviceWakeFromSxTriggered = SerialEvtDeviceWakeTriggeredSx; + + // + // Register the power policy callbacks. + // + //WdfDeviceSetPowerPolicyEventCallbacks(hDevice, &powerPolicyCallbacks); + + // + // Init the idle policy structure. By setting IdleCannotWakeFromS0 we tell the framework + // to power down the device without arming for wake. The only way the device can come + // back to D0 is when we call WdfDeviceStopIdle in SerialEvtDeviceFileCreate. + // We can't choose IdleCanWakeFromS0 by default is because onboard serial ports typically + // don't have wake capability. If the driver is used for plugin boards that does support + // wait-wake, you can update the settings to match that. If MS provided modem driver + // is used on ports that does support wake on ring, then it will update the settings + // by sending an internal ioctl to us. + // + WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(&idleSettings, IdleCannotWakeFromS0); + if(DeviceExtension->OwnsPowerPolicy && !DeviceExtension->RetainPowerOnClose) { + // + // Since we don't have to retain power when there are no open handles, we + // register for idle power management to save power. Check the use of + // WdfDeviceStopIdle in SerialEvtDeviceFileCreate. + // + idleSettings.UserControlOfIdleSettings = IdleAllowUserControl; + + status = WdfDeviceAssignS0IdleSettings(hDevice, &idleSettings); + if ( !NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, + "WdfDeviceSetPowerPolicyS0IdlePolicy failed %x \n", status); + return status; + } + } + + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "<-- SerialSetPowerPolicy\n"); + + return status; +} + +UINT32 +SerialReportMaxBaudRate(ULONG Bauds) +/*++ + +Routine Description: + + This routine returns the max baud rate given a selection of rates + +Arguments: + + Bauds - Bit-encoded list of supported bauds + + + Return Value: + + The max baud rate listed in Bauds + +--*/ +{ + int i; + + PAGED_CODE(); + + for(i=0; SupportedBaudRates[i].BaudRate != SERIAL_BAUD_INVALID; i++) { + + if(Bauds & SupportedBaudRates[i].Mask) { + return SupportedBaudRates[i].BaudRate; + } + } + + // + // We're in bad shape + // + + return 0; +} + +NTSTATUS +SerialInitController( + IN PSERIAL_DEVICE_EXTENSION pDevExt, + IN PCONFIG_DATA PConfigData + ) +/*++ + +Routine Description: + + Really too many things to mention here. In general initializes + kernel synchronization structures, allocates the typeahead buffer, + sets up defaults, etc. + +Arguments: + + PDevObj - Device object for the device to be started + + PConfigData - Pointer to a record for a single port. + +Return Value: + + STATUS_SUCCCESS if everything went ok. A !NT_SUCCESS status + otherwise. + +--*/ + +{ + NTSTATUS status = STATUS_SUCCESS; + SHORT junk; + int i; + + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "--> SerialInitController for %wZ\n", + &pDevExt->DeviceName); + + // + // Save the value of clock input to the part. We use this to calculate + // the divisor latch value. The value is in Hertz. + // + + pDevExt->ClockRate = PConfigData->ClockRate; + + + // + // Save if we have to enable TI's auto flow control + // + + + pDevExt->TL16C550CAFC = PConfigData->TL16C550CAFC; + + + // + // Map the memory for the control registers for the serial device + // into virtual memory. + // + pDevExt->Controller = + SerialGetMappedAddress(PConfigData->TrController, + PConfigData->SpanOfController, + (BOOLEAN)PConfigData->AddressSpace, + &pDevExt->UnMapRegisters); + + + if (!pDevExt->Controller) { + + SerialLogError( + pDevExt->DriverObject, + pDevExt->DeviceObject, + PConfigData->TrController, + SerialPhysicalZero, + 0, + 0, + 0, + 7, + STATUS_SUCCESS, + SERIAL_REGISTERS_NOT_MAPPED, + pDevExt->DeviceName.Length+sizeof(WCHAR), + pDevExt->DeviceName.Buffer, + 0, + NULL + ); + + SerialDbgPrintEx(TRACE_LEVEL_WARNING, DBG_PNP, "Could not map memory for device " + "registers for %wZ\n", &pDevExt->DeviceName); + + pDevExt->UnMapRegisters = FALSE; + status = STATUS_NONE_MAPPED; + goto ExtensionCleanup; + + } + + pDevExt->AddressSpace = PConfigData->AddressSpace; + pDevExt->SpanOfController = PConfigData->SpanOfController; + + // + // Save off the interface type and the bus number. + // + + pDevExt->Vector = PConfigData->TrVector; + pDevExt->Irql = (UCHAR)PConfigData->TrIrql; + pDevExt->InterruptMode = PConfigData->InterruptMode; + pDevExt->Affinity = PConfigData->Affinity; + + // + // If the user said to permit sharing within the device, propagate this + // through. + // + + pDevExt->PermitShare = PConfigData->PermitShare; + + + // + // Before we test whether the port exists (which will enable the FIFO) + // convert the rx trigger value to what should be used in the register. + // + // If a bogus value was given - crank them down to 1. + // + // If this is a "souped up" UART with like a 64 byte FIFO, they + // should use the appropriate "spoofing" value to get the desired + // results. I.e., if on their chip 0xC0 in the FCR is for 64 bytes, + // they should specify 14 in the registry. + // + + switch (PConfigData->RxFIFO) { + + case 1: + + pDevExt->RxFifoTrigger = SERIAL_1_BYTE_HIGH_WATER; + break; + + case 4: + + pDevExt->RxFifoTrigger = SERIAL_4_BYTE_HIGH_WATER; + break; + + case 8: + + pDevExt->RxFifoTrigger = SERIAL_8_BYTE_HIGH_WATER; + break; + + case 14: + + pDevExt->RxFifoTrigger = SERIAL_14_BYTE_HIGH_WATER; + break; + + default: + + pDevExt->RxFifoTrigger = SERIAL_1_BYTE_HIGH_WATER; + break; + + } + + + if (PConfigData->TxFIFO < 1) { + + pDevExt->TxFifoAmount = 1; + + } else { + + pDevExt->TxFifoAmount = PConfigData->TxFIFO; + + } + + if (!SerialDoesPortExist( + pDevExt, + &pDevExt->DeviceName, + PConfigData->ForceFifoEnable, + PConfigData->LogFifo + )) { + + // + // We couldn't verify that there was actually a + // port. No need to log an error as the port exist + // code will log exactly why. + // + + SerialDbgPrintEx(TRACE_LEVEL_WARNING, DBG_PNP, "DoesPortExist test failed for " + "%wZ\n", &pDevExt->DeviceName); + + status = STATUS_NO_SUCH_DEVICE; + goto ExtensionCleanup; + + } + + + // + // If the user requested that we disable the port, then + // do it now. Log the fact that the port has been disabled. + // + + if (PConfigData->DisablePort) { + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "disabled port %wZ as requested in " + "configuration\n", &pDevExt->DeviceName); + + status = STATUS_NO_SUCH_DEVICE; + + SerialLogError( + pDevExt->DriverObject, + pDevExt->DeviceObject, + PConfigData->TrController, + SerialPhysicalZero, + 0, + 0, + 0, + 57, + STATUS_SUCCESS, + SERIAL_DISABLED_PORT, + pDevExt->DeviceName.Length+sizeof(WCHAR), + pDevExt->DeviceName.Buffer, + 0, + NULL + ); + + goto ExtensionCleanup; + + } + + + + // + // Set up the default device control fields. + // Note that if the values are changed after + // the file is open, they do NOT revert back + // to the old value at file close. + // + + pDevExt->SpecialChars.XonChar = SERIAL_DEF_XON; + pDevExt->SpecialChars.XoffChar = SERIAL_DEF_XOFF; + pDevExt->HandFlow.ControlHandShake = SERIAL_DTR_CONTROL; + pDevExt->HandFlow.FlowReplace = SERIAL_RTS_CONTROL; + + + // + // Default Line control protocol. 7E1 + // + // Seven data bits. + // Even parity. + // 1 Stop bits. + // + + pDevExt->LineControl = SERIAL_7_DATA | + SERIAL_EVEN_PARITY | + SERIAL_NONE_PARITY; + + pDevExt->ValidDataMask = 0x7f; + pDevExt->CurrentBaud = 1200; + + + // + // We set up the default xon/xoff limits. + // + // This may be a bogus value. It looks like the BufferSize + // is not set up until the device is actually opened. + // + + pDevExt->HandFlow.XoffLimit = pDevExt->BufferSize >> 3; + pDevExt->HandFlow.XonLimit = pDevExt->BufferSize >> 1; + + pDevExt->BufferSizePt8 = ((3*(pDevExt->BufferSize>>2))+ + (pDevExt->BufferSize>>4)); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, " The default interrupt read buffer size is: %d\n" + "------ The XoffLimit is : %d\n" + "------ The XonLimit is : %d\n" + "------ The pt 8 size is : %d\n", + pDevExt->BufferSize, pDevExt->HandFlow.XoffLimit, + pDevExt->HandFlow.XonLimit, pDevExt->BufferSizePt8); + + + // + // Go through all the "named" baud rates to find out which ones + // can be supported with this port. + // + // + + pDevExt->SupportedBauds = SERIAL_BAUD_USER; + + + for(i=0; SupportedBaudRates[i].BaudRate != SERIAL_BAUD_INVALID; i++) { + + if (!NT_ERROR(SerialGetDivisorFromBaud( + pDevExt->ClockRate, + (LONG)SupportedBaudRates[i].BaudRate, + &junk + ))) { + + pDevExt->SupportedBauds |= SupportedBaudRates[i].Mask; + } + } + + + + + // + // Mark this device as not being opened by anyone. We keep a + // variable around so that spurious interrupts are easily + // dismissed by the ISR. + // + + SetDeviceIsOpened(pDevExt, FALSE, FALSE); + + // + // Store values into the extension for interval timing. + // + + // + // If the interval timer is less than a second then come + // in with a short "polling" loop. + // + // For large (> then 2 seconds) use a 1 second poller. + // + + pDevExt->ShortIntervalAmount.QuadPart = -1; + pDevExt->LongIntervalAmount.QuadPart = -10000000; + pDevExt->CutOverAmount.QuadPart = 200000000; + + DISABLE_ALL_INTERRUPTS (pDevExt, pDevExt->Controller); + + WRITE_MODEM_CONTROL(pDevExt, pDevExt->Controller, (UCHAR)0); + + // make sure there is no escape character currently set + pDevExt->EscapeChar = 0; + // + // This should set up everything as it should be when + // a device is to be opened. We do need to lower the + // modem lines, and disable the recalcitrant fifo + // so that it will show up if the user boots to dos. + // + + // __WARNING_IRQ_SET_TOO_HIGH: we are calling interrupt synchronize routine directly. Suppress it because interrupt is not connected yet. + // __WARNING_INVALID_PARAM_VALUE_1: Interrupt is UNREFERENCED_PARAMETER, so it can be NULL +#pragma warning(suppress: __WARNING_IRQ_SET_TOO_HIGH; suppress: __WARNING_INVALID_PARAM_VALUE_1) + SerialReset(NULL, pDevExt); + +#pragma warning(suppress: __WARNING_IRQ_SET_TOO_HIGH; suppress: __WARNING_INVALID_PARAM_VALUE_1) + SerialMarkClose(NULL, pDevExt); + +#pragma warning(suppress: __WARNING_IRQ_SET_TOO_HIGH; suppress: __WARNING_INVALID_PARAM_VALUE_1) + SerialClrRTS(NULL, pDevExt); + +#pragma warning(suppress: __WARNING_IRQ_SET_TOO_HIGH; suppress: __WARNING_INVALID_PARAM_VALUE_1) + SerialClrDTR(NULL, pDevExt); + + // + // Fill in WMI hardware data + // + pDevExt->WmiHwData.IrqNumber = pDevExt->Irql; + pDevExt->WmiHwData.IrqLevel = pDevExt->Irql; + pDevExt->WmiHwData.IrqVector = pDevExt->Vector; + pDevExt->WmiHwData.IrqAffinityMask = pDevExt->Affinity; + pDevExt->WmiHwData.InterruptType = pDevExt->InterruptMode == Latched + ? SERIAL_WMI_INTTYPE_LATCHED : SERIAL_WMI_INTTYPE_LEVEL; + pDevExt->WmiHwData.BaseIOAddress = (ULONG_PTR)pDevExt->Controller; + + // + // Fill in WMI device state data (as defaults) + // + + pDevExt->WmiCommData.BaudRate = pDevExt->CurrentBaud; + pDevExt->WmiCommData.BitsPerByte = (pDevExt->LineControl & 0x03) + 5; + pDevExt->WmiCommData.ParityCheckEnable = (pDevExt->LineControl & 0x08) + ? TRUE : FALSE; + + switch (pDevExt->LineControl & SERIAL_PARITY_MASK) { + case SERIAL_NONE_PARITY: + pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_NONE; + break; + + case SERIAL_ODD_PARITY: + pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_ODD; + break; + + case SERIAL_EVEN_PARITY: + pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_EVEN; + break; + + case SERIAL_MARK_PARITY: + pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_MARK; + break; + + case SERIAL_SPACE_PARITY: + pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_SPACE; + break; + + default: + ASSERTMSG(0, "Illegal Parity setting for WMI"); + pDevExt->WmiCommData.Parity = SERIAL_WMI_PARITY_NONE; + break; + } + + pDevExt->WmiCommData.StopBits = pDevExt->LineControl & SERIAL_STOP_MASK + ? (pDevExt->WmiCommData.BitsPerByte == 5 ? SERIAL_WMI_STOP_1_5 + : SERIAL_WMI_STOP_2) : SERIAL_WMI_STOP_1; + pDevExt->WmiCommData.XoffCharacter = pDevExt->SpecialChars.XoffChar; + pDevExt->WmiCommData.XoffXmitThreshold = pDevExt->HandFlow.XoffLimit; + pDevExt->WmiCommData.XonCharacter = pDevExt->SpecialChars.XonChar; + pDevExt->WmiCommData.XonXmitThreshold = pDevExt->HandFlow.XonLimit; + pDevExt->WmiCommData.MaximumBaudRate + = SerialReportMaxBaudRate(pDevExt->SupportedBauds); + pDevExt->WmiCommData.MaximumOutputBufferSize = (UINT32)((ULONG)-1); + pDevExt->WmiCommData.MaximumInputBufferSize = (UINT32)((ULONG)-1); + pDevExt->WmiCommData.Support16BitMode = FALSE; + pDevExt->WmiCommData.SupportDTRDSR = TRUE; + pDevExt->WmiCommData.SupportIntervalTimeouts = TRUE; + pDevExt->WmiCommData.SupportParityCheck = TRUE; + pDevExt->WmiCommData.SupportRTSCTS = TRUE; + pDevExt->WmiCommData.SupportXonXoff = TRUE; + pDevExt->WmiCommData.SettableBaudRate = TRUE; + pDevExt->WmiCommData.SettableDataBits = TRUE; + pDevExt->WmiCommData.SettableFlowControl = TRUE; + pDevExt->WmiCommData.SettableParity = TRUE; + pDevExt->WmiCommData.SettableParityCheck = TRUE; + pDevExt->WmiCommData.SettableStopBits = TRUE; + pDevExt->WmiCommData.IsBusy = FALSE; + + // + // Common error path cleanup. If the status is + // bad, get rid of the device extension, device object + // and any memory associated with it. + // + +ExtensionCleanup: ; + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "<-- SerialInitController %x\n", status); + + return status; +} + + +NTSTATUS +SerialMapHWResources( + IN WDFDEVICE Device, + IN WDFCMRESLIST PResList, + IN WDFCMRESLIST PTrResList, + OUT PCONFIG_DATA PConfig + ) +/*++ + +Routine Description: + + This routine will get the configuration information and put + it and the translated values into CONFIG_DATA structures. + +Arguments: + + Device - Handle to a framework device object. + + Resources - Handle to a collection of framework resource objects. + This collection identifies the raw (bus-relative) hardware + resources that have been assigned to the device. + + ResourcesTranslated - Handle to a collection of framework resource objects. + This collection identifies the translated (system-physical) + hardware resources that have been assigned to the device. + The resources appear from the CPU's point of view. + Use this list of resources to map I/O space and + device-accessible memory into virtual address space + +Return Value: + + STATUS_SUCCESS if consistant configuration was found - otherwise. + returns STATUS_SERIAL_NO_DEVICE_INITED. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION pDevExt; + NTSTATUS status = STATUS_SUCCESS; + ULONG i; + PCM_PARTIAL_RESOURCE_DESCRIPTOR pPartialTrResourceDesc, pPartialRawResourceDesc; + ULONG gotInt = 0; + ULONG gotIO = 0; + ULONG ioResIndex = 0; + ULONG curIoIndex = 0; + ULONG gotMem = 0; + BOOLEAN DebugPortInUse = FALSE; + + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "--> SerialMapHWResources\n"); + + // + // Get the DeviceExtension.. + // + pDevExt = SerialGetDeviceExtension (Device); + + if ((PResList == NULL) || (PTrResList == NULL)) { + ASSERT(PResList != NULL); + ASSERT(PTrResList != NULL); + status = STATUS_INSUFFICIENT_RESOURCES; + goto End; + } + + for (i = 0; i < WdfCmResourceListGetCount(PTrResList); i++) { + + pPartialTrResourceDesc = WdfCmResourceListGetDescriptor(PTrResList, i); + pPartialRawResourceDesc = WdfCmResourceListGetDescriptor(PResList, i); + + switch (pPartialTrResourceDesc->Type) { + case CmResourceTypePort: + + ASSERT(!(pPartialTrResourceDesc->u.Port.Length == SERIAL_STATUS_LENGTH)); + + if (gotIO == 0) { + + if (curIoIndex == ioResIndex) { + + gotIO = 1; + PConfig->TrController = pPartialTrResourceDesc->u.Port.Start; + + if (!PConfig->TrController.LowPart) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Bogus port address %x\n", + PConfig->TrController.LowPart); + status = STATUS_DEVICE_CONFIGURATION_ERROR; + goto End; + } + // + // We need the raw address to check if the debugger is using the com port + // + PConfig->Controller = pPartialRawResourceDesc->u.Port.Start; + PConfig->AddressSpace = pPartialTrResourceDesc->Flags; + pDevExt->SerialReadUChar = SerialReadPortUChar; + pDevExt->SerialWriteUChar = SerialWritePortUChar; + + } else { + curIoIndex++; + } + } + + break; + + // + // If this is 8 bytes long and we haven't found any I/O range, + // then this is probably a fancy-pants machine with memory replacing + // IO space + // + case CmResourceTypeMemory: + + ASSERT(!(pPartialTrResourceDesc->u.Port.Length == SERIAL_STATUS_LENGTH)); + + if ((gotMem == 0) && (gotIO == 0) + && (pPartialTrResourceDesc->u.Memory.Length + == (SERIAL_REGISTER_SPAN + SERIAL_STATUS_LENGTH))) { + gotMem = 1; + PConfig->TrController = pPartialTrResourceDesc->u.Memory.Start; + + if (!PConfig->TrController.LowPart) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Bogus I/O memory address %x\n", + PConfig->TrController.LowPart); + status = STATUS_DEVICE_CONFIGURATION_ERROR; + goto End; + } + + PConfig->Controller = pPartialRawResourceDesc->u.Memory.Start; + PConfig->AddressSpace = CM_RESOURCE_PORT_MEMORY; + PConfig->SpanOfController = SERIAL_REGISTER_SPAN; + pDevExt->SerialReadUChar = SerialReadRegisterUChar; + pDevExt->SerialWriteUChar = SerialWriteRegisterUChar; + } + break; + + case CmResourceTypeInterrupt: + if (gotInt == 0) { + gotInt = 1; + PConfig->TrVector = pPartialTrResourceDesc->u.Interrupt.Vector; + + if (!PConfig->TrVector) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Bogus vector 0\n"); + status = STATUS_DEVICE_CONFIGURATION_ERROR; + goto End; + } + + if (pPartialTrResourceDesc->ShareDisposition == CmResourceShareShared) { + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "Sharing interrupt with other devices \n"); + } else { + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "Interrupt is not shared with other devices\n"); + } + + PConfig->TrIrql = pPartialTrResourceDesc->u.Interrupt.Level; + PConfig->Affinity = pPartialTrResourceDesc->u.Interrupt.Affinity; + } + break; + + default: break; + } // switch (pPartialTrResourceDesc->Type) + + } // for (i = 0; i < WdfCollectionGetCount + + if(!((gotMem || gotIO) && gotInt) ) + { + status = STATUS_INSUFFICIENT_RESOURCES; + goto End; + } + + // + // First check what type of AddressSpace this port is in. Then check + // if the debugger is using this port. If it is, set DebugPortInUse to TRUE. + // + if(PConfig->AddressSpace == CM_RESOURCE_PORT_MEMORY) { + + PHYSICAL_ADDRESS KdComPhysical; + + KdComPhysical = MmGetPhysicalAddress(*KdComPortInUse); + + if(KdComPhysical.LowPart == PConfig->Controller.LowPart) { + DebugPortInUse = TRUE; + } + + } else { + // + // This compare is done using **untranslated** values since that is what + // the kernel shoves in regardless of the architecture. + // + + if ((*KdComPortInUse) == (ULongToPtr(PConfig->Controller.LowPart))) { + DebugPortInUse = TRUE; + } + } + + if (DebugPortInUse) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Kernel debugger is using port at " + "address %p\n", *KdComPortInUse); + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Serial driver will not load port\n"); + + SerialLogError( + pDevExt->DriverObject, + NULL, + PConfig->TrController, + SerialPhysicalZero, + 0, + 0, + 0, + 3, + STATUS_SUCCESS, + SERIAL_KERNEL_DEBUGGER_ACTIVE, + pDevExt->DeviceName.Length+sizeof(WCHAR), + pDevExt->DeviceName.Buffer, + 0, + NULL + ); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto End; + } + +End: + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "<-- SerialMapHWResources %x\n", status); + + return status; +} + +VOID +SerialUnmapHWResources( + IN PSERIAL_DEVICE_EXTENSION PDevExt + ) +/*++ + +Routine Description: + + Releases resources (not pool) stored in the device extension. + +Arguments: + + PDevExt - Pointer to the device extension to release resources from. + +Return Value: + + VOID + +--*/ +{ + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "-->SerialUnMapResources(%p)\n", + PDevExt); + PAGED_CODE(); + + // + // If necessary, unmap the device registers. + // + + if (PDevExt->UnMapRegisters) { + MmUnmapIoSpace(PDevExt->Controller, PDevExt->SpanOfController); + PDevExt->UnMapRegisters = FALSE; + } + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "<--SerialUnMapResources\n"); +} + + +NTSTATUS +SerialReadSymName( + IN WDFDEVICE Device, + _Out_writes_bytes_(*SizeOfRegName) PWSTR RegName, + _Inout_ PUSHORT SizeOfRegName + ) +{ + NTSTATUS status; + WDFKEY hKey; + UNICODE_STRING value; + UNICODE_STRING valueName; + USHORT requiredLength; + + PAGED_CODE(); + + value.Buffer = RegName; + value.MaximumLength = *SizeOfRegName; + value.Length = 0; + + status = WdfDeviceOpenRegistryKey(Device, + PLUGPLAY_REGKEY_DEVICE, + STANDARD_RIGHTS_ALL, + WDF_NO_OBJECT_ATTRIBUTES, + &hKey); + + if (NT_SUCCESS (status)) { + // + // Fetch PortName which contains the suggested REG_SZ symbolic name. + // + + + RtlInitUnicodeString(&valueName, L"PortName"); + + status = WdfRegistryQueryUnicodeString (hKey, + &valueName, + &requiredLength, + &value); + + if (!NT_SUCCESS (status)) { + // + // This is for PCMCIA which currently puts the name under Identifier. + // + + RtlInitUnicodeString(&valueName, L"Identifier"); + status = WdfRegistryQueryUnicodeString (hKey, + &valueName, + &requiredLength, + &value); + + if (!NT_SUCCESS(status)) { + // + // Hmm. Either we have to pick a name or bail... + // + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Getting PortName/Identifier failed - %x\n", status); + } + } + + WdfRegistryClose(hKey); + } + + if(NT_SUCCESS(status)) { + // + // NULL terminate the string and return number of characters in the string. + // + if(value.Length > *SizeOfRegName - sizeof(WCHAR)) { + return STATUS_UNSUCCESSFUL; + } + + *SizeOfRegName = value.Length; + RegName[*SizeOfRegName/sizeof(WCHAR)] = UNICODE_NULL; + } + return status; +} + + +NTSTATUS +SerialDoExternalNaming(IN PSERIAL_DEVICE_EXTENSION PDevExt) + +/*++ + +Routine Description: + + This routine will be used to create a symbolic link + to the driver name in the given object directory. + + It will also create an entry in the device map for + this device - IF we could create the symbolic link. + +Arguments: + + Extension - Pointer to the device extension. + +Return Value: + + None. + +--*/ + +{ + NTSTATUS status = STATUS_SUCCESS; + WCHAR pRegName[SYMBOLIC_NAME_LENGTH]; + USHORT nameSize = sizeof(pRegName); + WDFSTRING stringHandle = NULL; + WDF_OBJECT_ATTRIBUTES attributes; + DECLARE_UNICODE_STRING_SIZE(symbolicLinkName,SYMBOLIC_NAME_LENGTH ) ; + + PAGED_CODE(); + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = PDevExt->WdfDevice; + status = WdfStringCreate(NULL, &attributes, &stringHandle); + if(!NT_SUCCESS(status)){ + goto SerialDoExternalNamingError; + } + + status = WdfDeviceRetrieveDeviceName(PDevExt->WdfDevice, stringHandle); + if(!NT_SUCCESS(status)){ + goto SerialDoExternalNamingError; + } + + // + // Since we are storing the buffer pointer of the string handle in our + // extension, we will hold onto string handle until the device is deleted. + // + WdfStringGetUnicodeString(stringHandle, &PDevExt->DeviceName); + + SerialGetRegistryKeyValue(PDevExt->WdfDevice, L"SerialSkipExternalNaming", &PDevExt->SkipNaming); + + if (PDevExt->SkipNaming) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Skipping external naming due to registry settings\n"); + return STATUS_SUCCESS; + } + + status = SerialReadSymName(PDevExt->WdfDevice, pRegName, &nameSize); + if (!NT_SUCCESS(status)) { + goto SerialDoExternalNamingError; + } + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "DosName is %ws\n", pRegName); + + status = RtlUnicodeStringPrintf(&symbolicLinkName, + L"%ws%ws", + L"\\DosDevices\\", + pRegName); + + if (!NT_SUCCESS(status)) { + goto SerialDoExternalNamingError; + } + + status = WdfDeviceCreateSymbolicLink(PDevExt->WdfDevice, &symbolicLinkName); + + if (!NT_SUCCESS(status)) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Couldn't create the symbolic link for port %wZ\n", &symbolicLinkName); + + goto SerialDoExternalNamingError; + + } + + + PDevExt->CreatedSymbolicLink = TRUE; + + status = RtlWriteRegistryValue(RTL_REGISTRY_DEVICEMAP, SERIAL_DEVICE_MAP, + PDevExt->DeviceName.Buffer, + REG_SZ, + pRegName, + nameSize + sizeof(WCHAR)); + + if (!NT_SUCCESS(status)) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Couldn't create the device map entry\n" + "------- for port %ws\n", PDevExt->DeviceName.Buffer); + + goto SerialDoExternalNamingError; + } + + PDevExt->CreatedSerialCommEntry = TRUE; + + // + // Make the device visible via a device association as well. + // The reference string is the eight digit device index + // + status = WdfDeviceCreateDeviceInterface(PDevExt->WdfDevice, + (LPGUID) &GUID_DEVINTERFACE_COMPORT, + NULL); + + if (!NT_SUCCESS (status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "Couldn't register class association\n" + "for port %wZ\n", &PDevExt->DeviceName); + + goto SerialDoExternalNamingError; + } + + return status; + + SerialDoExternalNamingError:; + + // + // Clean up error conditions + // + + PDevExt->DeviceName.Buffer = NULL; + + if (PDevExt->CreatedSerialCommEntry) { + _Analysis_assume_(NULL != PDevExt->DeviceName.Buffer); + RtlDeleteRegistryValue(RTL_REGISTRY_DEVICEMAP, SERIAL_DEVICE_MAP, + PDevExt->DeviceName.Buffer); + } + + if(stringHandle) { + WdfObjectDelete(stringHandle); + } + + return status; +} + + +VOID +SerialUndoExternalNaming(IN PSERIAL_DEVICE_EXTENSION Extension) + +/*++ + +Routine Description: + + This routine will be used to delete a symbolic link + to the driver name in the given object directory. + + It will also delete an entry in the device map for + this device if the symbolic link had been created. + +Arguments: + + Extension - Pointer to the device extension. + +Return Value: + + None. + +--*/ + +{ + + NTSTATUS status; + PWCHAR deviceName = Extension->DeviceName.Buffer; + + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, + "In SerialUndoExternalNaming for extension: " + "%p of port %ws\n", Extension, deviceName); + + // + // Maybe there is nothing for us to do + // + + if (Extension->SkipNaming) { + return; + } + + // + // We're cleaning up here. One reason we're cleaning up + // is that we couldn't allocate space for the NtNameOfPort. + // + + if ((deviceName != NULL) && Extension->CreatedSerialCommEntry) { + + status = RtlDeleteRegistryValue(RTL_REGISTRY_DEVICEMAP, + SERIAL_DEVICE_MAP, + deviceName); + if (!NT_SUCCESS(status)) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, + "Couldn't delete value entry %ws\n", + deviceName); + + } + } +} + +VOID +SerialPurgePendingRequests(PSERIAL_DEVICE_EXTENSION pDevExt) +/*++ + +Routine Description: + + This routine completes any irps pending for the passed device object. + +Arguments: + + PDevObj - Pointer to the device object whose irps must die. + +Return Value: + + VOID + +--*/ +{ + NTSTATUS status; + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, + ">SerialPurgePendingRequests(%p)\n", pDevExt); + + // + // Then cancel all the reads and writes. + // + + SerialPurgeRequests(pDevExt->WriteQueue, &pDevExt->CurrentWriteRequest); + + SerialPurgeRequests(pDevExt->ReadQueue, &pDevExt->CurrentReadRequest); + + // + // Next get rid of purges. + // + + SerialPurgeRequests(pDevExt->PurgeQueue, &pDevExt->CurrentPurgeRequest); + + // + // Get rid of any mask operations. + // + + SerialPurgeRequests( pDevExt->MaskQueue, &pDevExt->CurrentMaskRequest); + + // + // Now get rid of pending wait mask request. + // + + if (pDevExt->CurrentWaitRequest) { + + status = SerialClearCancelRoutine(pDevExt->CurrentWaitRequest, TRUE ); + if (NT_SUCCESS(status)) { + + SerialCompleteRequest(pDevExt->CurrentWaitRequest, STATUS_CANCELLED, 0); + pDevExt->CurrentWaitRequest = NULL; + + } + + } + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, "Controller); + + // + // Make sure that we are *aren't* accessing the divsior latch. + // + + WRITE_LINE_CONTROL(Extension, + Extension->Controller, + (UCHAR)(oldLCRContents & ~SERIAL_LCR_DLAB) + ); + + oldIERContents = READ_INTERRUPT_ENABLE(Extension, Extension->Controller); + + // + // Go up to power level for a very short time to prevent + // any interrupts from this device from coming in. + // + + KeRaiseIrql( + POWER_LEVEL, + &oldIrql + ); + + WRITE_INTERRUPT_ENABLE(Extension, + Extension->Controller, + 0x0f + ); + + value1 = READ_INTERRUPT_ENABLE(Extension, Extension->Controller); + value1 = value1 << 8; + value1 |= READ_RECEIVE_BUFFER(Extension, Extension->Controller); + + READ_DIVISOR_LATCH(Extension, + Extension->Controller, + (PSHORT) &value2 + ); + + WRITE_LINE_CONTROL(Extension, + Extension->Controller, + oldLCRContents + ); + + // + // Put the ier back to where it was before. If we are on a + // level sensitive port this should prevent the interrupts + // from coming in. If we are on a latched, we don't care + // cause the interrupts generated will just get dropped. + // + + WRITE_INTERRUPT_ENABLE(Extension, + Extension->Controller, + oldIERContents + ); + + KeLowerIrql(oldIrql); + + if (value1 == value2) { + + SerialLogError( + Extension->DeviceObject->DriverObject, + Extension->DeviceObject, + SerialPhysicalZero, + SerialPhysicalZero, + 0, + 0, + 0, + 62, + STATUS_SUCCESS, + SERIAL_DLAB_INVALID, + InsertString->Length+sizeof(WCHAR), + InsertString->Buffer, + 0, + NULL + ); + returnValue = FALSE; + goto AllDone; + + } + + AllDone: ; + + + // + // If we think that there is a serial device then we determine + // if a fifo is present. + // + + if (returnValue) { + + // + // Well, we think it's a serial device. Absolutely + // positively, prevent interrupts from occuring. + // + // We disable all the interrupt enable bits, and + // push down all the lines in the modem control + // We only needed to push down OUT2 which in + // PC's must also be enabled to get an interrupt. + // + + DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + + WRITE_MODEM_CONTROL(Extension, Extension->Controller, (UCHAR)0); + + // + // See if this is a 16550. We do this by writing to + // what would be the fifo control register with a bit + // pattern that tells the device to enable fifo's. + // We then read the iterrupt Id register to see if the + // bit pattern is present that identifies the 16550. + // + + WRITE_FIFO_CONTROL(Extension, + Extension->Controller, + SERIAL_FCR_ENABLE + ); + + regContents = READ_INTERRUPT_ID_REG(Extension, Extension->Controller); + + if (regContents & SERIAL_IIR_FIFOS_ENABLED) { + + // + // Save off that the device supports fifos. + // + + Extension->FifoPresent = TRUE; + + // + // There is a fine new "super" IO chip out there that + // will get stuck with a line status interrupt if you + // attempt to clear the fifo and enable it at the same + // time if data is present. The best workaround seems + // to be that you should turn off the fifo read a single + // byte, and then re-enable the fifo. + // + + WRITE_FIFO_CONTROL(Extension, + Extension->Controller, + (UCHAR)0 + ); + + READ_RECEIVE_BUFFER(Extension, Extension->Controller); + + // + // There are fifos on this card. Set the value of the + // receive fifo to interrupt when 4 characters are present. + // + + WRITE_FIFO_CONTROL(Extension, Extension->Controller, + (UCHAR)(SERIAL_FCR_ENABLE + | Extension->RxFifoTrigger + | SERIAL_FCR_RCVR_RESET + | SERIAL_FCR_TXMT_RESET)); + + } + + // + // The !Extension->FifoPresent is included in the test so that + // broken chips like the WinBond will still work after we test + // for the fifo. + // + + if (!ForceFifo || !Extension->FifoPresent) { + + Extension->FifoPresent = FALSE; + WRITE_FIFO_CONTROL(Extension, + Extension->Controller, + (UCHAR)0 + ); + + } + + if (Extension->FifoPresent) { + + if (LogFifo) { + + SerialLogError( + Extension->DeviceObject->DriverObject, + Extension->DeviceObject, + SerialPhysicalZero, + SerialPhysicalZero, + 0, + 0, + 0, + 15, + STATUS_SUCCESS, + SERIAL_FIFO_PRESENT, + InsertString->Length+sizeof(WCHAR), + InsertString->Buffer, + 0, + NULL + ); + + } + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, + "Fifo's detected at port address: %p\n", + Extension->Controller); + } + } + + return returnValue; +} + + + +BOOLEAN +SerialReset( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This places the hardware in a standard configuration. + + NOTE: This assumes that it is called at interrupt level. + + +Arguments: + + Context - The device extension for serial device + being managed. + +Return Value: + + Always FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION extension = Context; + UCHAR regContents; + UCHAR oldModemControl; + ULONG i; + + UNREFERENCED_PARAMETER(Interrupt); + + // + // Adjust the out2 bit. + // This will also prevent any interrupts from occuring. + // + + oldModemControl = READ_MODEM_CONTROL(extension, extension->Controller); + + WRITE_MODEM_CONTROL(extension, extension->Controller, + (UCHAR)(oldModemControl & ~SERIAL_MCR_OUT2)); + + // + // Reset the fifo's if there are any. + // + + if (extension->FifoPresent) { + + // + // There is a fine new "super" IO chip out there that + // will get stuck with a line status interrupt if you + // attempt to clear the fifo and enable it at the same + // time if data is present. The best workaround seems + // to be that you should turn off the fifo read a single + // byte, and then re-enable the fifo. + // + + WRITE_FIFO_CONTROL(extension, + extension->Controller, + (UCHAR)0 + ); + + READ_RECEIVE_BUFFER(extension, extension->Controller); + + WRITE_FIFO_CONTROL(extension, + extension->Controller, + (UCHAR)(SERIAL_FCR_ENABLE | extension->RxFifoTrigger | + SERIAL_FCR_RCVR_RESET | SERIAL_FCR_TXMT_RESET) + ); + + } + + // + // Make sure that the line control set up correct. + // + // 1) Make sure that the Divisor latch select is set + // up to select the transmit and receive register. + // + // 2) Make sure that we aren't in a break state. + // + + regContents = READ_LINE_CONTROL(extension, extension->Controller); + regContents &= ~(SERIAL_LCR_DLAB | SERIAL_LCR_BREAK); + + WRITE_LINE_CONTROL(extension, + extension->Controller, + regContents + ); + + // + // Read the receive buffer until the line status is + // clear. (Actually give up after a 5 reads.) + // + + for (i = 0; + i < 5; + i++ + ) { + #pragma warning(disable: 4127) + if (IsNotNEC_98) { + #pragma warning(default: 4127) + READ_RECEIVE_BUFFER(extension, extension->Controller); + if (!(READ_LINE_STATUS(extension, extension->Controller) & 1)) { + + break; + + } + } else { + // + // I get incorrect data when read enpty buffer. + // But do not read no data! for PC98! + // + if (!(READ_LINE_STATUS(extension, extension->Controller) & 1)) { + + break; + + } + READ_RECEIVE_BUFFER(extension, extension->Controller); + } + + } + + // + // Read the modem status until the low 4 bits are + // clear. (Actually give up after a 5 reads.) + // + + for (i = 0; + i < 1000; + i++ + ) { + + if (!(READ_MODEM_STATUS(extension, extension->Controller) & 0x0f)) { + + break; + + } + + } + + // + // Now we set the line control, modem control, and the + // baud to what they should be. + // + + // + // See if we have to enable special Auto Flow Control + // + + if (extension->TL16C550CAFC) { + oldModemControl = READ_MODEM_CONTROL(extension, extension->Controller); + + WRITE_MODEM_CONTROL(extension, extension->Controller, + (UCHAR)(oldModemControl | SERIAL_MCR_TL16C550CAFE)); + } + + + + SerialSetLineControl(extension->WdfInterrupt, extension); + + SerialSetupNewHandFlow( + extension, + &extension->HandFlow + ); + + SerialHandleModemUpdate( + extension, + FALSE + ); + + { + SHORT appropriateDivisor; + SERIAL_IOCTL_SYNC s; + + SerialGetDivisorFromBaud( extension->ClockRate, + extension->CurrentBaud, + &appropriateDivisor ); + + s.Extension = extension; + s.Data = (PVOID) (ULONG_PTR) appropriateDivisor; + SerialSetBaud(extension->WdfInterrupt, &s); + } + + // + // Enable which interrupts we want to receive. + // + // NOTE NOTE: This does not actually let interrupts + // occur. We must still raise the OUT2 bit in the + // modem control register. We will do that on open. + // + + ENABLE_ALL_INTERRUPTS(extension, extension->Controller); + + // + // Read the interrupt id register until the low bit is + // set. (Actually give up after a 5 reads.) + // + + for (i = 0; + i < 5; + i++ + ) { + + if (READ_INTERRUPT_ID_REG(extension, extension->Controller) & 0x01) { + + break; + + } + + } + + // + // Now we know that nothing could be transmitting at this point + // so we set the HoldingEmpty indicator. + // + + extension->HoldingEmpty = TRUE; + + return FALSE; +} + + + +PVOID +SerialGetMappedAddress( + PHYSICAL_ADDRESS IoAddress, + ULONG NumberOfBytes, + ULONG AddressSpace, + PBOOLEAN MappedAddress + ) + +/*++ + +Routine Description: + + This routine maps an IO address to system address space. + +Arguments: + + IoAddress - base device address to be mapped. + NumberOfBytes - number of bytes for which address is valid. + AddressSpace - Denotes whether the address is in io space or memory. + MappedAddress - indicates whether the address was mapped. + This only has meaning if the address returned + is non-null. + +Return Value: + + Mapped address + +--*/ + +{ + PVOID address; + + PAGED_CODE(); + + // + // Map the device base address into the virtual address space + // if the address is in memory space. + // + + if (!AddressSpace) { + + address = LocalMmMapIoSpace(IoAddress, + NumberOfBytes); + + *MappedAddress = (BOOLEAN)((address)?(TRUE):(FALSE)); + + + } else { + + address = ULongToPtr(IoAddress.LowPart); + *MappedAddress = FALSE; + + } + + return address; +} + +VOID +SerialSetInterruptPolicy( + _In_ WDFINTERRUPT WdfInterrupt + ) +/*++ + +Routine Description: + + This routine shows how to set the interrupt policy preferences. + +Arguments: + + WdfInterrupt - Interrupt object handle. + +Return Value: + + None + +--*/ +{ + WDF_INTERRUPT_EXTENDED_POLICY policyAndGroup; +#ifdef SERIAL_SELECT_INTERRUPT_GROUP + USHORT groupCount = 1; + USHORT group = 0; + UNICODE_STRING funcName; + PFN_KE_GET_ACTIVE_GROUP_COUNT fnKeQueryActiveGroupCount; + PFN_KE_QUERY_GROUP_AFFINITY fnKeQueryGroupAffinity; + KAFFINITY groupAffinity = (KAFFINITY)1; +#endif + + WDF_INTERRUPT_EXTENDED_POLICY_INIT(&policyAndGroup); + policyAndGroup.Priority = WdfIrqPriorityNormal; + +#ifdef SERIAL_SELECT_INTERRUPT_GROUP + // + // If OS supports groups, find how many they are. + // + RtlInitUnicodeString(&funcName, L"KeQueryActiveGroupCount"); + fnKeQueryActiveGroupCount = (PFN_KE_GET_ACTIVE_GROUP_COUNT) + MmGetSystemRoutineAddress(&funcName); + + if (fnKeQueryActiveGroupCount != NULL) { + groupCount = fnKeQueryActiveGroupCount(); + + // + // Make sure there is at least one group for the boot processor. + // + if (0 == groupCount) { + groupCount = 1; + } + } + + if (groupCount <= SERIAL_PREFERRED_INTERRUPT_GROUP) { + group = groupCount - 1; + } + else { + group = SERIAL_PREFERRED_INTERRUPT_GROUP; + } + + // + // Get the group affinity. + // + RtlInitUnicodeString(&funcName, L"KeQueryGroupAffinity"); + fnKeQueryGroupAffinity = (PFN_KE_QUERY_GROUP_AFFINITY) + MmGetSystemRoutineAddress(&funcName); + + if (fnKeQueryGroupAffinity != NULL) { + groupAffinity = fnKeQueryGroupAffinity(group); + + // + // Active groups have at least one processor. + // + if ((KAFFINITY)0 == groupAffinity) { + groupAffinity = (KAFFINITY)1; + } + } + + // + // Initialize group. + // + policyAndGroup.Policy = WdfIrqPolicySpecifiedProcessors; + policyAndGroup.TargetProcessorSetAndGroup.Group = group; + policyAndGroup.TargetProcessorSetAndGroup.Mask = groupAffinity; +#endif + + // + // Set interrupt policy and group preference. + // + WdfInterruptSetExtendedPolicy(WdfInterrupt, &policyAndGroup); +} + diff --git a/tests/projects/wdk/kmdf/serial/power.c b/tests/projects/wdk/kmdf/serial/power.c new file mode 100644 index 000000000..68aad3efa --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/power.c @@ -0,0 +1,331 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + power.c + +Abstract: + + This module contains the code that handles the power IRPs for the serial + driver. + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + + +#if defined(EVENT_TRACING) +#include "power.tmh" +#endif + + +PCHAR +DbgDevicePowerString( + IN WDF_POWER_DEVICE_STATE Type + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGESER,SerialEvtDeviceD0Exit) +#pragma alloc_text(PAGESER,SerialSaveDeviceState) +#endif // ALLOC_PRAGMA + +PCHAR +DbgDevicePowerString( + IN WDF_POWER_DEVICE_STATE Type + ) +/*++ + +Updated Routine Description: + DbgDevicePowerString does not change in this stage of the function driver. + +--*/ +{ + + switch (Type) + { + case WdfPowerDeviceInvalid: + return "WdfPowerDeviceInvalid"; + case WdfPowerDeviceD0: + return "WdfPowerDeviceD0"; + case WdfPowerDeviceD1: + return "WdfPowerDeviceD1"; + case WdfPowerDeviceD2: + return "WdfPowerDeviceD2"; + case WdfPowerDeviceD3: + return "WdfPowerDeviceD3"; + case WdfPowerDeviceD3Final: + return "WdfPowerDeviceD3Final"; + case WdfPowerDevicePrepareForHibernation: + return "WdfPowerDevicePrepareForHibernation"; + case WdfPowerDeviceMaximum: + return "WdfPowerDeviceMaximum"; + default: + return "UnKnown Device Power State"; + } +} + +NTSTATUS +SerialEvtDeviceD0Entry( + IN WDFDEVICE Device, + IN WDF_POWER_DEVICE_STATE PreviousState + ) +/*++ + +Routine Description: + + EvtDeviceD0Entry event callback must perform any operations that are + necessary before the specified device is used. It will be called every + time the hardware needs to be (re-)initialized. This includes after + IRP_MN_START_DEVICE, IRP_MN_CANCEL_STOP_DEVICE, IRP_MN_CANCEL_REMOVE_DEVICE, + IRP_MN_SET_POWER-D0. + + This function is not marked pageable because this function is in the + device power up path. When a function is marked pagable and the code + section is paged out, it will generate a page fault which could impact + the fast resume behavior because the client driver will have to wait + until the system drivers can service this page fault. + + This function runs at PASSIVE_LEVEL, even though it is not paged. A + driver can optionally make this function pageable if DO_POWER_PAGABLE + is set. Even if DO_POWER_PAGABLE isn't set, this function still runs + at PASSIVE_LEVEL. In this case, though, the function absolutely must + not do anything that will cause a page fault. + +Arguments: + + Device - Handle to a framework device object. + + PreviousState - Device power state which the device was in most recently. + If the device is being newly started, this will be + PowerDeviceUnspecified. + +Return Value: + + NTSTATUS + +--*/ +{ + PSERIAL_DEVICE_EXTENSION deviceExtension; + PSERIAL_DEVICE_STATE pDevState; + SHORT divisor; + SERIAL_IOCTL_SYNC S; + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, + "-->SerialEvtDeviceD0Entry - coming from %s\n", DbgDevicePowerString(PreviousState)); + + deviceExtension = SerialGetDeviceExtension (Device); + pDevState = &deviceExtension->DeviceState; + + // + // Restore the state of the UART. First, that involves disabling + // interrupts both via OUT2 and IER. + // + + WRITE_MODEM_CONTROL(deviceExtension, deviceExtension->Controller, 0); + DISABLE_ALL_INTERRUPTS(deviceExtension, deviceExtension->Controller); + + // + // Set the baud rate + // + + SerialGetDivisorFromBaud(deviceExtension->ClockRate, deviceExtension->CurrentBaud, &divisor); + S.Extension = deviceExtension; + S.Data = (PVOID) (ULONG_PTR) divisor; + +#pragma prefast(suppress: __WARNING_INFERRED_IRQ_TOO_LOW, "PFD warning that we are calling interrupt synchronize routine directly. Suppress it because interrupt is disabled above.") + SerialSetBaud(deviceExtension->WdfInterrupt, &S); + + // + // Reset / Re-enable the FIFO's + // + + if (deviceExtension->FifoPresent) { + WRITE_FIFO_CONTROL(deviceExtension, deviceExtension->Controller, (UCHAR)0); + READ_RECEIVE_BUFFER(deviceExtension, deviceExtension->Controller); + WRITE_FIFO_CONTROL(deviceExtension, deviceExtension->Controller, + (UCHAR)(SERIAL_FCR_ENABLE | deviceExtension->RxFifoTrigger + | SERIAL_FCR_RCVR_RESET + | SERIAL_FCR_TXMT_RESET)); + } else { + WRITE_FIFO_CONTROL(deviceExtension, deviceExtension->Controller, (UCHAR)0); + } + + // + // Restore a couple more registers + // + + WRITE_INTERRUPT_ENABLE(deviceExtension, deviceExtension->Controller, pDevState->IER); + WRITE_LINE_CONTROL(deviceExtension, deviceExtension->Controller, pDevState->LCR); + + // + // Clear out any stale interrupts + // + + READ_INTERRUPT_ID_REG(deviceExtension, deviceExtension->Controller); + READ_LINE_STATUS(deviceExtension, deviceExtension->Controller); + READ_MODEM_STATUS(deviceExtension, deviceExtension->Controller); + + // + // TODO: move this code to EvtInterruptEnable. + // + + if (deviceExtension->DeviceState.Reopen == TRUE) { + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, "Reopening device\n"); + + SetDeviceIsOpened(deviceExtension, TRUE, FALSE); + + // + // This enables interrupts on the device! + // + + WRITE_MODEM_CONTROL(deviceExtension, deviceExtension->Controller, + (UCHAR)(pDevState->MCR | SERIAL_MCR_OUT2)); + + // + // Refire the state machine + // + + DISABLE_ALL_INTERRUPTS(deviceExtension, deviceExtension->Controller); + ENABLE_ALL_INTERRUPTS(deviceExtension, deviceExtension->Controller); + } + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, "<--SerialEvtDeviceD0Entry\n"); + + return STATUS_SUCCESS; +} + + +NTSTATUS +SerialEvtDeviceD0Exit( + IN WDFDEVICE Device, + IN WDF_POWER_DEVICE_STATE TargetState + ) +/*++ + +Routine Description: + + EvtDeviceD0Exit event callback must perform any operations that are + necessary before the specified device is moved out of the D0 state. If the + driver needs to save hardware state before the device is powered down, then + that should be done here. + + This function runs at PASSIVE_LEVEL, though it is generally not paged. A + driver can optionally make this function pageable if DO_POWER_PAGABLE is set. + + Even if DO_POWER_PAGABLE isn't set, this function still runs at + PASSIVE_LEVEL. In this case, though, the function absolutely must not do + anything that will cause a page fault. + +Arguments: + + Device - Handle to a framework device object. + + TargetState - Device power state which the device will be put in once this + callback is complete. + +Return Value: + + NTSTATUS + +--*/ +{ + PSERIAL_DEVICE_EXTENSION deviceExtension; + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, + "-->SerialEvtDeviceD0Exit - moving to %s\n", DbgDevicePowerString(TargetState)); + + PAGED_CODE(); + + deviceExtension = SerialGetDeviceExtension (Device); + + if (deviceExtension->DeviceIsOpened == TRUE) { + LARGE_INTEGER charTime; + + SetDeviceIsOpened(deviceExtension, FALSE, TRUE); + + charTime.QuadPart = -SerialGetCharTime(deviceExtension).QuadPart; + + // + // Shut down the chip + // + + SerialDisableUART(deviceExtension); + + // + // Drain the device + // + + SerialDrainUART(deviceExtension, &charTime); + + // + // Save the device state + // + + SerialSaveDeviceState(deviceExtension); + } + else + { + SetDeviceIsOpened(deviceExtension, FALSE, FALSE); + } + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, "<--SerialEvtDeviceD0Exit\n"); + + return STATUS_SUCCESS; +} + + +VOID +SerialSaveDeviceState(IN PSERIAL_DEVICE_EXTENSION PDevExt) +/*++ + +Routine Description: + + This routine saves the device state of the UART + +Arguments: + + PDevExt - Pointer to the device extension for the devobj to save the state + for. + +Return Value: + + VOID + + +--*/ +{ + PSERIAL_DEVICE_STATE pDevState = &PDevExt->DeviceState; + + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, "Entering SerialSaveDeviceState\n"); + + // + // Read necessary registers direct + // + + pDevState->IER = READ_INTERRUPT_ENABLE(PDevExt, PDevExt->Controller); + pDevState->MCR = READ_MODEM_CONTROL(PDevExt, PDevExt->Controller); + pDevState->LCR = READ_LINE_CONTROL(PDevExt, PDevExt->Controller); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_POWER, "Leaving SerialSaveDeviceState\n"); +} + + +VOID +SetDeviceIsOpened(IN PSERIAL_DEVICE_EXTENSION PDevExt, IN BOOLEAN DeviceIsOpened, IN BOOLEAN Reopen) +{ + + PDevExt->DeviceIsOpened = DeviceIsOpened; + PDevExt->DeviceState.Reopen = Reopen; + +} + + + diff --git a/tests/projects/wdk/kmdf/serial/precomp.h b/tests/projects/wdk/kmdf/serial/precomp.h new file mode 100644 index 000000000..7c7c5d5ef --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/precomp.h @@ -0,0 +1,18 @@ + +#include +#include +#define WIN9X_COMPAT_SPINLOCK +#include "ntddk.h" +#include +#define NTSTRSAFE_LIB +#include +#include "ntddser.h" +#include +#include // required for GUID definitions +#include +#include "serial.h" +#include "serialp.h" +#include "serlog.h" +#include "log.h" +#include "trace.h" + diff --git a/tests/projects/wdk/kmdf/serial/precompsrc.c b/tests/projects/wdk/kmdf/serial/precompsrc.c new file mode 100644 index 000000000..5944cf515 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/precompsrc.c @@ -0,0 +1 @@ +#include "precomp.h" \ No newline at end of file diff --git a/tests/projects/wdk/kmdf/serial/purge.c b/tests/projects/wdk/kmdf/serial/purge.c new file mode 100644 index 000000000..fcc32148e --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/purge.c @@ -0,0 +1,175 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + purge.c + +Abstract: + + This module contains the code that is very specific to purge + operations in the serial driver + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "purge.tmh" +#endif + + +VOID +SerialStartPurge( + IN PSERIAL_DEVICE_EXTENSION Extension + ) + +/*++ + +Routine Description: + + Depending on the mask in the current request, purge the interrupt + buffer, the read queue, or the write queue, or all of the above. + +Arguments: + + Extension - Pointer to the device extension. + +Return Value: + + Will return STATUS_SUCCESS always. This is reasonable + since the DPC completion code that calls this routine doesn't + care and the purge request always goes through to completion + once it's started. + +--*/ + +{ + + WDFREQUEST NewRequest; + PREQUEST_CONTEXT reqContext; + + do { + + ULONG Mask; + reqContext = SerialGetRequestContext(Extension->CurrentPurgeRequest); + Mask = *((ULONG *) (reqContext->SystemBuffer)); + + if (Mask & SERIAL_PURGE_TXABORT) { + + SerialFlushRequests( + Extension->WriteQueue, + &Extension->CurrentWriteRequest + ); + + SerialFlushRequests( + Extension->WriteQueue, + &Extension->CurrentXoffRequest + ); + + } + + if (Mask & SERIAL_PURGE_RXABORT) { + + SerialFlushRequests( + Extension->ReadQueue, + &Extension->CurrentReadRequest + ); + + } + + if (Mask & SERIAL_PURGE_RXCLEAR) { + + // + // Clean out the interrupt buffer. + // + // Note that we do this under protection of the + // the drivers control lock so that we don't hose + // the pointers if there is currently a read that + // is reading out of the buffer. + // + + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialPurgeInterruptBuff, + Extension + ); + + } + + reqContext->Status = STATUS_SUCCESS; + reqContext->Information = 0; + + SerialGetNextRequest( + &Extension->CurrentPurgeRequest, + Extension->PurgeQueue, + &NewRequest, + TRUE, + Extension + ); + + } while (NewRequest); + + return; + +} + +BOOLEAN +SerialPurgeInterruptBuff( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine simply resets the interrupt (typeahead) buffer. + + NOTE: This routine is being called from WdfInterruptSynchronize. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + Always false. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + + UNREFERENCED_PARAMETER(Interrupt); + + // + // The typeahead buffer is by definition empty if there + // currently is a read owned by the isr. + // + + + if (Extension->ReadBufferBase == Extension->InterruptReadBuffer) { + + Extension->CurrentCharSlot = Extension->InterruptReadBuffer; + Extension->FirstReadableChar = Extension->InterruptReadBuffer; + Extension->LastCharSlot = Extension->InterruptReadBuffer + + (Extension->BufferSize - 1); + Extension->CharsInInterruptBuffer = 0; + + SerialHandleReducedIntBuffer(Extension); + + } + + return FALSE; + +} + + diff --git a/tests/projects/wdk/kmdf/serial/qsfile.c b/tests/projects/wdk/kmdf/serial/qsfile.c new file mode 100644 index 000000000..ad1604131 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/qsfile.c @@ -0,0 +1,180 @@ +/*++ + +Copyright (c) 1991, 1992, 1993 - 1997 Microsoft Corporation + +Module Name: + + qsfile.c + +Abstract: + + This module contains the code that is very specific to query/set file + operations in the serial driver. + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "qsfile.tmh" +#endif + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGESRP0,SerialQueryInformationFile) +#pragma alloc_text(PAGESRP0,SerialSetInformationFile) +#endif + + +NTSTATUS +SerialQueryInformationFile( + IN WDFDEVICE Device, + IN PIRP Irp + ) + +/*++ + +Routine Description: + + This routine is used to query the end of file information on + the opened serial port. Any other file information request + is retured with an invalid parameter. + + This routine always returns an end of file of 0. + +Arguments: + + DeviceObject - Pointer to the device object for this device + + Irp - Pointer to the IRP for the current request + +Return Value: + + The function value is the final status of the call + +--*/ + +{ + NTSTATUS Status; + PIO_STACK_LOCATION IrpSp; + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, ">SerialQueryInformationFile(%p, %p)\n", Device, Irp); + + PAGED_CODE(); + + + IrpSp = IoGetCurrentIrpStackLocation(Irp); + Irp->IoStatus.Information = 0L; + Status = STATUS_SUCCESS; + + if (IrpSp->Parameters.QueryFile.FileInformationClass == + FileStandardInformation) { + + if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(FILE_STANDARD_INFORMATION)) + { + Status = STATUS_BUFFER_TOO_SMALL; + } + else + { + PFILE_STANDARD_INFORMATION Buf = Irp->AssociatedIrp.SystemBuffer; + + Buf->AllocationSize.QuadPart = 0; + Buf->EndOfFile = Buf->AllocationSize; + Buf->NumberOfLinks = 0; + Buf->DeletePending = FALSE; + Buf->Directory = FALSE; + Irp->IoStatus.Information = sizeof(FILE_STANDARD_INFORMATION); + } + + } else if (IrpSp->Parameters.QueryFile.FileInformationClass == + FilePositionInformation) { + + if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(FILE_POSITION_INFORMATION)) + { + Status = STATUS_BUFFER_TOO_SMALL; + } + else + { + + ((PFILE_POSITION_INFORMATION)Irp->AssociatedIrp.SystemBuffer)-> + CurrentByteOffset.QuadPart = 0; + Irp->IoStatus.Information = sizeof(FILE_POSITION_INFORMATION); + } + + } else { + Status = STATUS_INVALID_PARAMETER; + } + + Irp->IoStatus.Status = Status; + + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return Status; + +} + +NTSTATUS +SerialSetInformationFile( + IN WDFDEVICE Device, + IN PIRP Irp + ) + +/*++ + +Routine Description: + + This routine is used to set the end of file information on + the opened parallel port. Any other file information request + is retured with an invalid parameter. + + This routine always ignores the actual end of file since + the query information code always returns an end of file of 0. + +Arguments: + + DeviceObject - Pointer to the device object for this device + + Irp - Pointer to the IRP for the current request + +Return Value: + +The function value is the final status of the call + +--*/ + +{ + NTSTATUS Status; + + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_PNP, ">SerialSetInformationFile(%p, %p)\n", Device, Irp); + + Irp->IoStatus.Information = 0L; + if ((IoGetCurrentIrpStackLocation(Irp)-> + Parameters.SetFile.FileInformationClass == + FileEndOfFileInformation) || + (IoGetCurrentIrpStackLocation(Irp)-> + Parameters.SetFile.FileInformationClass == + FileAllocationInformation)) { + + Status = STATUS_SUCCESS; + + } else { + + Status = STATUS_INVALID_PARAMETER; + + } + + Irp->IoStatus.Status = Status; + + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return Status; + +} + diff --git a/tests/projects/wdk/kmdf/serial/read.c b/tests/projects/wdk/kmdf/serial/read.c new file mode 100644 index 000000000..ab745fdd7 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/read.c @@ -0,0 +1,1748 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + read.c + +Abstract: + + This module contains the code that is very specific to read + operations in the serial driver + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "read.tmh" +#endif + +EVT_WDF_REQUEST_CANCEL SerialCancelCurrentRead; + +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGrabReadFromIsr; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialUpdateReadByIsr; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialUpdateInterruptBuffer; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialUpdateAndSwitchToUser; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialUpdateAndSwitchToNew; + +ULONG +SerialGetCharsFromIntBuffer( + PSERIAL_DEVICE_EXTENSION Extension + ); + + +NTSTATUS +SerialResizeBuffer( + IN PSERIAL_DEVICE_EXTENSION Extension + ); + +ULONG +SerialMoveToNewIntBuffer( + PSERIAL_DEVICE_EXTENSION Extension, + PUCHAR NewBuffer + ); + +VOID +SerialEvtIoRead( + IN WDFQUEUE Queue, + IN WDFREQUEST Request, + IN size_t Length + ) + +/*++ + +Routine Description: + + This is the dispatch routine for reading. It validates the parameters + for the read request and if all is ok then it places the request + on the work queue. + +Arguments: + + Queue - Queue handle + Request - Handle to the read request + Lenght - Length of the data buffer associated with the request. + The default property of the queue is to not dispatch + zero lenght read & write requests to the driver and + complete is with status success. So we will never get + a zero length request. + +Return Value: + + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION extension; + NTSTATUS status; + WDFDEVICE hDevice; + WDF_REQUEST_PARAMETERS params; + PREQUEST_CONTEXT reqContext; + size_t bufLen; + + hDevice = WdfIoQueueGetDevice(Queue); + extension = SerialGetDeviceExtension(hDevice); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, + ">SerialEvtIoRead(%p, 0x%I64x)\n", Request, Length); + + if (SerialCompleteIfError(extension, Request) != STATUS_SUCCESS) { + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "MajorFunction = params.Type; + reqContext->Length = (ULONG) Length; + + status = WdfRequestRetrieveOutputBuffer (Request, Length, &reqContext->SystemBuffer, &bufLen); + + if (!NT_SUCCESS (status)) { + + SerialCompleteRequest(Request , status, 0); + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_READ, "Length); + + // + // Well it looks like we actually have to do some + // work. Put the read on the queue so that we can + // process it when our previous reads are done. + // + SerialStartOrQueue(extension, Request, extension->ReadQueue, + &extension->CurrentReadRequest, SerialStartRead); + + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "SerialStartRead(%p)\n", Extension); + + updateChar.Extension = Extension; + + + do { + + reqContext = SerialGetRequestContext(Extension->CurrentReadRequest); + + // + // Check to see if this is a resize request. If it is + // then go to a routine that specializes in that. + // + + if (reqContext->MajorFunction != IRP_MJ_READ) { + + NTSTATUS localStatus = SerialResizeBuffer(Extension); + UNREFERENCED_PARAMETER(localStatus); + ASSERT(NT_SUCCESS(localStatus)); + + } else { + + Extension->NumberNeededForRead = reqContext->Length; + + // + // Calculate the timeout value needed for the + // request. Note that the values stored in the + // timeout record are in milliseconds. + // + + useTotalTimer = FALSE; + returnWithWhatsPresent = FALSE; + os2ssreturn = FALSE; + crunchDownToOne = FALSE; + useIntervalTimer = FALSE; + + // + // + // CIMEXCIMEX -- this is a lie + // + // Always initialize the timer objects so that the + // completion code can tell when it attempts to + // cancel the timers whether the timers had ever + // been Set. + // + // CIMEXCIMEX -- this is the truth + // + // What we want to do is just make sure the timers are + // cancelled to the best of our ability and move on with + // life. + // + + SerialCancelTimer(Extension->ReadRequestTotalTimer, Extension); + SerialCancelTimer(Extension->ReadRequestIntervalTimer, Extension); + + // + // We get the *current* timeout values to use for timing + // this read. + // + + + timeoutsForIrp = Extension->Timeouts; + + // + // Calculate the interval timeout for the read. + // + + if (timeoutsForIrp.ReadIntervalTimeout && + (timeoutsForIrp.ReadIntervalTimeout != + MAXULONG)) { + + useIntervalTimer = TRUE; + + Extension->IntervalTime.QuadPart = + UInt32x32To64( + timeoutsForIrp.ReadIntervalTimeout, + 10000 + ); + + + if (Extension->IntervalTime.QuadPart >= + Extension->CutOverAmount.QuadPart) { + + Extension->IntervalTimeToUse = + &Extension->LongIntervalAmount; + + } else { + + Extension->IntervalTimeToUse = + &Extension->ShortIntervalAmount; + + } + + } + + if (timeoutsForIrp.ReadIntervalTimeout == MAXULONG) { + + // + // We need to do special return quickly stuff here. + // + // 1) If both constant and multiplier are + // 0 then we return immediately with whatever + // we've got, even if it was zero. + // + // 2) If constant and multiplier are not MAXULONG + // then return immediately if any characters + // are present, but if nothing is there, then + // use the timeouts as specified. + // + // 3) If multiplier is MAXULONG then do as in + // "2" but return when the first character + // arrives. + // + + if (!timeoutsForIrp.ReadTotalTimeoutConstant && + !timeoutsForIrp.ReadTotalTimeoutMultiplier) { + + returnWithWhatsPresent = TRUE; + + } else if ((timeoutsForIrp.ReadTotalTimeoutConstant != MAXULONG) + && + (timeoutsForIrp.ReadTotalTimeoutMultiplier + != MAXULONG)) { + + useTotalTimer = TRUE; + os2ssreturn = TRUE; + multiplierVal = timeoutsForIrp.ReadTotalTimeoutMultiplier; + constantVal = timeoutsForIrp.ReadTotalTimeoutConstant; + + } else if ((timeoutsForIrp.ReadTotalTimeoutConstant != MAXULONG) + && + (timeoutsForIrp.ReadTotalTimeoutMultiplier + == MAXULONG)) { + + useTotalTimer = TRUE; + os2ssreturn = TRUE; + crunchDownToOne = TRUE; + multiplierVal = 0; + constantVal = timeoutsForIrp.ReadTotalTimeoutConstant; + + } + + } else { + + // + // If both the multiplier and the constant are + // zero then don't do any total timeout processing. + // + + if (timeoutsForIrp.ReadTotalTimeoutMultiplier || + timeoutsForIrp.ReadTotalTimeoutConstant) { + + // + // We have some timer values to calculate. + // + + useTotalTimer = TRUE; + multiplierVal = timeoutsForIrp.ReadTotalTimeoutMultiplier; + constantVal = timeoutsForIrp.ReadTotalTimeoutConstant; + + } + + } + + if (useTotalTimer) { + + totalTime.QuadPart = ((LONGLONG)(UInt32x32To64( + Extension->NumberNeededForRead, + multiplierVal + ) + + constantVal)) + * -10000; + + } + + + // + // We do this copy in the hope of getting most (if not + // all) of the characters out of the interrupt buffer. + // + // Note that we need to protect this operation with a + // spinlock since we don't want a purge to hose us. + // + + updateChar.CharsCopied = SerialGetCharsFromIntBuffer(Extension); + + // + // See if we have any cause to return immediately. + // + + if (returnWithWhatsPresent || (!Extension->NumberNeededForRead) || + (os2ssreturn && + reqContext->Information)) { + + // + // We got all we needed for this read. + // Update the number of characters in the + // interrupt read buffer. + // + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialUpdateInterruptBuffer, + &updateChar + ); + + reqContext->Status = STATUS_SUCCESS; + + } else { + + // + // The request might go under control of the isr. It + // won't hurt to initialize the reference count + // right now. + // + + SERIAL_INIT_REFERENCE(reqContext); + + // + // If we are supposed to crunch the read down to + // one character, then update the read length + // in the request and truncate the number needed for + // read down to one. Note that if we are doing + // this crunching, then the information must be + // zero (or we would have completed above) and + // the number needed for the read must still be + // equal to the read length. + // + + if (crunchDownToOne) { + + ASSERT( + (!reqContext->Information) + && + (Extension->NumberNeededForRead == reqContext->Length) + ); + + Extension->NumberNeededForRead = 1; + reqContext->Length = 1; + + } + + // + // We still need to get more characters for this read. + // synchronize with the isr so that we can update the + // number of characters and if necessary it will have the + // isr switch to copying into the users buffer. + // + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialUpdateAndSwitchToUser, + &updateChar + ); + + if (!updateChar.Completed) { + + SerialSetCancelRoutine(Extension->CurrentReadRequest, + SerialCancelCurrentRead); + + // + // The request still isn't complete. The + // completion routines will end up reinvoking + // this routine. So we simply leave. + // + // First thought we should start off the total + // timer for the read and increment the reference + // count that the total timer has on the current + // request. Note that this is safe, because even if + // the io has been satisfied by the isr it can't + // complete yet because we still own the cancel + // spinlock. + // + + if (useTotalTimer) { + BOOLEAN result; + + result = SerialSetTimer( + Extension->ReadRequestTotalTimer, + totalTime + ); + + if(result == FALSE) { + SERIAL_SET_REFERENCE( + reqContext, + SERIAL_REF_TOTAL_TIMER + ); + } + + } + + if (useIntervalTimer) { + + BOOLEAN result; + + KeQuerySystemTime( + &Extension->LastReadTime + + ); + result = SerialSetTimer( + Extension->ReadRequestIntervalTimer, + *Extension->IntervalTimeToUse + ); + + if(result == FALSE) { + SERIAL_SET_REFERENCE( + reqContext, + SERIAL_REF_INT_TIMER + ); + } + + } + + break; + + } else { + + reqContext->Status = STATUS_SUCCESS; + } + + } + + } + + // + // Well the operation is complete. + // + + SerialGetNextRequest(&Extension->CurrentReadRequest, + Extension->ReadQueue, + &newRequest, TRUE, Extension); + + } while (newRequest); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "SerialCompleteRead(%p)\n", + extension); + + // + // We set this to indicate to the interval timer + // that the read has completed. + // + // Recall that the interval timer dpc can be lurking in some + // DPC queue. + // + + extension->CountOnLastRead = SERIAL_COMPLETE_READ_COMPLETE; + + SerialTryToCompleteCurrent( + extension, + NULL, + STATUS_SUCCESS, + &extension->CurrentReadRequest, + extension->ReadQueue, + extension->ReadRequestIntervalTimer, + extension->ReadRequestTotalTimer, + SerialStartRead, + SerialGetNextRequest, + SERIAL_REF_ISR + ); + + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "CountOnLastRead = SERIAL_COMPLETE_READ_CANCEL; + + SerialTryToCompleteCurrent( + extension, + SerialGrabReadFromIsr, + STATUS_CANCELLED, + &extension->CurrentReadRequest, + extension->ReadQueue, + extension->ReadRequestIntervalTimer, + extension->ReadRequestTotalTimer, + SerialStartRead, + SerialGetNextRequest, + SERIAL_REF_CANCEL + ); + +} + + +BOOLEAN +SerialGrabReadFromIsr( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine is used to grab (if possible) the request from the + isr. If it finds that the isr still owns the request it grabs + the ipr away (updating the number of characters copied into the + users buffer). If it grabs it away it also decrements the + reference count on the request since it no longer belongs to the + isr (and the dpc that would complete it). + + NOTE: This routine assumes that if the current buffer that the + ISR is copying characters into is the interrupt buffer then + the dpc has already been queued. + + NOTE: This routine is being called from WdfInterruptSynchronize. + + NOTE: This routine assumes that it is called with the cancel spin + lock held. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + Always false. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION extension = Context; + PREQUEST_CONTEXT reqContext; + + UNREFERENCED_PARAMETER(Interrupt); + + reqContext = SerialGetRequestContext(extension->CurrentReadRequest); + + if (extension->ReadBufferBase != + extension->InterruptReadBuffer) { + + // + // We need to set the information to the number of characters + // that the read wanted minus the number of characters that + // didn't get read into the interrupt buffer. + // + + reqContext->Information = reqContext->Length - + ((extension->LastCharSlot - extension->CurrentCharSlot) + 1); + + // + // Switch back to the interrupt buffer. + // + + extension->ReadBufferBase = extension->InterruptReadBuffer; + extension->CurrentCharSlot = extension->InterruptReadBuffer; + extension->FirstReadableChar = extension->InterruptReadBuffer; + extension->LastCharSlot = extension->InterruptReadBuffer + + (extension->BufferSize - 1); + extension->CharsInInterruptBuffer = 0; + + SERIAL_CLEAR_REFERENCE( + reqContext, + SERIAL_REF_ISR + ); + + } + + return FALSE; + +} + +VOID +SerialReadTimeout( + IN WDFTIMER Timer + ) + +/*++ + +Routine Description: + + This routine is used to complete a read because its total + timer has expired. + +Arguments: + + +Return Value: + + None. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION extension = NULL; + + extension = SerialGetDeviceExtension(WdfTimerGetParentObject(Timer)); + + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, ">SerialReadTimeout(%p)\n", + extension); + + // + // We set this to indicate to the interval timer + // that the read has completed due to total timeout. + // + // Recall that the interval timer dpc can be lurking in some + // DPC queue. + // + + extension->CountOnLastRead = SERIAL_COMPLETE_READ_TOTAL; + + SerialTryToCompleteCurrent( + extension, + SerialGrabReadFromIsr, + STATUS_TIMEOUT, + &extension->CurrentReadRequest, + extension->ReadQueue, + extension->ReadRequestIntervalTimer, + extension->ReadRequestTotalTimer, + SerialStartRead, + SerialGetNextRequest, + SERIAL_REF_TOTAL_TIMER + ); + + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "CountOnLastRead = extension->ReadByIsr; + extension->ReadByIsr = 0; + + return FALSE; + +} + + +VOID +SerialIntervalReadTimeout( + IN WDFTIMER Timer + ) + +/*++ + +Routine Description: + + This routine is used timeout the request if the time between + characters exceed the interval time. A global is kept in + the device extension that records the count of characters read + the last the last time this routine was invoked (This dpc + will resubmit the timer if the count has changed). If the + count has not changed then this routine will attempt to complete + the request. Note the special case of the last count being zero. + The timer isn't really in effect until the first character is + read. + +Arguments: + + +Return Value: + + None. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION extension = NULL; + + extension = SerialGetDeviceExtension(WdfTimerGetParentObject(Timer)); + + + //SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, ">SerialIntervalReadTimeout(%p)\n", + // extension); + + if (extension->CountOnLastRead == SERIAL_COMPLETE_READ_TOTAL) { + + // + // This value is only set by the total + // timer to indicate that it has fired. + // If so, then we should simply try to complete. + // + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT, "in SERIAL_COMPLETE_READ_TOTAL\n"); + + SerialTryToCompleteCurrent( + extension, + SerialGrabReadFromIsr, + STATUS_TIMEOUT, + &extension->CurrentReadRequest, + extension->ReadQueue, + extension->ReadRequestIntervalTimer, + extension->ReadRequestTotalTimer, + SerialStartRead, + SerialGetNextRequest, + SERIAL_REF_INT_TIMER + ); + + } else if (extension->CountOnLastRead == SERIAL_COMPLETE_READ_COMPLETE) { + + // + // This value is only set by the regular + // completion routine. + // + // If so, then we should simply try to complete. + // + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT, "in SERIAL_COMPLETE_READ_COMPLETE\n"); + + SerialTryToCompleteCurrent( + extension, + SerialGrabReadFromIsr, + STATUS_SUCCESS, + &extension->CurrentReadRequest, + extension->ReadQueue, + extension->ReadRequestIntervalTimer, + extension->ReadRequestTotalTimer, + SerialStartRead, + SerialGetNextRequest, + SERIAL_REF_INT_TIMER + ); + + } else if (extension->CountOnLastRead == SERIAL_COMPLETE_READ_CANCEL) { + + // + // This value is only set by the cancel + // read routine. + // + // If so, then we should simply try to complete. + // + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_INIT, "in SERIAL_COMPLETE_READ_CANCEL\n"); + + SerialTryToCompleteCurrent( + extension, + SerialGrabReadFromIsr, + STATUS_CANCELLED, + &extension->CurrentReadRequest, + extension->ReadQueue, + extension->ReadRequestIntervalTimer, + extension->ReadRequestTotalTimer, + SerialStartRead, + SerialGetNextRequest, + SERIAL_REF_INT_TIMER + ); + + } else if (extension->CountOnLastRead || extension->ReadByIsr) { + + // + // Something has happened since we last came here. We + // check to see if the ISR has read in any more characters. + // If it did then we should update the isr's read count + // and resubmit the timer. + // + + if (extension->ReadByIsr) { + + WdfInterruptSynchronize( + extension->WdfInterrupt, + SerialUpdateReadByIsr, + extension + ); + + // + // Save off the "last" time something was read. + // As we come back to this routine we will compare + // the current time to the "last" time. If the + // difference is ever larger then the interval + // requested by the user, then time out the request. + // + + KeQuerySystemTime( + &extension->LastReadTime + ); + + SerialSetTimer( + extension->ReadRequestIntervalTimer, + *extension->IntervalTimeToUse + ); + + } else { + + // + // Take the difference between the current time + // and the last time we had characters and + // see if it is greater then the interval time. + // if it is, then time out the request. Otherwise + // go away again for a while. + // + + // + // No characters read in the interval time. Kill + // this read. + // + + LARGE_INTEGER currentTime; + + KeQuerySystemTime( + ¤tTime + ); + + if ((currentTime.QuadPart - extension->LastReadTime.QuadPart) >= + extension->IntervalTime.QuadPart) { + + SerialTryToCompleteCurrent( + extension, + SerialGrabReadFromIsr, + STATUS_TIMEOUT, + &extension->CurrentReadRequest, + extension->ReadQueue, + extension->ReadRequestIntervalTimer, + extension->ReadRequestTotalTimer, + SerialStartRead, + SerialGetNextRequest, + SERIAL_REF_INT_TIMER + ); + + } else { + + SerialSetTimer( + extension->ReadRequestIntervalTimer, + *extension->IntervalTimeToUse + ); + + } + + + } + + } else { + + // + // Timer doesn't really start until the first character. + // So we should simply resubmit ourselves. + // + + SerialSetTimer( + extension->ReadRequestIntervalTimer, + *extension->IntervalTimeToUse + ); + + } + + + //SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_READ, "CurrentReadRequest); + + // + // The minimum of the number of characters we need and + // the number of characters available + // + + numberOfCharsToGet = Extension->CharsInInterruptBuffer; + + if (numberOfCharsToGet > Extension->NumberNeededForRead) { + + numberOfCharsToGet = Extension->NumberNeededForRead; + + } + + if (numberOfCharsToGet) { + + // + // This will hold the number of characters between the + // first available character and the end of the buffer. + // Note that the buffer could wrap around but for the + // purposes of the first copy we don't care about that. + // + + firstTryNumberToGet = (ULONG)(Extension->LastCharSlot - + Extension->FirstReadableChar) + 1; + + if (firstTryNumberToGet > numberOfCharsToGet) { + + // + // The characters don't wrap. Actually they may wrap but + // we don't care for the purposes of this read since the + // characters we need are available before the wrap. + // + + RtlMoveMemory( + ((PUCHAR)(reqContext->SystemBuffer)) + + (reqContext->Length - Extension->NumberNeededForRead), + Extension->FirstReadableChar, + numberOfCharsToGet + ); + + Extension->NumberNeededForRead -= numberOfCharsToGet; + + // + // We now will move the pointer to the first character after + // what we just copied into the users buffer. + // + // We need to check if the stream of readable characters + // is wrapping around to the beginning of the buffer. + // + // Note that we may have just taken the last characters + // at the end of the buffer. + // + + if ((Extension->FirstReadableChar + (numberOfCharsToGet - 1)) == + Extension->LastCharSlot) { + + Extension->FirstReadableChar = Extension->InterruptReadBuffer; + + } else { + + Extension->FirstReadableChar += numberOfCharsToGet; + + } + + } else { + + // + // The characters do wrap. Get up until the end of the buffer. + // + + RtlMoveMemory( + ((PUCHAR)(reqContext->SystemBuffer)) + + (reqContext->Length - Extension->NumberNeededForRead), + Extension->FirstReadableChar, + firstTryNumberToGet + ); + + Extension->NumberNeededForRead -= firstTryNumberToGet; + + // + // Now get the rest of the characters from the beginning of the + // buffer. + // + + RtlMoveMemory( + ((PUCHAR)(reqContext->SystemBuffer)) + + (reqContext->Length - Extension->NumberNeededForRead), + Extension->InterruptReadBuffer, + numberOfCharsToGet - firstTryNumberToGet + ); + + Extension->FirstReadableChar = Extension->InterruptReadBuffer + + (numberOfCharsToGet - + firstTryNumberToGet); + + Extension->NumberNeededForRead -= (numberOfCharsToGet - + firstTryNumberToGet); + + } + + } + + reqContext->Information += numberOfCharsToGet; + return numberOfCharsToGet; + +} + + +BOOLEAN +SerialUpdateInterruptBuffer( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine is used to update the number of characters that + remain in the interrupt buffer. We need to use this routine + since the count could be updated during the update by execution + of the ISR. + + NOTE: This is called by WdfInterruptSynchronize. + +Arguments: + + Context - Points to a structure that contains a pointer to the + device extension and count of the number of characters + that we previously copied into the users buffer. The + structure actually has a third field that we don't + use in this routine. + +Return Value: + + Always FALSE. + +--*/ + +{ + + PSERIAL_UPDATE_CHAR update = Context; + PSERIAL_DEVICE_EXTENSION extension = update->Extension; + + UNREFERENCED_PARAMETER(Interrupt); + + ASSERT(extension->CharsInInterruptBuffer >= update->CharsCopied); + extension->CharsInInterruptBuffer -= update->CharsCopied; + + // + // Deal with flow control if necessary. + // + + SerialHandleReducedIntBuffer(extension); + + + return FALSE; + +} + + +BOOLEAN +SerialUpdateAndSwitchToUser( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine gets the (hopefully) few characters that + remain in the interrupt buffer after the first time we tried + to get them out. If we still don't have enough characters + to satisfy the read it will then we set things up so that the + ISR uses the user buffer copy into. + + This routine is also used to update a count that is maintained + by the ISR to keep track of the number of characters in its buffer. + + NOTE: This is called by WdfInterruptSynchronize. + +Arguments: + + Context - Points to a structure that contains a pointer to the + device extension, a count of the number of characters + that we previously copied into the users buffer, and + a boolean that we will set that defines whether we + switched the ISR to copy into the users buffer. + +Return Value: + + Always FALSE. + +--*/ + +{ + + PSERIAL_UPDATE_CHAR updateChar = Context; + PSERIAL_DEVICE_EXTENSION extension = updateChar->Extension; + PREQUEST_CONTEXT reqContext; + + UNREFERENCED_PARAMETER(Interrupt); + + reqContext = SerialGetRequestContext(extension->CurrentReadRequest); + + SerialUpdateInterruptBuffer(extension->WdfInterrupt, Context); + + // + // There are more characters to get to satisfy this read. + // Copy any characters that have arrived since we got + // the last batch. + // + + updateChar->CharsCopied = SerialGetCharsFromIntBuffer(extension); + + SerialUpdateInterruptBuffer(extension->WdfInterrupt, Context); + + // + // No more new characters will be "received" until we exit + // this routine. We again check to make sure that we + // haven't satisfied this read, and if we haven't we set things + // up so that the ISR copies into the user buffer. + // + + if (extension->NumberNeededForRead) { + + // + // We shouldn't be switching unless there are no + // characters left. + // + + ASSERT(!extension->CharsInInterruptBuffer); + + // + // We use the following to values to do inteval timing. + // + // CountOnLastRead is mostly used to simply prevent + // the interval timer from timing out before any characters + // are read. (Interval timing should only be effective + // after the first character is read.) + // + // After the first time the interval timer fires and + // characters have be read we will simply update with + // the value of ReadByIsr and then set ReadByIsr to zero. + // (We do that in a synchronization routine. + // + // If the interval timer dpc routine ever encounters + // ReadByIsr == 0 when CountOnLastRead is non-zero it + // will timeout the read. + // + // (Note that we have a special case of CountOnLastRead + // < 0. This is done by the read completion routines other + // than the total timeout dpc to indicate that the total + // timeout has expired.) + // + + extension->CountOnLastRead = (LONG)reqContext->Information; + + extension->ReadByIsr = 0; + + // + // By compareing the read buffer base address to the + // the base address of the interrupt buffer the ISR + // can determine whether we are using the interrupt + // buffer or the user buffer. + // + + extension->ReadBufferBase = reqContext->SystemBuffer; + + // + // The current char slot is after the last copied in + // character. We know there is always room since we + // we wouldn't have gotten here if there wasn't. + // + + extension->CurrentCharSlot = extension->ReadBufferBase + + reqContext->Information; + + // + // The last position that a character can go is on the + // last byte of user buffer. While the actual allocated + // buffer space may be bigger, we know that there is at + // least as much as the read length. + // + + extension->LastCharSlot = extension->ReadBufferBase + + (reqContext->Length - 1); +#if 0 // We set the cancel before calling this routine in StartRead + // + // Mark the request as being in a cancelable state. + // + IoSetCancelRoutine( + extension->CurrentReadIrp, + SerialCancelCurrentRead + ); + + SERIAL_SET_REFERENCE( + reqContext, + SERIAL_REF_CANCEL + ); +#endif + // + // Increment the reference count twice. + // + // Once for the Isr owning the request and once + // because the cancel routine has a reference + // to it. + // + + SERIAL_SET_REFERENCE( + reqContext, + SERIAL_REF_ISR + ); + + updateChar->Completed = FALSE; + + } else { + + updateChar->Completed = TRUE; + + } + + return FALSE; + +} +// +// We use this structure only to communicate to the synchronization +// routine when we are switching to the resized buffer. +// +typedef struct _SERIAL_RESIZE_PARAMS { + PSERIAL_DEVICE_EXTENSION Extension; + PUCHAR OldBuffer; + PUCHAR NewBuffer; + ULONG NewBufferSize; + ULONG NumberMoved; + } SERIAL_RESIZE_PARAMS,*PSERIAL_RESIZE_PARAMS; + + +NTSTATUS +SerialResizeBuffer( + IN PSERIAL_DEVICE_EXTENSION Extension + ) + +/*++ + +Routine Description: + + This routine will process the resize buffer request. + If size requested for the RX buffer is smaller than + the current buffer then we will simply return + STATUS_SUCCESS. (We don't want to make buffers smaller. + If we did that then we all of a sudden have "overrun" + problems to deal with as well as flow control to deal + with - very painful.) We ignore the TX buffer size + request since we don't use a TX buffer. + +Arguments: + + Extension - Pointer to the device extension for the port. + +Return Value: + + STATUS_SUCCESS if everything worked out ok. + STATUS_INSUFFICIENT_RESOURCES if we couldn't allocate the + memory for the buffer. + +--*/ + +{ + + PREQUEST_CONTEXT reqContext = SerialGetRequestContext(Extension->CurrentReadRequest); + PSERIAL_QUEUE_SIZE rs = reqContext->SystemBuffer; + + PVOID newBuffer = reqContext->Type3InputBuffer; + + + reqContext->Type3InputBuffer = NULL; + reqContext->Information = 0L; + reqContext->Status = STATUS_SUCCESS; + + if (rs->InSize <= Extension->BufferSize) { + + // + // Nothing to do. We don't make buffers smaller. Just + // agree with the user. We must deallocate the memory + // that was already allocated in the ioctl dispatch routine. + // + + ExFreePool(newBuffer); + + } else { + + SERIAL_RESIZE_PARAMS rp; + + // + // Hmmm, looks like we actually have to go + // through with this. We need to move all the + // data that is in the current buffer into this + // new buffer. We'll do this in two steps. + // + // First we go up to dispatch level and try to + // move as much as we can without stopping the + // ISR from running. We go up to dispatch level + // by acquiring the control lock. We do it at + // dispatch using the control lock so that: + // + // 1) We can't be context switched in the middle + // of the move. Our pointers into the buffer + // could be *VERY* stale by the time we got back. + // + // 2) We use the control lock since we don't want + // some pesky purge request to come along while + // we are trying to move. + // + // After the move, but while we still hold the control + // lock, we synch with the ISR and get those last + // (hopefully) few characters that have come in since + // we started the copy. We switch all of our pointers, + // counters, and such to point to this new buffer. NOTE: + // we need to be careful. If the buffer we were using + // was not the default one created when we initialized + // the device (i.e. it was created via a previous WDFREQUEST of + // this type), we should deallocate it. + // + + rp.Extension = Extension; + rp.OldBuffer = Extension->InterruptReadBuffer; + rp.NewBuffer = newBuffer; + rp.NewBufferSize = rs->InSize; + + rp.NumberMoved = SerialMoveToNewIntBuffer( + Extension, + newBuffer + ); + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialUpdateAndSwitchToNew, + &rp + ); + + // + // Free up the memory that the old buffer consumed. + // + + ExFreePool(rp.OldBuffer); + + } + + return STATUS_SUCCESS; + +} + + +ULONG +SerialMoveToNewIntBuffer( + PSERIAL_DEVICE_EXTENSION Extension, + PUCHAR NewBuffer + ) + +/*++ + +Routine Description: + + This routine is used to copy any characters out of the interrupt + buffer into the "new" buffer. It will be reading values that + are updated with the ISR but this is safe since this value is + only decremented by synchronization routines. This routine will + return the number of characters copied so some other routine + can call a synchronization routine to update what is seen at + interrupt level. + +Arguments: + + Extension - A pointer to the device extension. + NewBuffer - Where the characters are to be move to. + +Return Value: + + The number of characters that were copied into the user + buffer. + +--*/ + +{ + + ULONG numberOfCharsMoved = Extension->CharsInInterruptBuffer; + + + if (numberOfCharsMoved) { + + // + // This holds the number of characters between the first + // readable character and the last character we will read or + // the real physical end of the buffer (not the last readable + // character). + // + ULONG firstTryNumberToGet = (ULONG)(Extension->LastCharSlot - + Extension->FirstReadableChar) + 1; + + if (firstTryNumberToGet >= numberOfCharsMoved) { + + // + // The characters don't wrap. + // + + RtlMoveMemory( + NewBuffer, + Extension->FirstReadableChar, + numberOfCharsMoved + ); + + if ((Extension->FirstReadableChar+(numberOfCharsMoved-1)) == + Extension->LastCharSlot) { + + Extension->FirstReadableChar = Extension->InterruptReadBuffer; + + } else { + + Extension->FirstReadableChar += numberOfCharsMoved; + + } + + } else { + + // + // The characters do wrap. Get up until the end of the buffer. + // + + RtlMoveMemory( + NewBuffer, + Extension->FirstReadableChar, + firstTryNumberToGet + ); + + // + // Now get the rest of the characters from the beginning of the + // buffer. + // + + RtlMoveMemory( + NewBuffer+firstTryNumberToGet, + Extension->InterruptReadBuffer, + numberOfCharsMoved - firstTryNumberToGet + ); + + Extension->FirstReadableChar = Extension->InterruptReadBuffer + + numberOfCharsMoved - firstTryNumberToGet; + + } + + } + + return numberOfCharsMoved; + +} + + +BOOLEAN +SerialUpdateAndSwitchToNew( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine gets the (hopefully) few characters that + remain in the interrupt buffer after the first time we tried + to get them out. + + NOTE: This is called by WdfInterruptSynchronize. + +Arguments: + + Context - Points to a structure that contains a pointer to the + device extension, a pointer to the buffer we are moving + to, and a count of the number of characters + that we previously copied into the new buffer, and the + actual size of the new buffer. + +Return Value: + + Always FALSE. + +--*/ + +{ + + PSERIAL_RESIZE_PARAMS params = Context; + PSERIAL_DEVICE_EXTENSION extension = params->Extension; + ULONG tempCharsInInterruptBuffer = extension->CharsInInterruptBuffer; + + UNREFERENCED_PARAMETER(Interrupt); + + ASSERT(extension->CharsInInterruptBuffer >= params->NumberMoved); + + // + // We temporarily reduce the chars in interrupt buffer to + // "fool" the move routine. We will restore it after the + // move. + // + + extension->CharsInInterruptBuffer -= params->NumberMoved; + + if (extension->CharsInInterruptBuffer) { + + SerialMoveToNewIntBuffer( + extension, + params->NewBuffer + params->NumberMoved + ); + + } + + extension->CharsInInterruptBuffer = tempCharsInInterruptBuffer; + + + extension->LastCharSlot = params->NewBuffer + (params->NewBufferSize - 1); + extension->FirstReadableChar = params->NewBuffer; + extension->ReadBufferBase = params->NewBuffer; + extension->InterruptReadBuffer = params->NewBuffer; + extension->BufferSize = params->NewBufferSize; + + // + // We *KNOW* that the new interrupt buffer is larger than the + // old buffer. We don't need to worry about it being full. + // + + extension->CurrentCharSlot = extension->InterruptReadBuffer + + extension->CharsInInterruptBuffer; + + // + // We set up the default xon/xoff limits. + // + + extension->HandFlow.XoffLimit = extension->BufferSize >> 3; + extension->HandFlow.XonLimit = extension->BufferSize >> 1; + + extension->WmiCommData.XoffXmitThreshold = extension->HandFlow.XoffLimit; + extension->WmiCommData.XonXmitThreshold = extension->HandFlow.XonLimit; + + extension->BufferSizePt8 = ((3*(extension->BufferSize>>2))+ + (extension->BufferSize>>4)); + + // + // Since we (essentially) reduced the percentage of the interrupt + // buffer being full, we need to handle any flow control. + // + + SerialHandleReducedIntBuffer(extension); + + return FALSE; + +} + + diff --git a/tests/projects/wdk/kmdf/serial/registry.c b/tests/projects/wdk/kmdf/serial/registry.c new file mode 100644 index 000000000..5ab25955a --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/registry.c @@ -0,0 +1,443 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + registry.c + +Abstract: + + This module contains the code that is used to get values from the + registry and to manipulate entries in the registry. + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "registry.tmh" +#endif + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(INIT,SerialGetConfigDefaults) +#pragma alloc_text(PAGESRP0,SerialGetRegistryKeyValue) +#pragma alloc_text(PAGESRP0,SerialPutRegistryKeyValue) +#pragma alloc_text(PAGESRP0,SerialGetFdoRegistryKeyValue) +#endif // ALLOC_PRAGMA + + +#define PARAMATER_NAME_LEN 80 + + +NTSTATUS +SerialGetConfigDefaults( + IN PSERIAL_FIRMWARE_DATA DriverDefaultsPtr, + IN WDFDRIVER Driver + ) + +/*++ + +Routine Description: + + This routine reads the default configuration data from the + registry for the serial driver. + + It also builds fields in the registry for several configuration + options if they don't exist. + +Arguments: + + DriverDefaultsPtr - Pointer to a structure that will contain + the default configuration values. + + RegistryPath - points to the entry for this driver in the + current control set of the registry. + +Return Value: + + STATUS_SUCCESS if we got the defaults, otherwise we failed. + The only way to fail this call is if the STATUS_INSUFFICIENT_RESOURCES. + +--*/ + +{ + + NTSTATUS status = STATUS_SUCCESS; // return value + WDFKEY hKey; + DECLARE_UNICODE_STRING_SIZE(valueName,PARAMATER_NAME_LEN); + + status = WdfDriverOpenParametersRegistryKey(Driver, + STANDARD_RIGHTS_ALL, + WDF_NO_OBJECT_ATTRIBUTES, + &hKey); + if (!NT_SUCCESS (status)) { + return status; + } + + status = RtlUnicodeStringPrintf(&valueName,L"BreakOnEntry"); + if (!NT_SUCCESS (status)) { + goto End; + + } + + status = WdfRegistryQueryULong (hKey, + &valueName, + &DriverDefaultsPtr->ShouldBreakOnEntry); + + if (!NT_SUCCESS (status)) { + DriverDefaultsPtr->ShouldBreakOnEntry = 0; + } + + status = RtlUnicodeStringPrintf(&valueName,L"DebugLevel"); + if (!NT_SUCCESS (status)) { + goto End; + } + + status = WdfRegistryQueryULong (hKey, + &valueName, + &DriverDefaultsPtr->DebugLevel); + + if (!NT_SUCCESS (status)) { + DriverDefaultsPtr->DebugLevel = 0; + } + + + status = RtlUnicodeStringPrintf(&valueName,L"ForceFifoEnable"); + if (!NT_SUCCESS (status)) { + goto End; + } + + status = WdfRegistryQueryULong (hKey, + &valueName, + &DriverDefaultsPtr->ForceFifoEnableDefault); + + if (!NT_SUCCESS (status)) { + + // + // If it isn't then write out values so that it could + // be adjusted later. + // + DriverDefaultsPtr->ForceFifoEnableDefault = SERIAL_FORCE_FIFO_DEFAULT; + + status = WdfRegistryAssignULong(hKey, + &valueName, + DriverDefaultsPtr->ForceFifoEnableDefault + ); + if (!NT_SUCCESS (status)) { + goto End; + } + + } + + status = RtlUnicodeStringPrintf(&valueName,L"RxFIFO"); + if (!NT_SUCCESS (status)) { + goto End; + } + + status = WdfRegistryQueryULong (hKey, + &valueName, + &DriverDefaultsPtr->RxFIFODefault); + + if (!NT_SUCCESS (status)) { + + DriverDefaultsPtr->RxFIFODefault = SERIAL_RX_FIFO_DEFAULT; + + status = WdfRegistryAssignULong(hKey, + &valueName, + DriverDefaultsPtr->RxFIFODefault + ); + if (!NT_SUCCESS (status)) { + goto End; + } + + } + + status = RtlUnicodeStringPrintf(&valueName,L"TxFIFO"); + if (!NT_SUCCESS (status)) { + goto End; + } + + status = WdfRegistryQueryULong (hKey, + &valueName, + &DriverDefaultsPtr->TxFIFODefault); + + if (!NT_SUCCESS (status)) { + + DriverDefaultsPtr->TxFIFODefault = SERIAL_TX_FIFO_DEFAULT; + + status = WdfRegistryAssignULong(hKey, + &valueName, + DriverDefaultsPtr->TxFIFODefault + ); + if (!NT_SUCCESS (status)) { + goto End; + } + + } + + status = RtlUnicodeStringPrintf(&valueName,L"PermitShare"); + if (!NT_SUCCESS (status)) { + goto End; + } + + status = WdfRegistryQueryULong (hKey, + &valueName, + &DriverDefaultsPtr->PermitShareDefault); + + if (!NT_SUCCESS (status)) { + + DriverDefaultsPtr->PermitShareDefault = SERIAL_PERMIT_SHARE_DEFAULT; + + status = WdfRegistryAssignULong(hKey, + &valueName, + DriverDefaultsPtr->PermitShareDefault + ); + if (!NT_SUCCESS (status)) { + goto End; + } + + } + + status = RtlUnicodeStringPrintf(&valueName,L"LogFifo"); + if (!NT_SUCCESS (status)) { + goto End; + } + + status = WdfRegistryQueryULong (hKey, + &valueName, + &DriverDefaultsPtr->LogFifoDefault); + + if (!NT_SUCCESS (status)) { + + DriverDefaultsPtr->LogFifoDefault = SERIAL_LOG_FIFO_DEFAULT; + + status = WdfRegistryAssignULong(hKey, + &valueName, + DriverDefaultsPtr->LogFifoDefault + ); + if (!NT_SUCCESS (status)) { + goto End; + } + + DriverDefaultsPtr->LogFifoDefault = 1; + } + + + status = RtlUnicodeStringPrintf(&valueName,L"UartRemovalDetect"); + if (!NT_SUCCESS (status)) { + goto End; + } + + status = WdfRegistryQueryULong (hKey, + &valueName, + &DriverDefaultsPtr->UartRemovalDetect); + + if (!NT_SUCCESS (status)) { + DriverDefaultsPtr->UartRemovalDetect = 0; + } + + +End: + WdfRegistryClose(hKey); + return (status); +} + +BOOLEAN +SerialGetRegistryKeyValue( + IN WDFDEVICE WdfDevice, + _In_ PCWSTR Name, + OUT PULONG Value + ) +/*++ + +Routine Description: + + Can be used to read any REG_DWORD registry value stored + under Device Parameter. + +Arguments: + + FdoData - pointer to the device extension + Name - Name of the registry value + Value - + + +Return Value: + + TRUE if successful + FALSE if not present/error in reading registry + +--*/ +{ + WDFKEY hKey = NULL; + NTSTATUS status; + BOOLEAN retValue = FALSE; + UNICODE_STRING valueName; + + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, ">SerialGetRegistryKeyValue(XXX)\n"); + + *Value = 0; + + status = WdfDeviceOpenRegistryKey(WdfDevice, + PLUGPLAY_REGKEY_DEVICE, + STANDARD_RIGHTS_ALL, + WDF_NO_OBJECT_ATTRIBUTES, + &hKey); + + if (NT_SUCCESS (status)) { + + RtlInitUnicodeString(&valueName,Name); + + status = WdfRegistryQueryULong (hKey, + &valueName, + Value); + + if (NT_SUCCESS (status)) { + retValue = TRUE; + } + + WdfRegistryClose(hKey); + } + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "<--SerialGetRegistryKeyValue %ws %d \n", + Name, *Value); + + return retValue; +} + +#define PARAMATER_NAME_LEN 80 + +BOOLEAN +SerialPutRegistryKeyValue( + IN WDFDEVICE WdfDevice, + _In_ PCWSTR Name, + IN ULONG Value + ) +/*++ + +Routine Description: + + Can be used to write any REG_DWORD registry value stored + under Device Parameter. + +Arguments: + + +Return Value: + + TRUE - if write is successful + FALSE - otherwise + +--*/ +{ + WDFKEY hKey = NULL; + NTSTATUS status; + BOOLEAN retValue = FALSE; + UNICODE_STRING valueName; + + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, "Entered PciDrvWriteRegistryValue\n"); + + // + // write the value out to the registry + // + status = WdfDeviceOpenRegistryKey(WdfDevice, + PLUGPLAY_REGKEY_DEVICE, + STANDARD_RIGHTS_ALL, + WDF_NO_OBJECT_ATTRIBUTES, + &hKey); + + if (NT_SUCCESS (status)) { + + RtlInitUnicodeString(&valueName,Name); + + status = WdfRegistryAssignULong (hKey, + &valueName, + Value + ); + + if (NT_SUCCESS (status)) { + retValue = TRUE; + } + + WdfRegistryClose(hKey); + } + + return retValue; + +} + +BOOLEAN +SerialGetFdoRegistryKeyValue( + IN PWDFDEVICE_INIT DeviceInit, + _In_ PCWSTR Name, + OUT PULONG Value + ) +/*++ + +Routine Description: + + Can be used to read any REG_DWORD registry value stored + under Device Parameter. + +Arguments: + + FdoData - pointer to the device extension + Name - Name of the registry value + Value - + + +Return Value: + + TRUE if successful + FALSE if not present/error in reading registry + +--*/ +{ + WDFKEY hKey = NULL; + NTSTATUS status; + BOOLEAN retValue = FALSE; + UNICODE_STRING valueName; + + PAGED_CODE(); + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, + "-->SerialGetFdoRegistryKeyValue\n"); + + *Value = 0; + + status = WdfFdoInitOpenRegistryKey(DeviceInit, + PLUGPLAY_REGKEY_DEVICE, + STANDARD_RIGHTS_ALL, + WDF_NO_OBJECT_ATTRIBUTES, + &hKey); + + if (NT_SUCCESS (status)) { + + RtlInitUnicodeString(&valueName,Name); + + status = WdfRegistryQueryULong (hKey, &valueName, Value); + + if (NT_SUCCESS (status)) { + retValue = TRUE; + } + + WdfRegistryClose(hKey); + } + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, + "<--SerialGetFdoRegistryKeyValue %ws %d \n", + Name, *Value); + + return retValue; +} + + diff --git a/tests/projects/wdk/kmdf/serial/serial.h b/tests/projects/wdk/kmdf/serial/serial.h new file mode 100644 index 000000000..fcbf9748f --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/serial.h @@ -0,0 +1,1757 @@ +/*++ + +Copyright (c) 1990, 1991, 1992, 1993 - 1997 Microsoft Corporation + +Module Name : + + serial.h + +Abstract: + + Type definitions and data for the serial port driver + +--*/ + +#define POOL_TAG 'XMOC' + + +// +// Some default driver values. We will check the registry for +// them first. +// +#define SERIAL_UNINITIALIZED_DEFAULT 1234567 +#define SERIAL_FORCE_FIFO_DEFAULT 1 +#define SERIAL_RX_FIFO_DEFAULT 8 +#define SERIAL_TX_FIFO_DEFAULT 14 +#define SERIAL_PERMIT_SHARE_DEFAULT 0 +#define SERIAL_LOG_FIFO_DEFAULT 0 + + +// +// This define gives the default Object directory +// that we should use to insert the symbolic links +// between the NT device name and namespace used by +// that object directory. +#define DEFAULT_DIRECTORY L"DosDevices" + +// +// For the above directory, the serial port will +// use the following name as the suffix of the serial +// ports for that directory. It will also append +// a number onto the end of the name. That number +// will start at 1. +#define DEFAULT_SERIAL_NAME L"COM" +// +// +// This define gives the default NT name for +// for serial ports detected by the firmware. +// This name will be appended to Device prefix +// with a number following it. The number is +// incremented each time encounter a serial +// port detected by the firmware. Note that +// on a system with multiple busses, this means +// that the first port on a bus is not necessarily +// \Device\Serial0. +// +#define DEFAULT_NT_SUFFIX L"Serial" +#define _DRIVER_NAME_ "Serial.sys" + +#define DEVICE_OBJECT_NAME_LENGTH 128 +#define SYMBOLIC_NAME_LENGTH 128 +#define SERIAL_DEVICE_MAP L"SERIALCOMM" + +// +// GUID_DEVINTERFACE_COMPORT is not defined in the Win2K +// headers, so we will need this definition to avoid compilation +// errors. +// +#define GUID_DEVINTERFACE_COMPORT GUID_CLASS_COMPORT + +// +// This value - which could be redefined at compile +// time, define the stride between registers +// +#if !defined(SERIAL_REGISTER_STRIDE) +#define SERIAL_REGISTER_STRIDE 1 +#endif + +// +// Offsets from the base register address of the +// various registers for the 8250 family of UARTS. +// +#define RECEIVE_BUFFER_REGISTER ((ULONG)((0x00)*SERIAL_REGISTER_STRIDE)) +#define TRANSMIT_HOLDING_REGISTER ((ULONG)((0x00)*SERIAL_REGISTER_STRIDE)) +#define INTERRUPT_ENABLE_REGISTER ((ULONG)((0x01)*SERIAL_REGISTER_STRIDE)) +#define INTERRUPT_IDENT_REGISTER ((ULONG)((0x02)*SERIAL_REGISTER_STRIDE)) +#define FIFO_CONTROL_REGISTER ((ULONG)((0x02)*SERIAL_REGISTER_STRIDE)) +#define LINE_CONTROL_REGISTER ((ULONG)((0x03)*SERIAL_REGISTER_STRIDE)) +#define MODEM_CONTROL_REGISTER ((ULONG)((0x04)*SERIAL_REGISTER_STRIDE)) +#define LINE_STATUS_REGISTER ((ULONG)((0x05)*SERIAL_REGISTER_STRIDE)) +#define MODEM_STATUS_REGISTER ((ULONG)((0x06)*SERIAL_REGISTER_STRIDE)) +#define DIVISOR_LATCH_LSB ((ULONG)((0x00)*SERIAL_REGISTER_STRIDE)) +#define DIVISOR_LATCH_MSB ((ULONG)((0x01)*SERIAL_REGISTER_STRIDE)) +#define SERIAL_REGISTER_SPAN ((ULONG)(7*SERIAL_REGISTER_STRIDE)) + +// +// If we have an interrupt status register this is its assumed +// length. +// +#define SERIAL_STATUS_LENGTH ((ULONG)(1*SERIAL_REGISTER_STRIDE)) + +// +// Bitmask definitions for accessing the 8250 device registers. +// + +// +// These bits define the number of data bits trasmitted in +// the Serial Data Unit (SDU - Start,data, parity, and stop bits) +// +#define SERIAL_DATA_LENGTH_5 0x00 +#define SERIAL_DATA_LENGTH_6 0x01 +#define SERIAL_DATA_LENGTH_7 0x02 +#define SERIAL_DATA_LENGTH_8 0x03 + + +// +// These masks define the interrupts that can be enabled or disabled. +// +// +// This interrupt is used to notify that there is new incomming +// data available. The SERIAL_RDA interrupt is enabled by this bit. +// +#define SERIAL_IER_RDA 0x01 + +// +// This interrupt is used to notify that there is space available +// in the transmitter for another character. The SERIAL_THR +// interrupt is enabled by this bit. +// +#define SERIAL_IER_THR 0x02 + +// +// This interrupt is used to notify that some sort of error occured +// with the incomming data. The SERIAL_RLS interrupt is enabled by +// this bit. +#define SERIAL_IER_RLS 0x04 + +// +// This interrupt is used to notify that some sort of change has +// taken place in the modem control line. The SERIAL_MS interrupt is +// enabled by this bit. +// +#define SERIAL_IER_MS 0x08 + + +// +// These masks define the values of the interrupt identification +// register. The low bit must be clear in the interrupt identification +// register for any of these interrupts to be valid. The interrupts +// are defined in priority order, with the highest value being most +// important. See above for a description of what each interrupt +// implies. +// +#define SERIAL_IIR_RLS 0x06 +#define SERIAL_IIR_RDA 0x04 +#define SERIAL_IIR_CTI 0x0c +#define SERIAL_IIR_THR 0x02 +#define SERIAL_IIR_MS 0x00 + +// +// This bit mask get the value of the high two bits of the +// interrupt id register. If this is a 16550 class chip +// these bits will be a one if the fifo's are enbled, otherwise +// they will always be zero. +// +#define SERIAL_IIR_FIFOS_ENABLED 0xc0 + +// +// If the low bit is logic one in the interrupt identification register +// this implies that *NO* interrupts are pending on the device. +// +#define SERIAL_IIR_NO_INTERRUPT_PENDING 0x01 + + +// +// Use these bits to detect removal of serial card for Stratus implementation +// +#define SERIAL_IIR_MUST_BE_ZERO 0x30 + + +// +// These masks define access to the fifo control register. +// + +// +// Enabling this bit in the fifo control register will turn +// on the fifos. If the fifos are enabled then the high two +// bits of the interrupt id register will be set to one. Note +// that this only occurs on a 16550 class chip. If the high +// two bits in the interrupt id register are not one then +// we know we have a lower model chip. +// +// +#define SERIAL_FCR_ENABLE ((UCHAR)0x01) +#define SERIAL_FCR_RCVR_RESET ((UCHAR)0x02) +#define SERIAL_FCR_TXMT_RESET ((UCHAR)0x04) + +// +// This set of values define the high water marks (when the +// interrupts trip) for the receive fifo. +// +#define SERIAL_1_BYTE_HIGH_WATER ((UCHAR)0x00) +#define SERIAL_4_BYTE_HIGH_WATER ((UCHAR)0x40) +#define SERIAL_8_BYTE_HIGH_WATER ((UCHAR)0x80) +#define SERIAL_14_BYTE_HIGH_WATER ((UCHAR)0xc0) + +// +// These masks define access to the line control register. +// + +// +// This defines the bit used to control the definition of the "first" +// two registers for the 8250. These registers are the input/output +// register and the interrupt enable register. When the DLAB bit is +// enabled these registers become the least significant and most +// significant bytes of the divisor value. +// +#define SERIAL_LCR_DLAB 0x80 + +// +// This defines the bit used to control whether the device is sending +// a break. When this bit is set the device is sending a space (logic 0). +// +// Most protocols will assume that this is a hangup. +// +#define SERIAL_LCR_BREAK 0x40 + +// +// These defines are used to set the line control register. +// +#define SERIAL_5_DATA ((UCHAR)0x00) +#define SERIAL_6_DATA ((UCHAR)0x01) +#define SERIAL_7_DATA ((UCHAR)0x02) +#define SERIAL_8_DATA ((UCHAR)0x03) +#define SERIAL_DATA_MASK ((UCHAR)0x03) + +#define SERIAL_1_STOP ((UCHAR)0x00) +#define SERIAL_1_5_STOP ((UCHAR)0x04) // Only valid for 5 data bits +#define SERIAL_2_STOP ((UCHAR)0x04) // Not valid for 5 data bits +#define SERIAL_STOP_MASK ((UCHAR)0x04) + +#define SERIAL_NONE_PARITY ((UCHAR)0x00) +#define SERIAL_ODD_PARITY ((UCHAR)0x08) +#define SERIAL_EVEN_PARITY ((UCHAR)0x18) +#define SERIAL_MARK_PARITY ((UCHAR)0x28) +#define SERIAL_SPACE_PARITY ((UCHAR)0x38) +#define SERIAL_PARITY_MASK ((UCHAR)0x38) + +// +// These masks define access the modem control register. +// + +// +// This bit controls the data terminal ready (DTR) line. When +// this bit is set the line goes to logic 0 (which is then inverted +// by normal hardware). This is normally used to indicate that +// the device is available to be used. Some odd hardware +// protocols (like the kernel debugger) use this for handshaking +// purposes. +// +#define SERIAL_MCR_DTR 0x01 + +// +// This bit controls the ready to send (RTS) line. When this bit +// is set the line goes to logic 0 (which is then inverted by the normal +// hardware). This is used for hardware handshaking. It indicates that +// the hardware is ready to send data and it is waiting for the +// receiving end to set clear to send (CTS). +// +#define SERIAL_MCR_RTS 0x02 + +// +// This bit is used for general purpose output. +// +#define SERIAL_MCR_OUT1 0x04 + +// +// This bit is used for general purpose output. +// +#define SERIAL_MCR_OUT2 0x08 + +// +// This bit controls the loopback testing mode of the device. Basically +// the outputs are connected to the inputs (and vice versa). +// +#define SERIAL_MCR_LOOP 0x10 + +// +// This bit enables auto flow control on a TI TL16C550C/TL16C550CI +// + +#define SERIAL_MCR_TL16C550CAFE 0x20 + + +// +// These masks define access to the line status register. The line +// status register contains information about the status of data +// transfer. The first five bits deal with receive data and the +// last two bits deal with transmission. An interrupt is generated +// whenever bits 1 through 4 in this register are set. +// + +// +// This bit is the data ready indicator. It is set to indicate that +// a complete character has been received. This bit is cleared whenever +// the receive buffer register has been read. +// +#define SERIAL_LSR_DR 0x01 + +// +// This is the overrun indicator. It is set to indicate that the receive +// buffer register was not read befor a new character was transferred +// into the buffer. This bit is cleared when this register is read. +// +#define SERIAL_LSR_OE 0x02 + +// +// This is the parity error indicator. It is set whenever the hardware +// detects that the incoming serial data unit does not have the correct +// parity as defined by the parity select in the line control register. +// This bit is cleared by reading this register. +// +#define SERIAL_LSR_PE 0x04 + +// +// This is the framing error indicator. It is set whenever the hardware +// detects that the incoming serial data unit does not have a valid +// stop bit. This bit is cleared by reading this register. +// +#define SERIAL_LSR_FE 0x08 + +// +// This is the break interrupt indicator. It is set whenever the data +// line is held to logic 0 for more than the amount of time it takes +// to send one serial data unit. This bit is cleared whenever the +// this register is read. +// +#define SERIAL_LSR_BI 0x10 + +// +// This is the transmit holding register empty indicator. It is set +// to indicate that the hardware is ready to accept another character +// for transmission. This bit is cleared whenever a character is +// written to the transmit holding register. +// +#define SERIAL_LSR_THRE 0x20 + +// +// This bit is the transmitter empty indicator. It is set whenever the +// transmit holding buffer is empty and the transmit shift register +// (a non-software accessable register that is used to actually put +// the data out on the wire) is empty. Basically this means that all +// data has been sent. It is cleared whenever the transmit holding or +// the shift registers contain data. +// +#define SERIAL_LSR_TEMT 0x40 + +// +// This bit indicates that there is at least one error in the fifo. +// The bit will not be turned off until there are no more errors +// in the fifo. +// +#define SERIAL_LSR_FIFOERR 0x80 + + +// +// These masks are used to access the modem status register. +// Whenever one of the first four bits in the modem status +// register changes state a modem status interrupt is generated. +// + +// +// This bit is the delta clear to send. It is used to indicate +// that the clear to send bit (in this register) has *changed* +// since this register was last read by the CPU. +// +#define SERIAL_MSR_DCTS 0x01 + +// +// This bit is the delta data set ready. It is used to indicate +// that the data set ready bit (in this register) has *changed* +// since this register was last read by the CPU. +// +#define SERIAL_MSR_DDSR 0x02 + +// +// This is the trailing edge ring indicator. It is used to indicate +// that the ring indicator input has changed from a low to high state. +// +#define SERIAL_MSR_TERI 0x04 + +// +// This bit is the delta data carrier detect. It is used to indicate +// that the data carrier bit (in this register) has *changed* +// since this register was last read by the CPU. +// +#define SERIAL_MSR_DDCD 0x08 + +// +// This bit contains the (complemented) state of the clear to send +// (CTS) line. +// +#define SERIAL_MSR_CTS 0x10 + +// +// This bit contains the (complemented) state of the data set ready +// (DSR) line. +// +#define SERIAL_MSR_DSR 0x20 + +// +// This bit contains the (complemented) state of the ring indicator +// (RI) line. +// +#define SERIAL_MSR_RI 0x40 + +// +// This bit contains the (complemented) state of the data carrier detect +// (DCD) line. +// +#define SERIAL_MSR_DCD 0x80 + +// +// This should be more than enough space to hold then +// numeric suffix of the device name. +// +#define DEVICE_NAME_DELTA 20 + + +// +// Up to 16 Ports Per card. However for sixteen +// port cards the interrupt status register must me +// the indexing kind rather then the bitmask kind. +// +// +#define SERIAL_MAX_PORTS_INDEXED (16) +#define SERIAL_MAX_PORTS_NONINDEXED (8) + +typedef struct _CONFIG_DATA { + PHYSICAL_ADDRESS Controller; + PHYSICAL_ADDRESS TrController; + ULONG SpanOfController; + ULONG ClockRate; + ULONG AddressSpace; + ULONG DisablePort; + ULONG ForceFifoEnable; + ULONG RxFIFO; + ULONG TxFIFO; + ULONG PermitShare; + ULONG PermitSystemWideShare; + ULONG LogFifo; + KINTERRUPT_MODE InterruptMode; + ULONG TrVector; + ULONG TrIrql; + KAFFINITY Affinity; + ULONG TL16C550CAFC; + } CONFIG_DATA,*PCONFIG_DATA; + + +// +// This structure contains configuration data, much of which +// is read from the registry. +// +typedef struct _SERIAL_FIRMWARE_DATA { + PDRIVER_OBJECT DriverObject; + ULONG ControllersFound; + ULONG ForceFifoEnableDefault; + ULONG DebugLevel; + ULONG ShouldBreakOnEntry; + ULONG RxFIFODefault; + ULONG TxFIFODefault; + ULONG PermitShareDefault; + ULONG PermitSystemWideShare; + ULONG LogFifoDefault; + ULONG UartRemovalDetect; + UNICODE_STRING Directory; + UNICODE_STRING NtNameSuffix; + UNICODE_STRING DirectorySymbolicName; + LIST_ENTRY ConfigList; +} SERIAL_FIRMWARE_DATA,*PSERIAL_FIRMWARE_DATA; + +// +// Default xon/xoff characters. +// +#define SERIAL_DEF_XON 0x11 +#define SERIAL_DEF_XOFF 0x13 + +// +// Reasons that recption may be held up. +// +#define SERIAL_RX_DTR ((ULONG)0x01) +#define SERIAL_RX_XOFF ((ULONG)0x02) +#define SERIAL_RX_RTS ((ULONG)0x04) +#define SERIAL_RX_DSR ((ULONG)0x08) + +// +// Reasons that transmission may be held up. +// +#define SERIAL_TX_CTS ((ULONG)0x01) +#define SERIAL_TX_DSR ((ULONG)0x02) +#define SERIAL_TX_DCD ((ULONG)0x04) +#define SERIAL_TX_XOFF ((ULONG)0x08) +#define SERIAL_TX_BREAK ((ULONG)0x10) + +// +// These values are used by the routines that can be used +// to complete a read (other than interval timeout) to indicate +// to the interval timeout that it should complete. +// +#define SERIAL_COMPLETE_READ_CANCEL ((LONG)-1) +#define SERIAL_COMPLETE_READ_TOTAL ((LONG)-2) +#define SERIAL_COMPLETE_READ_COMPLETE ((LONG)-3) + +// +// These are default values that shouldn't appear in the registry +// +#define SERIAL_BAD_VALUE ((ULONG)-1) + + +typedef struct _SERIAL_DEVICE_STATE { + // + // TRUE if we need to set the state to open + // on a powerup + // + + BOOLEAN Reopen; + + // + // Hardware registers + // + + UCHAR IER; + // FCR is known by other values + UCHAR LCR; + UCHAR MCR; + // LSR is never written + // MSR is never written + // SCR is either scratch or interrupt status + + +} SERIAL_DEVICE_STATE, *PSERIAL_DEVICE_STATE; + + +typedef +UCHAR +(*PREAD_PORT_UCHAR)( + IN UCHAR *Register + ); + +typedef +VOID +(*PWRITE_PORT_UCHAR)( + IN UCHAR *Register, + IN UCHAR Value + ); + +typedef struct _SERIAL_DEVICE_EXTENSION { + // + // WDF device handle + // + WDFDEVICE WdfDevice; + // + // Points to the device object that contains + // this device extension. + // + PDEVICE_OBJECT DeviceObject; + // + // We keep a pointer around to our device name for dumps + // and for creating "external" symbolic links to this + // device. + // + UNICODE_STRING DeviceName; + // + // Pointer to the driver object + // + + PDRIVER_OBJECT DriverObject; + + // + // Records whether we actually created the symbolic link name + // at driver load time. If we didn't create it, we won't try + // to destroy it when we unload. + // + BOOLEAN CreatedSymbolicLink; + + // + // Records whether we actually created an entry in SERIALCOMM + // at driver load time. If we didn't create it, we won't try + // to destroy it when the device is removed. + // + BOOLEAN CreatedSerialCommEntry; + + // + // Did we update system count for serial ports + // + BOOLEAN IsSystemConfigInfoUpdated; + + // + // Should we expose external interfaces? + // + ULONG SkipNaming; + + // + // Support the TI TL16C550C and TL16C550CI auto flow control + // + + ULONG TL16C550CAFC; + + // + // Detect removed hardware in intterrupt routine flag + // + ULONG UartRemovalDetect; + + // + // We keep track of whether the somebody has the device currently + // opened with a simple boolean. We need to know this so that + // spurious interrupts from the device (especially during initialization) + // will be ignored. This value is only accessed in the ISR and + // is only set via synchronization routines. We may be able + // to get rid of this boolean when the code is more fleshed out. + // + BOOLEAN DeviceIsOpened; + + // + // Current state during powerdown + // + + SERIAL_DEVICE_STATE DeviceState; + + // + // TRUE if we own power policy + // + + BOOLEAN OwnsPowerPolicy; + + // + // TRUE if we should retain power on close and not aggressively + // reduce power consumption + // + + BOOLEAN RetainPowerOnClose; + + // + // Should we enable wakeup + // + + BOOLEAN IsWakeEnabled; + + // + // This list head is used to contain the time ordered list + // of read requests. Access to this list is protected by + // the global cancel spinlock. + // + WDFQUEUE ReadQueue; + + // + // This list head is used to contain the time ordered list + // of write requests. Access to this list is protected by + // the global cancel spinlock. + // + WDFQUEUE WriteQueue; + + // + // This list head is used to contain the time ordered list + // of set and wait mask requests. Access to this list is protected by + // the global cancel spinlock. + // + WDFQUEUE MaskQueue; + + // + // Holds the serialized list of purge requests. + // + WDFQUEUE PurgeQueue; + + // + // This points to the request that is currently being processed + // for the read queue. This field is initialized by the open to + // NULL. + // + // This value is only set at dispatch level. It may be + // read at interrupt level. + // + WDFREQUEST CurrentReadRequest; + + // + // This points to the request that is currently being processed + // for the write queue. + // + // This value is only set at dispatch level. It may be + // read at interrupt level. + // + WDFREQUEST CurrentWriteRequest; + + // + // Points to the request that is currently being processed to + // affect the wait mask operations. + // + WDFREQUEST CurrentMaskRequest; + + // + // Points to the request that is currently being processed to + // purge the read/write queues and buffers. + // + WDFREQUEST CurrentPurgeRequest; + + // + // Points to the current request that is waiting on a comm event. + // + WDFREQUEST CurrentWaitRequest; + + // + // Points to the request that is being used to send an immediate + // character. + // + WDFREQUEST CurrentImmediateRequest; + + // + // Points to the request that is being used to count the number + // of characters received after an xoff (as currently defined + // by the IOCTL_SERIAL_XOFF_COUNTER ioctl) is sent. + // + WDFREQUEST CurrentXoffRequest; + + // + // The base address for the set of device registers + // of the serial port. + // + PUCHAR Controller; + // + // This value holds the span (in units of bytes) of the register + // set controlling this port. This is constant over the life + // of the port. + // + ULONG SpanOfController; + + // + // Address space + // + + ULONG AddressSpace; + + PREAD_PORT_UCHAR SerialReadUChar; + PWRITE_PORT_UCHAR SerialWriteUChar; + + // + // Hold the clock rate input to the serial part. + // + ULONG ClockRate; + + // + // The number of characters to push out if a fifo is present. + // + ULONG TxFifoAmount; + + // + // Set to indicate that it is ok to share interrupts within the device. + // + ULONG PermitShare; + + + // + // Points to the interrupt object for used by this device. + // + WDFINTERRUPT WdfInterrupt; + + // + // Translated vector + // + ULONG Vector; + // + // Translated Irql + // + KIRQL Irql; + + KINTERRUPT_MODE InterruptMode; + + KAFFINITY Affinity; + + // + // This value is set by the read code to hold the time value + // used for read interval timing. We keep it in the extension + // so that the interval timer dpc routine determine if the + // interval time has passed for the IO. + // + LARGE_INTEGER IntervalTime; + + // + // These two values hold the "constant" time that we should use + // to delay for the read interval time. + // + LARGE_INTEGER ShortIntervalAmount; + LARGE_INTEGER LongIntervalAmount; + + // + // This holds the value that we use to determine if we should use + // the long interval delay or the short interval delay. + // + LARGE_INTEGER CutOverAmount; + + // + // This holds the system time when we last time we had + // checked that we had actually read characters. Used + // for interval timing. + // + LARGE_INTEGER LastReadTime; + + + // + // This points the the delta time that we should use to + // delay for interval timing. + // + PLARGE_INTEGER IntervalTimeToUse; + + + // + // Set at intialization to indicate that on the current + // architecture we need to unmap the base register address + // when we unload the driver. + // + BOOLEAN UnMapRegisters; + + // + // Holds the number of bytes remaining in the current write + // request. + // + // This location is only accessed while at interrupt level. + // + ULONG WriteLength; + + // + // Holds a pointer to the current character to be sent in + // the current write. + // + // This location is only accessed while at interrupt level. + // + PUCHAR WriteCurrentChar; + + // + // This is a buffer for the read processing. + // + // The buffer works as a ring. When the character is read from + // the device it will be place at the end of the ring. + // + // Characters are only placed in this buffer at interrupt level + // although character may be read at any level. The pointers + // that manage this buffer may not be updated except at interrupt + // level. + // + PUCHAR InterruptReadBuffer; + + // + // This is a pointer to the first character of the buffer into + // which the interrupt service routine is copying characters. + // + PUCHAR ReadBufferBase; + + // + // This is a count of the number of characters in the interrupt + // buffer. This value is set and read at interrupt level. Note + // that this value is only *incremented* at interrupt level so + // it is safe to read it at any level. When characters are + // copied out of the read buffer, this count is decremented by + // a routine that synchronizes with the ISR. + // + ULONG CharsInInterruptBuffer; + + // + // Points to the first available position for a newly received + // character. This variable is only accessed at interrupt level and + // buffer initialization code. + // + PUCHAR CurrentCharSlot; + + // + // This variable is used to contain the last available position + // in the read buffer. It is updated at open and at interrupt + // level when switching between the users buffer and the interrupt + // buffer. + // + PUCHAR LastCharSlot; + + // + // This marks the first character that is available to satisfy + // a read request. Note that while this always points to valid + // memory, it may not point to a character that can be sent to + // the user. This can occur when the buffer is empty. + // + PUCHAR FirstReadableChar; + + // + // Pointer to the lock variable returned for this extension when + // locking down the driver + // + PVOID LockPtr; + + + // + // This variable holds the size of whatever buffer we are currently + // using. + // + ULONG BufferSize; + + // + // This variable holds .8 of BufferSize. We don't want to recalculate + // this real often - It's needed when so that an application can be + // "notified" that the buffer is getting full. + // + ULONG BufferSizePt8; + + // + // This value holds the number of characters desired for a + // particular read. It is initially set by read length in the + // WDFREQUEST. It is decremented each time more characters are placed + // into the "users" buffer buy the code that reads characters + // out of the typeahead buffer into the users buffer. If the + // typeahead buffer is exhausted by the read, and the reads buffer + // is given to the isr to fill, this value is becomes meaningless. + // + ULONG NumberNeededForRead; + + // + // This mask will hold the bitmask sent down via the set mask + // ioctl. It is used by the interrupt service routine to determine + // if the occurence of "events" (in the serial drivers understanding + // of the concept of an event) should be noted. + // + ULONG IsrWaitMask; + + // + // This mask will always be a subset of the IsrWaitMask. While + // at device level, if an event occurs that is "marked" as interesting + // in the IsrWaitMask, the driver will turn on that bit in this + // history mask. The driver will then look to see if there is a + // request waiting for an event to occur. If there is one, it + // will copy the value of the history mask into the wait request, zero + // the history mask, and complete the wait request. If there is no + // waiting request, the driver will be satisfied with just recording + // that the event occured. If a wait request should be queued, + // the driver will look to see if the history mask is non-zero. If + // it is non-zero, the driver will copy the history mask into the + // request, zero the history mask, and then complete the request. + // + ULONG HistoryMask; + + // + // This is a pointer to the where the history mask should be + // placed when completing a wait. It is only accessed at + // device level. + // + // We have a pointer here to assist us to synchronize completing a wait. + // If this is non-zero, then we have wait outstanding, and the isr still + // knows about it. We make this pointer null so that the isr won't + // attempt to complete the wait. + // + // We still keep a pointer around to the wait request, since the actual + // pointer to the wait request will be used for the "common" request completion + // path. + // + ULONG *IrpMaskLocation; + + // + // This mask holds all of the reason that transmission + // is not proceeding. Normal transmission can not occur + // if this is non-zero. + // + // This is only written from interrupt level. + // This could be (but is not) read at any level. + // + ULONG TXHolding; + + // + // This mask holds all of the reason that reception + // is not proceeding. Normal reception can not occur + // if this is non-zero. + // + // This is only written from interrupt level. + // This could be (but is not) read at any level. + // + ULONG RXHolding; + + // + // This holds the reasons that the driver thinks it is in + // an error state. + // + // This is only written from interrupt level. + // This could be (but is not) read at any level. + // + ULONG ErrorWord; + + // + // This keeps a total of the number of characters that + // are in all of the "write" irps that the driver knows + // about. It is only accessed with the cancel spinlock + // held. + // + ULONG TotalCharsQueued; + + // + // This holds a count of the number of characters read + // the last time the interval timer dpc fired. It + // is a long (rather than a ulong) since the other read + // completion routines use negative values to indicate + // to the interval timer that it should complete the read + // if the interval timer DPC was lurking in some DPC queue when + // some other way to complete occurs. + // + LONG CountOnLastRead; + + // + // This is a count of the number of characters read by the + // isr routine. It is *ONLY* written at isr level. We can + // read it at dispatch level. + // + ULONG ReadByIsr; + + // + // This holds the current baud rate for the device. + // + ULONG CurrentBaud; + + // + // This is the number of characters read since the XoffCounter + // was started. This variable is only accessed at device level. + // If it is greater than zero, it implies that there is an + // XoffCounter ioctl in the queue. + // + LONG CountSinceXoff; + + // + // This ulong is incremented each time something trys to start + // the execution path that tries to lower the RTS line when + // doing transmit toggling. If it "bumps" into another path + // (indicated by a false return value from queueing a dpc + // and a TRUE return value tring to start a timer) it will + // decrement the count. These increments and decrements + // are all done at device level. Note that in the case + // of a bump while trying to start the timer, we have to + // go up to device level to do the decrement. + // + ULONG CountOfTryingToLowerRTS; + + // + // This ULONG is used to keep track of the "named" (in ntddser.h) + // baud rates that this particular device supports. + // + ULONG SupportedBauds; + + // + // Holds the timeout controls for the device. This value + // is set by the Ioctl processing. + // + // It should only be accessed under protection of the control + // lock since more than one request can be in the control dispatch + // routine at one time. + // + SERIAL_TIMEOUTS Timeouts; + + // + // This holds the various characters that are used + // for replacement on errors and also for flow control. + // + // They are only set at interrupt level. + // + SERIAL_CHARS SpecialChars; + + // + // This structure holds the handshake and control flow + // settings for the serial driver. + // + // It is only set at interrupt level. It can be + // be read at any level with the control lock held. + // + SERIAL_HANDFLOW HandFlow; + + + // + // Holds performance statistics that applications can query. + // Reset on each open. Only set at device level. + // + SERIALPERF_STATS PerfStats; + + // + // This holds what we beleive to be the current value of + // the line control register. + // + // It should only be accessed under protection of the control + // lock since more than one request can be in the control dispatch + // routine at one time. + // + UCHAR LineControl; + + + // + // This is only accessed at interrupt level. It keeps track + // of whether the holding register is empty. + // + BOOLEAN HoldingEmpty; + + // + // This variable is only accessed at interrupt level. It + // indicates that we want to transmit a character immediately. + // That is - in front of any characters that could be transmitting + // from a normal write. + // + BOOLEAN TransmitImmediate; + + // + // This variable is only accessed at interrupt level. Whenever + // a wait is initiated this variable is set to false. + // Whenever any kind of character is written it is set to true. + // Whenever the write queue is found to be empty the code that + // is processing that completing request will synchonize with the interrupt. + // If this synchronization code finds that the variable is true and that + // there is a wait on the transmit queue being empty then it is + // certain that the queue was emptied and that it has happened since + // the wait was initiated. + // + BOOLEAN EmptiedTransmit; + + // + // We keep the following values around so that we can connect + // to the interrupt and report resources after the configuration + // record is gone. + // + + // + // We hold the character that should be transmitted immediately. + // + // Note that we can't use this to determine whether there is + // a character to send because the character to send could be + // zero. + // + UCHAR ImmediateChar; + + // + // This holds the mask that will be used to mask off unwanted + // data bits of the received data (valid data bits can be 5,6,7,8) + // The mask will normally be 0xff. This is set while the control + // lock is held since it wouldn't have adverse effects on the + // isr if it is changed in the middle of reading characters. + // (What it would do to the app is another question - but then + // the app asked the driver to do it.) + // + UCHAR ValidDataMask; + + // + // The application can turn on a mode,via the + // IOCTL_SERIAL_LSRMST_INSERT ioctl, that will cause the + // serial driver to insert the line status or the modem + // status into the RX stream. The parameter with the ioctl + // is a pointer to a UCHAR. If the value of the UCHAR is + // zero, then no insertion will ever take place. If the + // value of the UCHAR is non-zero (and not equal to the + // xon/xoff characters), then the serial driver will insert. + // + UCHAR EscapeChar; + + // + // These two booleans are used to indicate to the isr transmit + // code that it should send the xon or xoff character. They are + // only accessed at open and at interrupt level. + // + BOOLEAN SendXonChar; + BOOLEAN SendXoffChar; + + // + // This boolean will be true if a 16550 is present *and* enabled. + // + BOOLEAN FifoPresent; + + // + // This is the water mark that the rxfifo should be + // set to when the fifo is turned on. This is not the actual + // value, but the encoded value that goes into the register. + // + UCHAR RxFifoTrigger; + + // + // This points to a DPC used to complete write requests. + // + WDFDPC CompleteWriteDpc; + + // + // This points to a DPC used to complete read requests. + // + WDFDPC CompleteReadDpc; + + + // + // This dpc is fired off if a comm error occurs. It will + // execute a dpc routine that will cancel all pending reads + // and writes. + // + WDFDPC CommErrorDpc; + + // + // This dpc is fired off if an event occurs and there was + // a request waiting on that event. A dpc routine will execute + // that completes the request. + // + WDFDPC CommWaitDpc; + + // + // This dpc is fired off when the transmit immediate char + // character is given to the hardware. It will simply complete + // the request. + // + WDFDPC CompleteImmediateDpc; + + // + // This dpc is fired off if the xoff counter actually runs down + // to zero. + // + WDFDPC XoffCountCompleteDpc; + + // + // This dpc is fired off only from device level to start off + // a timer that will queue a dpc to check if the RTS line + // should be lowered when we are doing transmit toggling. + // + WDFDPC StartTimerLowerRTSDpc; + + // + // This timer used to handle total read request timing. + // + WDFTIMER ReadRequestTotalTimer; + + // + // This timer used to handle interval read request timing. + // + WDFTIMER ReadRequestIntervalTimer; + + // + // This timer used to handle total write request timing. + // + WDFTIMER WriteRequestTotalTimer; + + // + // This is timer structure used to handle total time request timing. + // + WDFTIMER ImmediateTotalTimer; + + // + // This timer is used to timeout the xoff counter io. + // + WDFTIMER XoffCountTimer; + + // + // This timer is used to invoke a dpc one character time + // after the timer is set. That dpc will be used to check + // whether we should lower the RTS line if we are doing + // transmit toggling. + // + WDFTIMER LowerRTSTimer; + + // + // WMI Information + // + + // + // WMI Comm Data + // + + SERIAL_WMI_COMM_DATA WmiCommData; + + // + // WMI HW Data + // + + SERIAL_WMI_HW_DATA WmiHwData; + + // + // WMI Performance Data + // + + SERIAL_WMI_PERF_DATA WmiPerfData; + +} SERIAL_DEVICE_EXTENSION,*PSERIAL_DEVICE_EXTENSION; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SERIAL_DEVICE_EXTENSION, + SerialGetDeviceExtension) + +// +// This is the scratch area for every request. +// We will copy some of the frequently used information of the request +// into our context area so that way we don't have to call WdfRequestGetParams +// function everytime. +// +typedef struct _REQUEST_CONTEXT { + ULONG_PTR Information; + NTSTATUS Status; + ULONG Length; + PVOID RefCount; + PVOID SystemBuffer; + UCHAR MajorFunction; + PFN_WDF_REQUEST_CANCEL CancelRoutine; + BOOLEAN Cancelled; + PVOID Type3InputBuffer; + PSERIAL_DEVICE_EXTENSION Extension; + ULONG IoctlCode; + BOOLEAN MarkCancelableOnResume; +} REQUEST_CONTEXT, *PREQUEST_CONTEXT; + + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(REQUEST_CONTEXT, + SerialGetRequestContext) + + +// +// This is the Interrupt context for the Serial device. This structure is used +// for keeping track of whether the Interrupt is connected or not. +// +typedef struct _SERIAL_INTERRUPT_CONTEXT { + + // + // This boolean value indicates whether Interrupt is connected. + // + BOOLEAN IsInterruptConnected; + + // + // This lock is used to synchronize the file close logic and + // the Surprise Removal logic. When a surprise remove happens, + // the device interrupts are disabled. When this occurs, the + // file close logic should not attempt to use the interrupt + // object. + // + WDFWAITLOCK InterruptStateLock; + +} SERIAL_INTERRUPT_CONTEXT, *PSERIAL_INTERRUPT_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SERIAL_INTERRUPT_CONTEXT, + SerialGetInterruptContext) + + +#define SERIAL_FLAGS_CLEAR 0x0L +#define SERIAL_FLAGS_STARTED 0x1L +#define SERIAL_FLAGS_STOPPED 0x2L +#define SERIAL_FLAGS_BROKENHW 0x4L +#define SERIAL_FLAGS_LEGACY_ENUMED 0x8L + + +__inline +UCHAR +SerialReadPortUChar ( + IN UCHAR * x + ) +{ + return READ_PORT_UCHAR (x); +} +__inline +VOID +SerialWritePortUChar ( + IN UCHAR * x, + IN UCHAR y + ) +{ + WRITE_PORT_UCHAR (x,y); +} + +__inline +UCHAR +SerialReadRegisterUChar ( + IN UCHAR * x + ) +{ + return READ_REGISTER_UCHAR (x); +} + +__inline +VOID +SerialWriteRegisterUChar ( + IN UCHAR * x, + IN UCHAR y + ) +{ + WRITE_REGISTER_UCHAR (x,y); +} + + + +// +// Sets the divisor latch register. The divisor latch register +// is used to control the baud rate of the 8250. +// +// As with all of these routines it is assumed that it is called +// at a safe point to access the hardware registers. In addition +// it also assumes that the data is correct. +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// DesiredDivisor - The value to which the divisor latch register should +// be set. +// +#define WRITE_DIVISOR_LATCH(Extension, BaseAddress,DesiredDivisor) \ +do \ +{ \ + PUCHAR Address = BaseAddress; \ + SHORT Divisor = DesiredDivisor; \ + UCHAR LineControl; \ + LineControl = Extension->SerialReadUChar(Address+LINE_CONTROL_REGISTER); \ + Extension->SerialWriteUChar( \ + Address+LINE_CONTROL_REGISTER, \ + (UCHAR)(LineControl | SERIAL_LCR_DLAB) \ + ); \ + Extension->SerialWriteUChar( \ + Address+DIVISOR_LATCH_LSB, \ + (UCHAR)(Divisor & 0xff) \ + ); \ + Extension->SerialWriteUChar( \ + Address+DIVISOR_LATCH_MSB, \ + (UCHAR)((Divisor & 0xff00) >> 8) \ + ); \ + Extension->SerialWriteUChar( \ + Address+LINE_CONTROL_REGISTER, \ + LineControl \ + ); \ +} WHILE (0) + +// +// Reads the divisor latch register. The divisor latch register +// is used to control the baud rate of the 8250. +// +// As with all of these routines it is assumed that it is called +// at a safe point to access the hardware registers. In addition +// it also assumes that the data is correct. +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// DesiredDivisor - A pointer to the 2 byte word which will contain +// the value of the divisor. +// +#define READ_DIVISOR_LATCH(Extension, BaseAddress,PDesiredDivisor) \ +do \ +{ \ + PUCHAR Address = BaseAddress; \ + PSHORT PDivisor = PDesiredDivisor; \ + UCHAR LineControl; \ + UCHAR Lsb; \ + UCHAR Msb; \ + LineControl = Extension->SerialReadUChar(Address+LINE_CONTROL_REGISTER); \ + Extension->SerialWriteUChar( \ + Address+LINE_CONTROL_REGISTER, \ + (UCHAR)(LineControl | SERIAL_LCR_DLAB) \ + ); \ + Lsb = Extension->SerialReadUChar(Address+DIVISOR_LATCH_LSB); \ + Msb = Extension->SerialReadUChar(Address+DIVISOR_LATCH_MSB); \ + *PDivisor = Lsb; \ + *PDivisor = *PDivisor | (((USHORT)Msb) << 8); \ + Extension->SerialWriteUChar( \ + Address+LINE_CONTROL_REGISTER, \ + LineControl \ + ); \ +} WHILE (0) + +// +// This macro reads the interrupt enable register. +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +#define READ_INTERRUPT_ENABLE(Extension, BaseAddress) \ + (Extension->SerialReadUChar((BaseAddress)+INTERRUPT_ENABLE_REGISTER)) + +// +// This macro writes the interrupt enable register. +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// Values - The values to write to the interrupt enable register. +// +#define WRITE_INTERRUPT_ENABLE(Extension, BaseAddress,Values) \ +do \ +{ \ + Extension->SerialWriteUChar( \ + BaseAddress+INTERRUPT_ENABLE_REGISTER, \ + Values \ + ); \ +} WHILE (0) + +// +// This macro disables all interrupts on the hardware. +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// +#define DISABLE_ALL_INTERRUPTS(Extension, BaseAddress) \ +do \ +{ \ + WRITE_INTERRUPT_ENABLE(Extension, BaseAddress,0); \ +} WHILE (0) + +// +// This macro enables all interrupts on the hardware. +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// +#define ENABLE_ALL_INTERRUPTS(Extension, BaseAddress) \ +do \ +{ \ + \ + WRITE_INTERRUPT_ENABLE( \ + (Extension), (BaseAddress), \ + (UCHAR)(SERIAL_IER_RDA | SERIAL_IER_THR | \ + SERIAL_IER_RLS | SERIAL_IER_MS) \ + ); \ + \ +} WHILE (0) + +// +// This macro reads the interrupt identification register +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// Note that this routine potententially quites a transmitter +// empty interrupt. This is because one way that the transmitter +// empty interrupt is cleared is to simply read the interrupt id +// register. +// +// +#define READ_INTERRUPT_ID_REG(Extension, BaseAddress) \ + (Extension->SerialReadUChar((BaseAddress)+INTERRUPT_IDENT_REGISTER)) + +// +// This macro reads the modem control register +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// +#define READ_MODEM_CONTROL(Extension, BaseAddress) \ + (Extension->SerialReadUChar((BaseAddress)+MODEM_CONTROL_REGISTER)) + +// +// This macro reads the modem status register +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// +#define READ_MODEM_STATUS(Extension, BaseAddress) \ + (Extension->SerialReadUChar((BaseAddress)+MODEM_STATUS_REGISTER)) + +// +// This macro reads a value out of the receive buffer +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// +#define READ_RECEIVE_BUFFER(Extension, BaseAddress) \ + (Extension->SerialReadUChar((BaseAddress)+RECEIVE_BUFFER_REGISTER)) + +// +// This macro reads the line status register +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// +#define READ_LINE_STATUS(Extension, BaseAddress) \ + (Extension->SerialReadUChar((BaseAddress)+LINE_STATUS_REGISTER)) + +// +// This macro writes the line control register +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// +#define WRITE_LINE_CONTROL(Extension, BaseAddress,NewLineControl) \ +do \ +{ \ + Extension->SerialWriteUChar( \ + (BaseAddress)+LINE_CONTROL_REGISTER, \ + (NewLineControl) \ + ); \ +} WHILE (0) + +// +// This macro reads the line control register +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// +#define READ_LINE_CONTROL(Extension, BaseAddress) \ + (Extension->SerialReadUChar((BaseAddress)+LINE_CONTROL_REGISTER)) + + +// +// This macro writes to the transmit register +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// TransmitChar - The character to send down the wire. +// +// +#define WRITE_TRANSMIT_HOLDING(Extension, BaseAddress,TransmitChar) \ +do \ +{ \ + Extension->SerialWriteUChar( \ + (BaseAddress)+TRANSMIT_HOLDING_REGISTER, \ + (TransmitChar) \ + ); \ +} WHILE (0) + +// +// This macro writes to the transmit FIFO register +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// TransmitChars - Pointer to the characters to send down the wire. +// +// TxN - number of charactes to send. +// +// +#define WRITE_TRANSMIT_FIFO_HOLDING(Extension, BaseAddress,TransmitChars,TxN) \ +do \ +{ \ + WRITE_PORT_BUFFER_UCHAR( \ + (BaseAddress)+TRANSMIT_HOLDING_REGISTER, \ + (TransmitChars), \ + (TxN) \ + ); \ +} WHILE (0) + +// +// This macro writes to the control register +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// ControlValue - The value to set the fifo control register too. +// +// +#define WRITE_FIFO_CONTROL(Extension, BaseAddress,ControlValue) \ +do \ +{ \ + Extension->SerialWriteUChar( \ + (BaseAddress)+FIFO_CONTROL_REGISTER, \ + (ControlValue) \ + ); \ +} WHILE (0) + +// +// This macro writes to the modem control register +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. +// +// ModemControl - The control bits to send to the modem control. +// +// +#define WRITE_MODEM_CONTROL(Extension, BaseAddress,ModemControl) \ +do \ +{ \ + Extension->SerialWriteUChar( \ + (BaseAddress)+MODEM_CONTROL_REGISTER, \ + (ModemControl) \ + ); \ +} WHILE (0) + +#define WRITE_INTERRUPT_STATUS(Extension, BaseAddress,Status) \ +do \ +{ \ + Extension->SerialWriteUChar(BaseAddress, Status); \ +} WHILE (0) + + +// +// This macro reads the interrupt status register +// +// Arguments: +// +// BaseAddress - A pointer to the address from which the hardware +// device registers are located. BaseAddress is gotten +// from PSERIAL_MULTIPORT_DISPATCH->InterruptStatus which +// already has the complete address +// +// AddressSpace - Flag indicating where port is located, MMIO or IO +// space +// +// +#define READ_INTERRUPT_STATUS(Extension, BaseAddress) \ + Extension->SerialReadUChar(BaseAddress)) + +// +// We use this to query into the registry as to whether we +// should break at driver entry. +// + +extern SERIAL_FIRMWARE_DATA driverDefaults; + + +// +// This is exported from the kernel. It is used to point +// to the address that the kernel debugger is using. +// + +extern PUCHAR *KdComPortInUse; + + +typedef enum _SERIAL_MEM_COMPARES { + AddressesAreEqual, + AddressesOverlap, + AddressesAreDisjoint + } SERIAL_MEM_COMPARES,*PSERIAL_MEM_COMPARES; + +#define SERIAL_BAUD_INVALID 0xFFFFFFFF + +typedef struct _SUPPORTED_BAUD_RATES { + UINT32 BaudRate; + ULONG Mask; +}SUPPORTED_BAUD_RATES; + diff --git a/tests/projects/wdk/kmdf/serial/serial.inx b/tests/projects/wdk/kmdf/serial/serial.inx new file mode 100644 index 000000000..3322653b0 Binary files /dev/null and b/tests/projects/wdk/kmdf/serial/serial.inx differ diff --git a/tests/projects/wdk/kmdf/serial/serial.rc b/tests/projects/wdk/kmdf/serial/serial.rc new file mode 100644 index 000000000..9fbb59de3 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/serial.rc @@ -0,0 +1,14 @@ +#include + +#include + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_DRV_SYSTEM +#define VER_FILEDESCRIPTION_STR "Serial Device Driver" +#define VER_INTERNALNAME_STR "serial.sys" +#define VER_ORIGINALFILENAME_STR "serial.sys" + +#include "common.ver" + +#include "serlog.rc" + diff --git a/tests/projects/wdk/kmdf/serial/serialp.h b/tests/projects/wdk/kmdf/serial/serialp.h new file mode 100644 index 000000000..d363f3503 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/serialp.h @@ -0,0 +1,596 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name : + + serialp.h + +Abstract: + + Prototypes and macros that are used throughout the driver. + +--*/ + +//----------------------------------------------------------------------------- +// 4127 -- Conditional Expression is Constant warning +//----------------------------------------------------------------------------- +#define WHILE(constant) \ +__pragma(warning(suppress: 4127)) while(constant) + +typedef +VOID +(*PSERIAL_START_ROUTINE) ( + IN PSERIAL_DEVICE_EXTENSION + ); + +typedef +VOID +(*PSERIAL_GET_NEXT_ROUTINE) ( + IN WDFREQUEST *CurrentOpRequest, + IN WDFQUEUE QueueToProcess, + OUT WDFREQUEST *NewRequest, + IN BOOLEAN CompleteCurrent, + PSERIAL_DEVICE_EXTENSION Extension + ); + +DRIVER_INITIALIZE DriverEntry; + +EVT_WDF_DRIVER_DEVICE_ADD SerialEvtDeviceAdd; +EVT_WDF_OBJECT_CONTEXT_CLEANUP SerialEvtDriverContextCleanup; +EVT_WDF_DEVICE_CONTEXT_CLEANUP SerialEvtDeviceContextCleanup; + +EVT_WDF_DEVICE_D0_ENTRY SerialEvtDeviceD0Entry; +EVT_WDF_DEVICE_D0_EXIT SerialEvtDeviceD0Exit; +EVT_WDF_DEVICE_D0_ENTRY_POST_INTERRUPTS_ENABLED SerialEvtDeviceD0EntryPostInterruptsEnabled; +EVT_WDF_DEVICE_D0_EXIT_PRE_INTERRUPTS_DISABLED SerialEvtDeviceD0ExitPreInterruptsDisabled; +EVT_WDF_DEVICE_PREPARE_HARDWARE SerialEvtPrepareHardware; +EVT_WDF_DEVICE_RELEASE_HARDWARE SerialEvtReleaseHardware; + +EVT_WDF_DEVICE_FILE_CREATE SerialEvtDeviceFileCreate; +EVT_WDF_FILE_CLOSE SerialEvtFileClose; + +EVT_WDF_IO_QUEUE_IO_READ SerialEvtIoRead; +EVT_WDF_IO_QUEUE_IO_WRITE SerialEvtIoWrite; +EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL SerialEvtIoDeviceControl; +EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL SerialEvtIoInternalDeviceControl; +EVT_WDF_IO_QUEUE_IO_CANCELED_ON_QUEUE SerialEvtCanceledOnQueue; +EVT_WDF_IO_QUEUE_IO_STOP SerialEvtIoStop; +EVT_WDF_IO_QUEUE_IO_RESUME SerialEvtIoResume; + +EVT_WDF_INTERRUPT_ENABLE SerialEvtInterruptEnable; +EVT_WDF_INTERRUPT_DISABLE SerialEvtInterruptDisable; + +EVT_WDF_DPC SerialCompleteRead; +EVT_WDF_DPC SerialCompleteWrite; +EVT_WDF_DPC SerialCommError; +EVT_WDF_DPC SerialCompleteImmediate; +EVT_WDF_DPC SerialCompleteXoff; +EVT_WDF_DPC SerialCompleteWait; +EVT_WDF_DPC SerialStartTimerLowerRTS; + +EVT_WDF_TIMER SerialReadTimeout; +EVT_WDF_TIMER SerialIntervalReadTimeout; +EVT_WDF_TIMER SerialWriteTimeout; +EVT_WDF_TIMER SerialTimeoutImmediate; +EVT_WDF_TIMER SerialTimeoutXoff; +EVT_WDF_TIMER SerialInvokePerhapsLowerRTS; + +VOID +SerialStartRead( + IN PSERIAL_DEVICE_EXTENSION Extension + ); + +VOID +SerialStartWrite( + IN PSERIAL_DEVICE_EXTENSION Extension + ); + +VOID +SerialStartMask( + IN PSERIAL_DEVICE_EXTENSION Extension + ); + +VOID +SerialStartImmediate( + IN PSERIAL_DEVICE_EXTENSION Extension + ); + +VOID +SerialStartPurge( + IN PSERIAL_DEVICE_EXTENSION Extension + ); + +VOID +SerialGetNextWrite( + IN WDFREQUEST *CurrentOpRequest, + IN WDFQUEUE QueueToProcess, + IN WDFREQUEST *NewRequest, + IN BOOLEAN CompleteCurrent, + IN PSERIAL_DEVICE_EXTENSION Extension + ); + +EVT_WDFDEVICE_WDM_IRP_PREPROCESS SerialWdmDeviceFileCreate; +EVT_WDFDEVICE_WDM_IRP_PREPROCESS SerialWdmFileClose; +EVT_WDFDEVICE_WDM_IRP_PREPROCESS SerialFlush; + +EVT_WDFDEVICE_WDM_IRP_PREPROCESS SerialQueryInformationFile; +EVT_WDFDEVICE_WDM_IRP_PREPROCESS SerialSetInformationFile; + +NTSTATUS +SerialDeviceFileCreateWorker ( + IN WDFDEVICE Device + ); + + +VOID +SerialFileCloseWorker( + IN WDFDEVICE Device + ); + +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialProcessEmptyTransmit; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetDTR; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialClrDTR; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetRTS; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialClrRTS; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetBaud; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetLineControl; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetHandFlow; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialTurnOnBreak; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialTurnOffBreak; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialPretendXoff; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialPretendXon; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialReset; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialPerhapsLowerRTS; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialMarkOpen; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialMarkClose; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGetStats; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialClearStats; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetChars; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetMCRContents; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGetMCRContents; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialSetFCRContents; + +BOOLEAN +SerialSetupNewHandFlow( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN PSERIAL_HANDFLOW NewHandFlow + ); + + +VOID +SerialHandleReducedIntBuffer( + IN PSERIAL_DEVICE_EXTENSION Extension + ); + +VOID +SerialProdXonXoff( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN BOOLEAN SendXon + ); + +EVT_WDF_REQUEST_CANCEL SerialCancelWait; + + +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialPurgeInterruptBuff; + +VOID +SerialPurgeRequests( + IN WDFQUEUE QueueToClean, + IN WDFREQUEST *CurrentOpRequest + ); + +VOID +SerialFlushRequests( + IN WDFQUEUE QueueToClean, + IN WDFREQUEST *CurrentOpRequest + ); + +VOID +SerialGetNextRequest( + IN WDFREQUEST *CurrentOpRequest, + IN WDFQUEUE QueueToProcess, + OUT WDFREQUEST *NextIrp, + IN BOOLEAN CompleteCurrent, + IN PSERIAL_DEVICE_EXTENSION extension + ); + + +VOID +SerialTryToCompleteCurrent( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN PFN_WDF_INTERRUPT_SYNCHRONIZE SynchRoutine OPTIONAL, + IN NTSTATUS StatusToUse, + IN WDFREQUEST *CurrentOpRequest, + IN WDFQUEUE QueueToProcess, + IN WDFTIMER IntervalTimer, + IN WDFTIMER TotalTimer, + IN PSERIAL_START_ROUTINE Starter, + IN PSERIAL_GET_NEXT_ROUTINE GetNextIrp, + IN LONG RefType + ); + +VOID +SerialStartOrQueue( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN WDFREQUEST Request, + IN WDFQUEUE QueueToExamine, + IN WDFREQUEST *CurrentOpRequest, + IN PSERIAL_START_ROUTINE Starter + ); + +NTSTATUS +SerialCompleteIfError( + PSERIAL_DEVICE_EXTENSION extension, + WDFREQUEST Request + ); + +ULONG +SerialHandleModemUpdate( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN BOOLEAN DoingTX + ); + + +EVT_WDF_INTERRUPT_ISR SerialISR; + +NTSTATUS +SerialGetDivisorFromBaud( + IN ULONG ClockRate, + IN LONG DesiredBaud, + OUT PSHORT AppropriateDivisor + ); + +VOID +SerialCleanupDevice( + IN PSERIAL_DEVICE_EXTENSION Extension + ); + +UCHAR +SerialProcessLSR( + IN PSERIAL_DEVICE_EXTENSION Extension + ); + +LARGE_INTEGER +SerialGetCharTime( + IN PSERIAL_DEVICE_EXTENSION Extension + ); + + +VOID +SerialPutChar( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN UCHAR CharToPut + ); + +NTSTATUS +SerialGetConfigDefaults( + IN PSERIAL_FIRMWARE_DATA DriverDefaultsPtr, + IN WDFDRIVER Driver + ); + +VOID +SerialGetProperties( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN PSERIAL_COMMPROP Properties + ); + +VOID +SerialLogError( + _In_ PDRIVER_OBJECT DriverObject, + _In_opt_ PDEVICE_OBJECT DeviceObject, + _In_ PHYSICAL_ADDRESS P1, + _In_ PHYSICAL_ADDRESS P2, + _In_ ULONG SequenceNumber, + _In_ UCHAR MajorFunctionCode, + _In_ UCHAR RetryCount, + _In_ ULONG UniqueErrorValue, + _In_ NTSTATUS FinalStatus, + _In_ NTSTATUS SpecificIOStatus, + _In_ ULONG LengthOfInsert1, + _In_reads_bytes_opt_(LengthOfInsert1) PWCHAR Insert1, + _In_ ULONG LengthOfInsert2, + _In_reads_bytes_opt_(LengthOfInsert2) PWCHAR Insert2 + ); + +NTSTATUS +SerialMapHWResources( + IN WDFDEVICE Device, + IN WDFCMRESLIST PResList, + IN WDFCMRESLIST PTrResList, + OUT PCONFIG_DATA PConfig + ); + +VOID +SerialUnmapHWResources( + IN PSERIAL_DEVICE_EXTENSION PDevExt + ); + +BOOLEAN +SerialGetRegistryKeyValue ( + IN WDFDEVICE WdfDevice, + _In_ PCWSTR Name, + OUT PULONG Value + ); + + +BOOLEAN +SerialPutRegistryKeyValue ( + IN WDFDEVICE WdfDevice, + _In_ PCWSTR Name, + IN ULONG Value + ); + +NTSTATUS +SerialInitController( + IN PSERIAL_DEVICE_EXTENSION pDevExt, + IN PCONFIG_DATA PConfigData + ); + +BOOLEAN +SerialCIsrSw( + IN WDFINTERRUPT Interrupt, + IN ULONG MessageID + ); + +NTSTATUS +SerialDoExternalNaming( + IN PSERIAL_DEVICE_EXTENSION PDevExt + ); + +PVOID +SerialGetMappedAddress( + PHYSICAL_ADDRESS IoAddress, + ULONG NumberOfBytes, + ULONG AddressSpace, + PBOOLEAN MappedAddress + ); + +BOOLEAN +SerialDoesPortExist( + IN PSERIAL_DEVICE_EXTENSION Extension, + PUNICODE_STRING InsertString, + IN ULONG ForceFifo, + IN ULONG LogFifo + ); + +SERIAL_MEM_COMPARES +SerialMemCompare( + IN PHYSICAL_ADDRESS A, + IN ULONG SpanOfA, + IN PHYSICAL_ADDRESS B, + IN ULONG SpanOfB + ); + +VOID +SerialUndoExternalNaming( + IN PSERIAL_DEVICE_EXTENSION Extension + ); + +VOID +SerialReleaseResources( + IN PSERIAL_DEVICE_EXTENSION PDevExt + ); + +VOID +SerialPurgePendingRequests( + PSERIAL_DEVICE_EXTENSION pDevExt + ); + +VOID +SerialDisableUART( + IN PVOID Context + ); + +VOID +SerialDrainUART( + IN PSERIAL_DEVICE_EXTENSION PDevExt, + IN PLARGE_INTEGER PDrainTime + ); + +VOID +SerialSaveDeviceState( + IN PSERIAL_DEVICE_EXTENSION PDevExt + ); + +NTSTATUS +SerialSetPowerPolicy( + IN PSERIAL_DEVICE_EXTENSION DeviceExtension + ); + +UINT32 +SerialReportMaxBaudRate( + ULONG Bauds + ); + +BOOLEAN +SerialInsertQueueDpc( + IN WDFDPC Dpc + ); + +BOOLEAN +SerialSetTimer( + IN WDFTIMER Timer, + IN LARGE_INTEGER DueTime + ); + +BOOLEAN +SerialCancelTimer( + IN WDFTIMER Timer, + IN PSERIAL_DEVICE_EXTENSION PDevExt + ); + +VOID +SerialUnlockPages( + IN WDFDPC PDpc, + IN PVOID PDeferredContext, + IN PVOID PSysContext1, + IN PVOID PSysContext2) + ; + +VOID +SerialMarkHardwareBroken( + IN PSERIAL_DEVICE_EXTENSION PDevExt + ); + +VOID +SerialDisableInterfacesResources( + IN PSERIAL_DEVICE_EXTENSION PDevExt, + IN BOOLEAN DisableUART + ); + +VOID +SerialSetDeviceFlags( + IN PSERIAL_DEVICE_EXTENSION PDevExt, + OUT PULONG PFlags, + IN ULONG Value, + IN BOOLEAN Set + ); + + +VOID +SetDeviceIsOpened( + IN PSERIAL_DEVICE_EXTENSION PDevExt, + IN BOOLEAN DeviceIsOpened, + IN BOOLEAN Reopen + ); + +BOOLEAN +IsQueueEmpty( + IN WDFQUEUE Queue + ); + +NTSTATUS +SerialCreateTimersAndDpcs( + IN PSERIAL_DEVICE_EXTENSION PDevExt + ); + +VOID +SerialDrainTimersAndDpcs( + IN PSERIAL_DEVICE_EXTENSION PDevExt + ); + +VOID +SerialSetCancelRoutine( + IN WDFREQUEST Request, + IN PFN_WDF_REQUEST_CANCEL CancelRoutine + ); + +NTSTATUS +SerialClearCancelRoutine( + IN WDFREQUEST Request, + IN BOOLEAN ClearReference + ); + +NTSTATUS +SerialWmiRegistration( + WDFDEVICE Device + ); + +NTSTATUS +SerialReadSymName( + IN WDFDEVICE Device, + _Out_writes_bytes_(*SizeOfRegName) PWSTR RegName, + _Inout_ PUSHORT SizeOfRegName + ); + +VOID +SerialCompleteRequest( + IN WDFREQUEST Request, + IN NTSTATUS Status, + IN ULONG_PTR Info + ); + +BOOLEAN +SerialGetFdoRegistryKeyValue( + IN PWDFDEVICE_INIT DeviceInit, + _In_ PCWSTR Name, + OUT PULONG Value + ); + +VOID +SerialSetInterruptPolicy( + _In_ WDFINTERRUPT WdfInterrupt + ); + +typedef struct _SERIAL_UPDATE_CHAR { + PSERIAL_DEVICE_EXTENSION Extension; + ULONG CharsCopied; + BOOLEAN Completed; + } SERIAL_UPDATE_CHAR,*PSERIAL_UPDATE_CHAR; + +// +// The following simple structure is used to send a pointer +// the device extension and an ioctl specific pointer +// to data. +// +typedef struct _SERIAL_IOCTL_SYNC { + PSERIAL_DEVICE_EXTENSION Extension; + PVOID Data; + } SERIAL_IOCTL_SYNC,*PSERIAL_IOCTL_SYNC; + + +// +// The following three macros are used to initialize, set +// and clear references in IRPs that are used by +// this driver. The reference is stored in the fourth +// argument of the request, which is never used by any operation +// accepted by this driver. +// + +#define SERIAL_REF_ISR (0x00000001) +#define SERIAL_REF_CANCEL (0x00000002) +#define SERIAL_REF_TOTAL_TIMER (0x00000004) +#define SERIAL_REF_INT_TIMER (0x00000008) +#define SERIAL_REF_XOFF_REF (0x00000010) + + +#define SERIAL_INIT_REFERENCE(ReqContext) { \ + (ReqContext)->RefCount = NULL; \ + } + +#define SERIAL_SET_REFERENCE(ReqContext, RefType) \ + do { \ + LONG _refType = (RefType); \ + PULONG_PTR _arg4 = (PVOID)&(ReqContext)->RefCount; \ + ASSERT(!(*_arg4 & _refType)); \ + *_arg4 |= _refType; \ + } WHILE (0) + +#define SERIAL_CLEAR_REFERENCE(ReqContext, RefType) \ + do { \ + LONG _refType = (RefType); \ + PULONG_PTR _arg4 = (PVOID)&(ReqContext)->RefCount; \ + ASSERT(*_arg4 & _refType); \ + *_arg4 &= ~_refType; \ + } WHILE (0) + +#define SERIAL_REFERENCE_COUNT(ReqContext) \ + ((ULONG_PTR)(((ReqContext)->RefCount))) + +#define SERIAL_TEST_REFERENCE(ReqContext, RefType) ((ULONG_PTR)ReqContext ->RefCount & RefType) + +// +// Prototypes and defines to handle processor groups. +// +typedef +USHORT +(*PFN_KE_GET_ACTIVE_GROUP_COUNT)( + VOID + ); + +typedef +KAFFINITY +(*PFN_KE_QUERY_GROUP_AFFINITY) ( + _In_ USHORT GroupNumber + ); + +// +// Force the serial interrupt to run on the last interrupt group. +// +//#define SERIAL_SELECT_INTERRUPT_GROUP 1 +#define SERIAL_LAST_INTERRUPT_GROUP 0xFFFF +#define SERIAL_PREFERRED_INTERRUPT_GROUP SERIAL_LAST_INTERRUPT_GROUP + + + diff --git a/tests/projects/wdk/kmdf/serial/serlog.mc b/tests/projects/wdk/kmdf/serial/serlog.mc new file mode 100644 index 000000000..ee6935b67 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/serlog.mc @@ -0,0 +1,290 @@ +;/*++ BUILD Version: 0001 // Increment this if a change has global effects +; +;Copyright (c) 1992, 1993 Microsoft Corporation +; +;Module Name: +; +; ntiologc.h +; +;Abstract: +; +; Constant definitions for the I/O error code log values. +; +;--*/ +; +;#ifndef _SERLOG_ +;#define _SERLOG_ +; +;// +;// Status values are 32 bit values layed out as follows: +;// +;// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +;// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +;// +---+-+-------------------------+-------------------------------+ +;// |Sev|C| Facility | Code | +;// +---+-+-------------------------+-------------------------------+ +;// +;// where +;// +;// Sev - is the severity code +;// +;// 00 - Success +;// 01 - Informational +;// 10 - Warning +;// 11 - Error +;// +;// C - is the Customer code flag +;// +;// Facility - is the facility code +;// +;// Code - is the facility's status code +;// +; +MessageIdTypedef=NTSTATUS + +SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS + Informational=0x1:STATUS_SEVERITY_INFORMATIONAL + Warning=0x2:STATUS_SEVERITY_WARNING + Error=0x3:STATUS_SEVERITY_ERROR + ) + +FacilityNames=(System=0x0 + RpcRuntime=0x2:FACILITY_RPC_RUNTIME + RpcStubs=0x3:FACILITY_RPC_STUBS + Io=0x4:FACILITY_IO_ERROR_CODE + Serial=0x6:FACILITY_SERIAL_ERROR_CODE + ) + + +MessageId=0x0001 Facility=Serial Severity=Informational SymbolicName=SERIAL_KERNEL_DEBUGGER_ACTIVE +Language=English +The kernel debugger is already using %2. +. + +MessageId=0x0002 Facility=Serial Severity=Informational SymbolicName=SERIAL_FIFO_PRESENT +Language=English +While validating that %2 was really a serial port, a fifo was detected. The fifo will be used. +. + +MessageId=0x0003 Facility=Serial Severity=Informational SymbolicName=SERIAL_USER_OVERRIDE +Language=English +User configuration data for parameter %2 overriding firmware configuration data. +. + +MessageId=0x0004 Facility=Serial Severity=Warning SymbolicName=SERIAL_NO_SYMLINK_CREATED +Language=English +Unable to create the symbolic link for %2. +. + +MessageId=0x0005 Facility=Serial Severity=Warning SymbolicName=SERIAL_NO_DEVICE_MAP_CREATED +Language=English +Unable to create the device map entry for %2. +. + +MessageId=0x0006 Facility=Serial Severity=Warning SymbolicName=SERIAL_NO_DEVICE_MAP_DELETED +Language=English +Unable to delete the device map entry for %2. +. + +MessageId=0x0007 Facility=Serial Severity=Error SymbolicName=SERIAL_UNREPORTED_IRQL_CONFLICT +Language=English +Another driver on the system, which did not report its resources, has already claimed the interrupt used by %2. +. + +MessageId=0x0008 Facility=Serial Severity=Error SymbolicName=SERIAL_INSUFFICIENT_RESOURCES +Language=English +Not enough resources were available for the driver. +. + +MessageId=0x0009 Facility=Serial Severity=Error SymbolicName=SERIAL_UNSUPPORTED_CLOCK_RATE +Language=English +The baud clock rate configuration is not supported on device %2. +. + +MessageId=0x000A Facility=Serial Severity=Error SymbolicName=SERIAL_REGISTERS_NOT_MAPPED +Language=English +The hardware locations for %2 could not be translated to something the memory management system could understand. +. + +MessageId=0x000B Facility=Serial Severity=Error SymbolicName=SERIAL_RESOURCE_CONFLICT +Language=English +The hardware resources for %2 are already in use by another device. +. + +MessageId=0x000C Facility=Serial Severity=Error SymbolicName=SERIAL_NO_BUFFER_ALLOCATED +Language=English +No memory could be allocated in which to place new data for %2. +. + +MessageId=0x000D Facility=Serial Severity=Error SymbolicName=SERIAL_IER_INVALID +Language=English +While validating that %2 was really a serial port, the interrupt enable register contained enabled bits in a must be zero bitfield. +The device is assumed not to be a serial port and will be deleted. +. + +MessageId=0x000E Facility=Serial Severity=Error SymbolicName=SERIAL_MCR_INVALID +Language=English +While validating that %2 was really a serial port, the modem control register contained enabled bits in a must be zero bitfield. +The device is assumed not to be a serial port and will be deleted. +. + +MessageId=0x000F Facility=Serial Severity=Error SymbolicName=SERIAL_IIR_INVALID +Language=English +While validating that %2 was really a serial port, the interrupt id register contained enabled bits in a must be zero bitfield. +The device is assumed not to be a serial port and will be deleted. +. + +MessageId=0x0010 Facility=Serial Severity=Error SymbolicName=SERIAL_DL_INVALID +Language=English +While validating that %2 was really a serial port, the baud rate register could not be set consistantly. +The device is assumed not to be a serial port and will be deleted. +. + +MessageId=0x0011 Facility=Serial Severity=Error SymbolicName=SERIAL_NOT_ENOUGH_CONFIG_INFO +Language=English +Some firmware configuration information was incomplete. +. + +MessageId=0x0012 Facility=Serial Severity=Error SymbolicName=SERIAL_NO_PARAMETERS_INFO +Language=English +No Parameters subkey was found for user defined data. This is odd, and it also means no user configuration can be found. +. + +MessageId=0x0013 Facility=Serial Severity=Error SymbolicName=SERIAL_UNABLE_TO_ACCESS_CONFIG +Language=English +Specific user configuration data is unretrievable. +. + +MessageId=0x0014 Facility=Serial Severity=Error SymbolicName=SERIAL_INVALID_PORT_INDEX +Language=English +On parameter %2 which indicates a multiport card, must have a port index specified greater than 0. +. + +MessageId=0x0015 Facility=Serial Severity=Error SymbolicName=SERIAL_PORT_INDEX_TOO_HIGH +Language=English +On parameter %2 which indicates a multiport card, the port index for the multiport card is too large. +. + +MessageId=0x0016 Facility=Serial Severity=Error SymbolicName=SERIAL_UNKNOWN_BUS +Language=English +The bus type for %2 is not recognizable. +. + +MessageId=0x0017 Facility=Serial Severity=Error SymbolicName=SERIAL_BUS_NOT_PRESENT +Language=English +The bus type for %2 is not available on this computer. +. + +MessageId=0x0018 Facility=Serial Severity=Error SymbolicName=SERIAL_BUS_INTERRUPT_CONFLICT +Language=English +The bus specified for %2 does not support the specified method of interrupt. +. + +MessageId=0x0019 Facility=Serial Severity=Error SymbolicName=SERIAL_INVALID_USER_CONFIG +Language=English +User configuration for parameter %2 must have %3. +. + +MessageId=0x001A Facility=Serial Severity=Error SymbolicName=SERIAL_DEVICE_TOO_HIGH +Language=English +The user specified port for %2 is way too high in physical memory. +. + +MessageId=0x001B Facility=Serial Severity=Error SymbolicName=SERIAL_STATUS_TOO_HIGH +Language=English +The status port for %2 is way too high in physical memory. +. + +MessageId=0x001C Facility=Serial Severity=Error SymbolicName=SERIAL_STATUS_CONTROL_CONFLICT +Language=English +The status port for %2 overlaps the control registers for the device. +. + +MessageId=0x001D Facility=Serial Severity=Error SymbolicName=SERIAL_CONTROL_OVERLAP +Language=English +The control registers for %2 overlaps with the %3 control registers. +. + +MessageId=0x001E Facility=Serial Severity=Error SymbolicName=SERIAL_STATUS_OVERLAP +Language=English +The status register for %2 overlaps the %3 control registers. +. + +MessageId=0x001F Facility=Serial Severity=Error SymbolicName=SERIAL_STATUS_STATUS_OVERLAP +Language=English +The status register for %2 overlaps with the %3 status register. +. + +MessageId=0x0020 Facility=Serial Severity=Error SymbolicName=SERIAL_CONTROL_STATUS_OVERLAP +Language=English +The control registers for %2 overlaps the %3 status register. +. + +MessageId=0x0021 Facility=Serial Severity=Error SymbolicName=SERIAL_MULTI_INTERRUPT_CONFLICT +Language=English +Two ports, %2 and %3, on a single multiport card can't have two different interrupts. +. + +MessageId=0x0022 Facility=Serial Severity=Informational SymbolicName=SERIAL_DISABLED_PORT +Language=English +Disabling %2 as requested by the configuration data. +. + +MessageId=0x0023 Facility=Serial Severity=Error SymbolicName=SERIAL_GARBLED_PARAMETER +Language=English +Parameter %2 data is unretrievable from the registry. +. + +MessageId=0x0024 Facility=Serial Severity=Error SymbolicName=SERIAL_DLAB_INVALID +Language=English +While validating that %2 was really a serial port, the contents of the divisor latch register was identical to the interrupt enable and the receive registers. +The device is assumed not to be a serial port and will be deleted. +. + +MessageId=0x0025 Facility=Serial Severity=Error SymbolicName=SERIAL_NO_TRANSLATE_PORT +Language=English +Could not translate the user reported I/O port for %2. +. + +MessageId=0x0026 Facility=Serial Severity=Error SymbolicName=SERIAL_NO_GET_INTERRUPT +Language=English +Could not get the user reported interrupt for %2 from the HAL. +. + +MessageId=0x0027 Facility=Serial Severity=Error SymbolicName=SERIAL_NO_TRANSLATE_ISR +Language=English +Could not translate the user reported Interrupt Status Register for %2. +. + +MessageId=0x0028 Facility=Serial Severity=Error SymbolicName=SERIAL_NO_DEVICE_REPORT +Language=English +Could not report the discovered legacy device %2 to the IO subsystem. +. + +MessageId=0x0029 Facility=Serial Severity=Error SymbolicName=SERIAL_REGISTRY_WRITE_FAILED +Language=English +Error writing to the registry. +. + +MessageId=0x002A Facility=Serial Severity=Warning SymbolicName=SERIAL_MOUSE_CONFLICT_IRQ +Language=English +There is a serial mouse using the same interrupt as %2. Therefore, %2 will not be started. +. + +MessageId=0x002B Facility=Serial Severity=Warning SymbolicName=SERIAL_MOUSE_ON_PORT +Language=English +There was a serial mouse found on %2. Therefore, %2 will be assigned to the mouse. +. + +MessageId=0x002C Facility=Serial Severity=Error SymbolicName=SERIAL_NO_DEVICE_REPORT_RES +Language=English +Could not report device %2 to IO subsystem due to a resource conflict. +. + +MessageId=0x002D Facility=Serial Severity=Error SymbolicName=SERIAL_HARDWARE_FAILURE +Language=English +The serial driver detected a hardware failure on device %2 and will disable this device. +. + +;#endif /* _NTIOLOGC_ */ + diff --git a/tests/projects/wdk/kmdf/serial/trace.h b/tests/projects/wdk/kmdf/serial/trace.h new file mode 100644 index 000000000..5bd9d50ca --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/trace.h @@ -0,0 +1,118 @@ +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +Module Name: + + TRACE.h + +Abstract: + + Header file for the debug tracing related function defintions and macros. + +Environment: + + Kernel mode + +--*/ + +#include // For TRACE_LEVEL definitions + +#if !defined(EVENT_TRACING) + +// +// TODO: These defines are missing in evntrace.h +// in some DDK build environments (XP). +// +#if !defined(TRACE_LEVEL_NONE) + #define TRACE_LEVEL_NONE 0 + #define TRACE_LEVEL_CRITICAL 1 + #define TRACE_LEVEL_FATAL 1 + #define TRACE_LEVEL_ERROR 2 + #define TRACE_LEVEL_WARNING 3 + #define TRACE_LEVEL_INFORMATION 4 + #define TRACE_LEVEL_VERBOSE 5 + #define TRACE_LEVEL_RESERVED6 6 + #define TRACE_LEVEL_RESERVED7 7 + #define TRACE_LEVEL_RESERVED8 8 + #define TRACE_LEVEL_RESERVED9 9 +#endif + + +// +// Define Debug Flags +// +#define DBG_INIT 0x00000001 +#define DBG_PNP 0x00000002 +#define DBG_POWER 0x00000004 +#define DBG_WMI 0x00000008 +#define DBG_CREATE_CLOSE 0x00000010 +#define DBG_IOCTLS 0x00000020 +#define DBG_WRITE 0x00000040 +#define DBG_READ 0x00000080 +#define DBG_DPC 0x00000100 +#define DBG_INTERRUPT 0x00000200 +#define DBG_LOCKS 0x00000400 +#define DBG_QUEUEING 0x00000800 +#define DBG_HW_ACCESS 0x00001000 + +VOID +TraceEvents ( + IN ULONG DebugPrintLevel, + IN ULONG DebugPrintFlag, + IN PCCHAR DebugMessage, + ... + ); + +#define WPP_INIT_TRACING(DriverObject, RegistryPath) +#define WPP_CLEANUP(DriverObject) + +#else +// +// If software tracing is defined in the sources file.. +// WPP_DEFINE_CONTROL_GUID specifies the GUID used for this driver. +// *** REPLACE THE GUID WITH YOUR OWN UNIQUE ID *** +// WPP_DEFINE_BIT allows setting debug bit masks to selectively print. +// The names defined in the WPP_DEFINE_BIT call define the actual names +// that are used to control the level of tracing for the control guid +// specified. +// +// Name of the logger is Serial and the guid is +// {F3A79AB6-9827-4419-9465-45CF949EF659} +// (0xf3a79ab6, 0x9827, 0x4419, 0x94, 0x65, 0x45, 0xcf, 0x94, 0x9e, 0xf6, 0x59); +// + +#define WPP_CHECK_FOR_NULL_STRING //to prevent exceptions due to NULL strings + +#define WPP_CONTROL_GUIDS \ + WPP_DEFINE_CONTROL_GUID(SerialTraceGuid,(bc6c9364,fc67,42c5,acf7,abed3b12ecc6), \ + WPP_DEFINE_BIT(DBG_INIT) /* bit 0 = 0x00000001 */ \ + WPP_DEFINE_BIT(DBG_PNP) /* bit 1 = 0x00000002 */ \ + WPP_DEFINE_BIT(DBG_POWER) /* bit 2 = 0x00000004 */ \ + WPP_DEFINE_BIT(DBG_WMI) /* bit 3 = 0x00000008 */ \ + WPP_DEFINE_BIT(DBG_CREATE_CLOSE) /* bit 4 = 0x00000010 */ \ + WPP_DEFINE_BIT(DBG_IOCTLS) /* bit 5 = 0x00000020 */ \ + WPP_DEFINE_BIT(DBG_WRITE) /* bit 6 = 0x00000040 */ \ + WPP_DEFINE_BIT(DBG_READ) /* bit 7 = 0x00000080 */ \ + WPP_DEFINE_BIT(DBG_DPC) /* bit 8 = 0x00000100 */ \ + WPP_DEFINE_BIT(DBG_INTERRUPT) /* bit 9 = 0x00000200 */ \ + WPP_DEFINE_BIT(DBG_LOCKS) /* bit 10 = 0x00000400 */ \ + WPP_DEFINE_BIT(DBG_QUEUEING) /* bit 11 = 0x00000800 */ \ + WPP_DEFINE_BIT(DBG_HW_ACCESS) /* bit 12 = 0x00001000 */ \ + /* You can have up to 32 defines. If you want more than that,\ + you have to provide another trace control GUID */\ + ) + + +#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) WPP_LEVEL_LOGGER(flags) +#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl) + + +#endif + + diff --git a/tests/projects/wdk/kmdf/serial/utils.c b/tests/projects/wdk/kmdf/serial/utils.c new file mode 100644 index 000000000..a84136efe --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/utils.c @@ -0,0 +1,1946 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + utils.c + +Abstract: + + This module contains code that perform queueing and completion + manipulation on requests. Also module generic functions such + as error logging. + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "utils.tmh" +#endif + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGESRP0,SerialMemCompare) +#pragma alloc_text(PAGESRP0,SerialLogError) +#pragma alloc_text(PAGESRP0,SerialMarkHardwareBroken) +#endif // ALLOC_PRAGMA + + +VOID +SerialRundownIrpRefs( + IN WDFREQUEST *CurrentOpRequest, + IN WDFTIMER IntervalTimer, + IN WDFTIMER TotalTimer, + IN PSERIAL_DEVICE_EXTENSION PDevExt, + IN LONG RefType + ); + +static const PHYSICAL_ADDRESS SerialPhysicalZero = {0}; + +VOID +SerialPurgeRequests( + IN WDFQUEUE QueueToClean, + IN WDFREQUEST *CurrentOpRequest + ) + +/*++ + +Routine Description: + + This function is used to cancel all queued and the current irps + for reads or for writes. Called at DPC level. + +Arguments: + + QueueToClean - A pointer to the queue which we're going to clean out. + + CurrentOpRequest - Pointer to a pointer to the current request. + +Return Value: + + None. + +--*/ + +{ + NTSTATUS status; + PREQUEST_CONTEXT reqContext; + + WdfIoQueuePurge(QueueToClean, WDF_NO_EVENT_CALLBACK, WDF_NO_CONTEXT); + + // + // The queue is clean. Now go after the current if + // it's there. + // + + if (*CurrentOpRequest) { + + PFN_WDF_REQUEST_CANCEL CancelRoutine; + + reqContext = SerialGetRequestContext(*CurrentOpRequest); + CancelRoutine = reqContext->CancelRoutine; + // + // Clear the common cancel routine but don't clear the reference because the + // request specific cancel routine called below will clear the reference. + // + status = SerialClearCancelRoutine(*CurrentOpRequest, FALSE); + if (NT_SUCCESS(status)) { + // + // Let us just call the CancelRoutine to start the next request. + // + if(CancelRoutine) { + CancelRoutine(*CurrentOpRequest); + } + } + } +} + +VOID +SerialFlushRequests( + IN WDFQUEUE QueueToClean, + IN WDFREQUEST *CurrentOpRequest + ) + +/*++ + +Routine Description: + + This function is used to cancel all queued and the current irps + for reads or for writes. Called at DPC level. + +Arguments: + + QueueToClean - A pointer to the queue which we're going to clean out. + + CurrentOpRequest - Pointer to a pointer to the current request. + +Return Value: + + None. + +--*/ + +{ + SerialPurgeRequests(QueueToClean, CurrentOpRequest); + + // + // Since purge puts the queue state to fail requests, we have to explicitly + // change the queue state to accept requests. + // + WdfIoQueueStart(QueueToClean); + +} + + +VOID +SerialGetNextRequest( + IN WDFREQUEST * CurrentOpRequest, + IN WDFQUEUE QueueToProcess, + OUT WDFREQUEST * NextRequest, + IN BOOLEAN CompleteCurrent, + IN PSERIAL_DEVICE_EXTENSION Extension + ) + +/*++ + +Routine Description: + + This function is used to make the head of the particular + queue the current request. It also completes the what + was the old current request if desired. + +Arguments: + + CurrentOpRequest - Pointer to a pointer to the currently active + request for the particular work list. Note that + this item is not actually part of the list. + + QueueToProcess - The list to pull the new item off of. + + NextIrp - The next Request to process. Note that CurrentOpRequest + will be set to this value under protection of the + cancel spin lock. However, if *NextIrp is NULL when + this routine returns, it is not necessaryly true the + what is pointed to by CurrentOpRequest will also be NULL. + The reason for this is that if the queue is empty + when we hold the cancel spin lock, a new request may come + in immediately after we release the lock. + + CompleteCurrent - If TRUE then this routine will complete the + request pointed to by the pointer argument + CurrentOpRequest. + +Return Value: + + None. + +--*/ + +{ + WDFREQUEST oldRequest = NULL; + PREQUEST_CONTEXT reqContext; + NTSTATUS status; + + UNREFERENCED_PARAMETER(Extension); + + oldRequest = *CurrentOpRequest; + *CurrentOpRequest = NULL; + + // + // Check to see if there is a new request to start up. + // + + status = WdfIoQueueRetrieveNextRequest( + QueueToProcess, + CurrentOpRequest + ); + + if(!NT_SUCCESS(status)) { + ASSERTMSG("WdfIoQueueRetrieveNextRequest failed", + status == STATUS_NO_MORE_ENTRIES); + } + + *NextRequest = *CurrentOpRequest; + + if (CompleteCurrent) { + + if (oldRequest) { + + reqContext = SerialGetRequestContext(oldRequest); + + SerialCompleteRequest(oldRequest, + reqContext->Status, + reqContext->Information); + } + } +} + +VOID +SerialTryToCompleteCurrent( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN PFN_WDF_INTERRUPT_SYNCHRONIZE SynchRoutine OPTIONAL, + IN NTSTATUS StatusToUse, + IN WDFREQUEST *CurrentOpRequest, + IN WDFQUEUE QueueToProcess OPTIONAL, + IN WDFTIMER IntervalTimer OPTIONAL, + IN WDFTIMER TotalTimer OPTIONAL, + IN PSERIAL_START_ROUTINE Starter OPTIONAL, + IN PSERIAL_GET_NEXT_ROUTINE GetNextRequest OPTIONAL, + IN LONG RefType + ) + +/*++ + +Routine Description: + + This routine attempts to remove all of the reasons there are + references on the current read/write. If everything can be completed + it will complete this read/write and try to start another. + + NOTE: This routine assumes that it is called with the cancel + spinlock held. + +Arguments: + + Extension - Simply a pointer to the device extension. + + SynchRoutine - A routine that will synchronize with the isr + and attempt to remove the knowledge of the + current request from the isr. NOTE: This pointer + can be null. + + IrqlForRelease - This routine is called with the cancel spinlock held. + This is the irql that was current when the cancel + spinlock was acquired. + + StatusToUse - The request's status field will be set to this value, if + this routine can complete the request. + + +Return Value: + + None. + +--*/ + +{ + PREQUEST_CONTEXT reqContext; + + ASSERTMSG("SerialTryToCompleteCurrent: CurrentOpRequest is NULL", *CurrentOpRequest); + + reqContext = SerialGetRequestContext(*CurrentOpRequest); + + if(RefType == SERIAL_REF_ISR || RefType == SERIAL_REF_XOFF_REF) { + // + // We can decrement the reference to "remove" the fact + // that the caller no longer will be accessing this request. + // + + SERIAL_CLEAR_REFERENCE( + reqContext, + RefType + ); + } + + if (SynchRoutine) { + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SynchRoutine, + Extension + ); + + } + + // + // Try to run down all other references to this request. + // + + SerialRundownIrpRefs( + CurrentOpRequest, + IntervalTimer, + TotalTimer, + Extension, + RefType + ); + + if(StatusToUse == STATUS_CANCELLED) { + // + // This function is called from a cancelroutine. So mark + // the request as cancelled. We need to do this because + // we may not complete the request below if somebody + // else has a reference to it. + // This state variable was added to avoid calling + // WdfRequestMarkCancelable second time on a request that + // has cancelled but wasn't completed in the cancel routine. + // + reqContext->Cancelled = TRUE; + } + + // + // See if the ref count is zero after trying to complete everybody else. + // + + if (!SERIAL_REFERENCE_COUNT(reqContext)) { + + WDFREQUEST newRequest; + + + // + // The ref count was zero so we should complete this + // request. + // + // The following call will also cause the current request to be + // completed. + // + + reqContext->Status = StatusToUse; + + if (StatusToUse == STATUS_CANCELLED) { + + reqContext->Information = 0; + + } + + if (GetNextRequest) { + + GetNextRequest( + CurrentOpRequest, + QueueToProcess, + &newRequest, + TRUE, + Extension + ); + + if (newRequest) { + + Starter(Extension); + + } + + } else { + + WDFREQUEST oldRequest = *CurrentOpRequest; + + // + // There was no get next routine. We will simply complete + // the request. We should make sure that we null out the + // pointer to the pointer to this request. + // + + *CurrentOpRequest = NULL; + + SerialCompleteRequest(oldRequest, + reqContext->Status, + reqContext->Information); + } + + } else { + + + } + +} + + +VOID +SerialEvtIoStop( + IN WDFQUEUE Queue, + IN WDFREQUEST Request, + IN ULONG ActionFlags + ) +/*++ + +Routine Description: + + This callback is invoked for every request pending in the driver (not queue) - + in-flight request. The Action parameter tells us why the callback is invoked - + because the device is being stopped, removed or suspended. In this + driver, we have told the framework not to stop or remove when there + are pending requests, so only reason for this callback is when the system is + suspending. + +Arguments: + + Queue - Queue the request currently belongs to + Request - Request that is currently out of queue and being processed by the driver + Action - Reason for this callback + +Return Value: + + None. Acknowledge the request so that framework can contiue suspending the + device. + +--*/ +{ + PREQUEST_CONTEXT reqContext; + + UNREFERENCED_PARAMETER(Queue); + + reqContext = SerialGetRequestContext(Request); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, + "--> SerialEvtIoStop %x %p\n", ActionFlags, Request); + + // + // System suspends all the timers before asking the driver to goto + // sleep. So let us not worry about cancelling the timers. Also the + // framework will disconnect the interrupt before calling our + // D0Exit handler so we can be sure that nobody will touch the hardware. + // So just acknowledge callback to say that we are okay to stop due to + // system suspend. Please note that since we have taken a power reference + // we will never idle out when there is an open handle. Also we have told + // the framework to not stop for resource rebalancing or remove when there are + // open handles, so let us not worry about that either. + // + if (ActionFlags & WdfRequestStopRequestCancelable) { + PFN_WDF_REQUEST_CANCEL cancelRoutine; + + // + // Request is in a cancelable state. So unmark cancelable before you + // acknowledge. We will mark the request cancelable when we resume. + // + cancelRoutine = reqContext->CancelRoutine; + + SerialClearCancelRoutine(Request, TRUE); + + // + // SerialClearCancelRoutine clears the cancel-routine. So set it back + // in the context. We will need that when we resume. + // + reqContext->CancelRoutine = cancelRoutine; + + reqContext->MarkCancelableOnResume = TRUE; + + ActionFlags &= ~WdfRequestStopRequestCancelable; + } + + ASSERT(ActionFlags == WdfRequestStopActionSuspend); + + WdfRequestStopAcknowledge(Request, FALSE); // Don't requeue the request + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, + "<-- SerialEvtIoStop \n"); +} + +VOID +SerialEvtIoResume( + IN WDFQUEUE Queue, + IN WDFREQUEST Request + ) +/*++ + +Routine Description: + + This callback is invoked for every request pending in the driver - in-flight + request - to notify that the hardware is ready for contiuing the processing + of the request. + +Arguments: + + Queue - Queue the request currently belongs to + Request - Request that is currently out of queue and being processed by the driver + +Return Value: + + None. + +--*/ +{ + PREQUEST_CONTEXT reqContext; + + UNREFERENCED_PARAMETER(Queue); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, + "--> SerialEvtIoResume %p \n", Request); + + reqContext = SerialGetRequestContext(Request); + + // + // If we unmarked cancelable on suspend, let us mark it cancelable again. + // + if (reqContext->MarkCancelableOnResume) { + SerialSetCancelRoutine(Request, reqContext->CancelRoutine); + reqContext->MarkCancelableOnResume = FALSE; + } + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, + "<-- SerialEvtIoResume \n"); +} + +VOID +SerialRundownIrpRefs( + IN WDFREQUEST *CurrentOpRequest, + IN WDFTIMER IntervalTimer OPTIONAL, + IN WDFTIMER TotalTimer OPTIONAL, + IN PSERIAL_DEVICE_EXTENSION PDevExt, + IN LONG RefType + ) + +/*++ + +Routine Description: + + This routine runs through the various items that *could* + have a reference to the current read/write. It try's to remove + the reason. If it does succeed in removing the reason it + will decrement the reference count on the request. + + NOTE: This routine assumes that it is called with the cancel + spin lock held. + +Arguments: + + CurrentOpRequest - Pointer to a pointer to current request for the + particular operation. + + IntervalTimer - Pointer to the interval timer for the operation. + NOTE: This could be null. + + TotalTimer - Pointer to the total timer for the operation. + NOTE: This could be null. + + PDevExt - Pointer to device extension + +Return Value: + + None. + +--*/ + + +{ + PREQUEST_CONTEXT reqContext; + WDFREQUEST request = *CurrentOpRequest; + + reqContext = SerialGetRequestContext(request); + + if(RefType == SERIAL_REF_CANCEL) { + // + // Caller is a cancel routine. So just clear the reference. + // + SERIAL_CLEAR_REFERENCE( reqContext, SERIAL_REF_CANCEL ); + reqContext->CancelRoutine = NULL; + + } else { + // + // Try to clear the cancelable state. + // + SerialClearCancelRoutine(request, TRUE); + } + if (IntervalTimer) { + + // + // Try to cancel the operations interval timer. If the operation + // returns true then the timer did have a reference to the + // request. Since we've canceled this timer that reference is + // no longer valid and we can decrement the reference count. + // + // If the cancel returns false then this means either of two things: + // + // a) The timer has already fired. + // + // b) There never was an interval timer. + // + // In the case of "b" there is no need to decrement the reference + // count since the "timer" never had a reference to it. + // + // In the case of "a", then the timer itself will be coming + // along and decrement it's reference. Note that the caller + // of this routine might actually be the this timer, so + // decrement the reference. + // + + if (SerialCancelTimer(IntervalTimer, PDevExt)) { + + SERIAL_CLEAR_REFERENCE( + reqContext, + SERIAL_REF_INT_TIMER + ); + + } else if(RefType == SERIAL_REF_INT_TIMER) { // caller is the timer + + SERIAL_CLEAR_REFERENCE( + reqContext, + SERIAL_REF_INT_TIMER + ); + } + + } + + if (TotalTimer) { + + // + // Try to cancel the operations total timer. If the operation + // returns true then the timer did have a reference to the + // request. Since we've canceled this timer that reference is + // no longer valid and we can decrement the reference count. + // + // If the cancel returns false then this means either of two things: + // + // a) The timer has already fired. + // + // b) There never was an total timer. + // + // In the case of "b" there is no need to decrement the reference + // count since the "timer" never had a reference to it. + // + // In the case of "a", then the timer itself will be coming + // along and decrement it's reference. Note that the caller + // of this routine might actually be the this timer, so + // decrement the reference. + // + + if (SerialCancelTimer(TotalTimer, PDevExt)) { + + SERIAL_CLEAR_REFERENCE( + reqContext, + SERIAL_REF_TOTAL_TIMER + ); + + } else if(RefType == SERIAL_REF_TOTAL_TIMER) { // caller is the timer + + SERIAL_CLEAR_REFERENCE( + reqContext, + SERIAL_REF_TOTAL_TIMER + ); + } + } +} + + +VOID +SerialStartOrQueue( + IN PSERIAL_DEVICE_EXTENSION Extension, + IN WDFREQUEST Request, + IN WDFQUEUE QueueToExamine, + IN WDFREQUEST *CurrentOpRequest, + IN PSERIAL_START_ROUTINE Starter + ) + +/*++ + +Routine Description: + + This routine is used to either start or queue any requst + that can be queued in the driver. + +Arguments: + + Extension - Points to the serial device extension. + + Request - The request to either queue or start. In either + case the request will be marked pending. + + QueueToExamine - The queue the request will be place on if there + is already an operation in progress. + + CurrentOpRequest - Pointer to a pointer to the request the is current + for the queue. The pointer pointed to will be + set with to Request if what CurrentOpRequest points to + is NULL. + + Starter - The routine to call if the queue is empty. + +Return Value: + + +--*/ + +{ + + NTSTATUS status; + PREQUEST_CONTEXT reqContext; + WDF_REQUEST_PARAMETERS params; + + reqContext = SerialGetRequestContext(Request); + + WDF_REQUEST_PARAMETERS_INIT(¶ms); + + WdfRequestGetParameters( + Request, + ¶ms); + + // + // If this is a write request then take the amount of characters + // to write and add it to the count of characters to write. + // + + if (params.Type == WdfRequestTypeWrite) { + + Extension->TotalCharsQueued += reqContext->Length; + + } else if ((params.Type == WdfRequestTypeDeviceControl) && + ((params.Parameters.DeviceIoControl.IoControlCode == IOCTL_SERIAL_IMMEDIATE_CHAR) || + (params.Parameters.DeviceIoControl.IoControlCode == IOCTL_SERIAL_XOFF_COUNTER))) { + + reqContext->IoctlCode = params.Parameters.DeviceIoControl.IoControlCode; // We need this in the destroy callback + + Extension->TotalCharsQueued++; + + } + + if (IsQueueEmpty(QueueToExamine) && !(*CurrentOpRequest)) { + + // + // There were no current operation. Mark this one as + // current and start it up. + // + + *CurrentOpRequest = Request; + + Starter(Extension); + + return; + + } else { + + // + // We don't know how long the request will be in the + // queue. If it gets cancelled while waiting in the queue, we will + // be notified by EvtCanceledOnQueue callback so that we can readjust + // the lenght or free the buffer. + // + reqContext->Extension = Extension; // We need this in the destroy callback + + status = WdfRequestForwardToIoQueue(Request, QueueToExamine); + if(!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_READ, "WdfRequestForwardToIoQueue failed%X\n", status); + ASSERTMSG("WdfRequestForwardToIoQueue failed ", FALSE); + SerialCompleteRequest(Request, status, 0); + } + + return; + } +} + +VOID +SerialEvtCanceledOnQueue( + IN WDFQUEUE Queue, + IN WDFREQUEST Request + ) + +/*++ + +Routine Description: + + Called when the request is cancelled while it's waiting + on the queue. This callback is used instead of EvtCleanupCallback + on the request because this one will be called with the + presentation lock held. + + +Arguments: + + Queue - Queue in which the request currently waiting + Request - Request being cancelled + + +Return Value: + + None. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION extension = NULL; + PREQUEST_CONTEXT reqContext; + + UNREFERENCED_PARAMETER(Queue); + + reqContext = SerialGetRequestContext(Request); + + extension = reqContext->Extension; + + // + // If this is a write request then take the amount of characters + // to write and subtract it from the count of characters to write. + // + + if (reqContext->MajorFunction == IRP_MJ_WRITE) { + + extension->TotalCharsQueued -= reqContext->Length; + + } else if (reqContext->MajorFunction == IRP_MJ_DEVICE_CONTROL) { + + // + // If it's an immediate then we need to decrement the + // count of chars queued. If it's a resize then we + // need to deallocate the pool that we're passing on + // to the "resizing" routine. + // + + if (( reqContext->IoctlCode == IOCTL_SERIAL_IMMEDIATE_CHAR) || + (reqContext->IoctlCode == IOCTL_SERIAL_XOFF_COUNTER)) { + + extension->TotalCharsQueued--; + + } else if (reqContext->IoctlCode == IOCTL_SERIAL_SET_QUEUE_SIZE) { + + // + // We shoved the pointer to the memory into the + // the type 3 buffer pointer which we KNOW we + // never use. + // + + ASSERT(reqContext->Type3InputBuffer); + + ExFreePool(reqContext->Type3InputBuffer); + + reqContext->Type3InputBuffer = NULL; + + } + + } + + SerialCompleteRequest(Request, WdfRequestGetStatus(Request), 0); +} + + +NTSTATUS +SerialCompleteIfError( + PSERIAL_DEVICE_EXTENSION extension, + WDFREQUEST Request + ) + +/*++ + +Routine Description: + + If the current request is not an IOCTL_SERIAL_GET_COMMSTATUS request and + there is an error and the application requested abort on errors, + then cancel the request. + +Arguments: + + extension - Pointer to the device context + + Request - Pointer to the WDFREQUEST to test. + +Return Value: + + STATUS_SUCCESS or STATUS_CANCELLED. + +--*/ + +{ + + WDF_REQUEST_PARAMETERS params; + NTSTATUS status = STATUS_SUCCESS; + + if ((extension->HandFlow.ControlHandShake & + SERIAL_ERROR_ABORT) && extension->ErrorWord) { + + WDF_REQUEST_PARAMETERS_INIT(¶ms); + + WdfRequestGetParameters( + Request, + ¶ms + ); + + + // + // There is a current error in the driver. No requests should + // come through except for the GET_COMMSTATUS. + // + + if ((params.Type != WdfRequestTypeDeviceControl) || + (params.Parameters.DeviceIoControl.IoControlCode != IOCTL_SERIAL_GET_COMMSTATUS)) { + status = STATUS_CANCELLED; + SerialCompleteRequest(Request, status, 0); + } + + } + + return status; + +} + +NTSTATUS +SerialCreateTimersAndDpcs( + IN PSERIAL_DEVICE_EXTENSION pDevExt + ) +/*++ + +Routine Description: + + This function creates all the timers and DPC objects. All the objects + are associated with the WDFDEVICE and the callbacks are serialized + with the device callbacks. Also these objects will be deleted automatically + when the device is deleted, so there is no need for the driver to explicitly + delete the objects. + +Arguments: + + PDevExt - Pointer to the device extension for the device + +Return Value: + + return NTSTATUS + +--*/ +{ + WDF_DPC_CONFIG dpcConfig; + WDF_TIMER_CONFIG timerConfig; + NTSTATUS status; + WDF_OBJECT_ATTRIBUTES dpcAttributes; + WDF_OBJECT_ATTRIBUTES timerAttributes; + + // + // Initialize all the timers used to timeout operations. + // + // + // This timer dpc is fired off if the timer for the total timeout + // for the read expires. It will cause the current read to complete. + // + + WDF_TIMER_CONFIG_INIT(&timerConfig, SerialReadTimeout); + + timerConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + timerAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfTimerCreate(&timerConfig, + &timerAttributes, + &pDevExt->ReadRequestTotalTimer); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(ReadRequestTotalTimer) failed [%#08lx]\n", status); + return status; + } + + // + // This dpc is fired off if the timer for the interval timeout + // expires. If no more characters have been read then the + // dpc routine will cause the read to complete. However, if + // more characters have been read then the dpc routine will + // resubmit the timer. + // + WDF_TIMER_CONFIG_INIT(&timerConfig, SerialIntervalReadTimeout); + + timerConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + timerAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfTimerCreate(&timerConfig, + &timerAttributes, + &pDevExt->ReadRequestIntervalTimer); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(ReadRequestIntervalTimer) failed [%#08lx]\n", status); + return status; + } + + // + // This dpc is fired off if the timer for the total timeout + // for the write expires. It will queue a dpc routine that + // will cause the current write to complete. + // + // + + WDF_TIMER_CONFIG_INIT(&timerConfig, SerialWriteTimeout); + + timerConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + timerAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfTimerCreate(&timerConfig, + &timerAttributes, + &pDevExt->WriteRequestTotalTimer); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(WriteRequestTotalTimer) failed [%#08lx]\n", status); + return status; + } + + // + // This dpc is fired off if the transmit immediate char + // character times out. The dpc routine will "grab" the + // request from the isr and time it out. + // + WDF_TIMER_CONFIG_INIT(&timerConfig, SerialTimeoutImmediate); + + timerConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + timerAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfTimerCreate(&timerConfig, + &timerAttributes, + &pDevExt->ImmediateTotalTimer); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(ImmediateTotalTimer) failed [%#08lx]\n", status); + return status; + } + + // + // This dpc is fired off if the timer used to "timeout" counting + // the number of characters received after the Xoff ioctl is started + // expired. + // + + WDF_TIMER_CONFIG_INIT(&timerConfig, SerialTimeoutXoff); + + timerConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + timerAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfTimerCreate(&timerConfig, + &timerAttributes, + &pDevExt->XoffCountTimer); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(XoffCountTimer) failed [%#08lx]\n", status); + return status; + } + + // + // This dpc is fired off when a timer expires (after one + // character time), so that code can be invoked that will + // check to see if we should lower the RTS line when + // doing transmit toggling. + // + WDF_TIMER_CONFIG_INIT(&timerConfig, SerialInvokePerhapsLowerRTS); + + timerConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + timerAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfTimerCreate(&timerConfig, + &timerAttributes, + &pDevExt->LowerRTSTimer); + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfTimerCreate(LowerRTSTimer) failed [%#08lx]\n", status); + return status; + } + + // + // Create a DPC to complete read requests. + // + + WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCompleteWrite); + + dpcConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes); + dpcAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfDpcCreate(&dpcConfig, + &dpcAttributes, + &pDevExt->CompleteWriteDpc); + if (!NT_SUCCESS(status)) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(CompleteWriteDpc) failed [%#08lx]\n", status); + return status; + } + + + // + // Create a DPC to complete read requests. + // + + WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCompleteRead); + + dpcConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes); + dpcAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfDpcCreate(&dpcConfig, + &dpcAttributes, + &pDevExt->CompleteReadDpc); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(CompleteReadDpc) failed [%#08lx]\n", status); + return status; + } + + // + // This dpc is fired off if a comm error occurs. It will + // cancel all pending reads and writes. + // + WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCommError); + + dpcConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes); + dpcAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfDpcCreate(&dpcConfig, + &dpcAttributes, + &pDevExt->CommErrorDpc); + + + if (!NT_SUCCESS(status)) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(CommErrorDpc) failed [%#08lx]\n", status); + return status; + } + + // + // This dpc is fired off when the transmit immediate char + // character is given to the hardware. It will simply complete + // the request. + // + + WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCompleteImmediate); + + dpcConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes); + dpcAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfDpcCreate(&dpcConfig, + &dpcAttributes, + &pDevExt->CompleteImmediateDpc); + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(CompleteImmediateDpc) failed [%#08lx]\n", status); + return status; + } + + // + // This dpc is fired off if an event occurs and there was + // a request waiting on that event. A dpc routine will execute + // that completes the request. + // + WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCompleteWait); + + dpcConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes); + dpcAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfDpcCreate(&dpcConfig, + &dpcAttributes, + &pDevExt->CommWaitDpc); + if (!NT_SUCCESS(status)) { + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(CommWaitDpc) failed [%#08lx]\n", status); + return status; + } + + // + // This dpc is fired off if the xoff counter actually runs down + // to zero. + // + WDF_DPC_CONFIG_INIT(&dpcConfig, SerialCompleteXoff); + + dpcConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes); + dpcAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfDpcCreate(&dpcConfig, + &dpcAttributes, + &pDevExt->XoffCountCompleteDpc); + + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(XoffCountCompleteDpc) failed [%#08lx]\n", status); + return status; + } + + + // + // This dpc is fired off only from device level to start off + // a timer that will queue a dpc to check if the RTS line + // should be lowered when we are doing transmit toggling. + // + WDF_DPC_CONFIG_INIT(&dpcConfig, SerialStartTimerLowerRTS); + + dpcConfig.AutomaticSerialization = TRUE; + + WDF_OBJECT_ATTRIBUTES_INIT(&dpcAttributes); + dpcAttributes.ParentObject = pDevExt->WdfDevice; + + status = WdfDpcCreate(&dpcConfig, + &dpcAttributes, + &pDevExt->StartTimerLowerRTSDpc); + if (!NT_SUCCESS(status)) { + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_PNP, "WdfDpcCreate(StartTimerLowerRTSDpc) failed [%#08lx]\n", status); + return status; + } + + return status; +} + + + + +BOOLEAN +SerialInsertQueueDpc(IN WDFDPC PDpc) +/*++ + +Routine Description: + + This function must be called to queue DPC's for the serial driver. + +Arguments: + + PDpc - Pointer to the Dpc object + +Return Value: + + Kicks up return value from KeInsertQueueDpc() + +--*/ +{ + // + // If the specified DPC object is not currently in the queue, WdfDpcEnqueue + // queues the DPC and returns TRUE. + // + + return WdfDpcEnqueue(PDpc); +} + + + +BOOLEAN +SerialSetTimer(IN WDFTIMER Timer, IN LARGE_INTEGER DueTime) +/*++ + +Routine Description: + + This function must be called to set timers for the serial driver. + +Arguments: + + Timer - pointer to timer dispatcher object + + DueTime - time at which the timer should expire + + +Return Value: + + Kicks up return value from KeSetTimerEx() + +--*/ +{ + BOOLEAN result; + // + // If the timer object was already in the system timer queue, WdfTimerStart returns TRUE + // + result = WdfTimerStart(Timer, DueTime.QuadPart); + + return result; + +} + + +VOID +SerialDrainTimersAndDpcs( + IN PSERIAL_DEVICE_EXTENSION PDevExt + ) +/*++ + +Routine Description: + + This function cancels all the timers and Dpcs and waits for them + to run to completion if they are already fired. + +Arguments: + + PDevExt - Pointer to the device extension for the device that needs to + set a timer + +Return Value: + +--*/ +{ + WdfTimerStop(PDevExt->ReadRequestTotalTimer, TRUE); + + WdfTimerStop(PDevExt->ReadRequestIntervalTimer, TRUE); + + WdfTimerStop(PDevExt->WriteRequestTotalTimer, TRUE); + + WdfTimerStop(PDevExt->ImmediateTotalTimer, TRUE); + + WdfTimerStop(PDevExt->XoffCountTimer, TRUE); + + WdfTimerStop(PDevExt->LowerRTSTimer, TRUE); + + WdfDpcCancel(PDevExt->CompleteWriteDpc, TRUE); + + WdfDpcCancel(PDevExt->CompleteReadDpc, TRUE); + + WdfDpcCancel(PDevExt->CommErrorDpc, TRUE); + + WdfDpcCancel(PDevExt->CompleteImmediateDpc, TRUE); + + WdfDpcCancel(PDevExt->CommWaitDpc, TRUE); + + WdfDpcCancel(PDevExt->XoffCountCompleteDpc, TRUE); + + WdfDpcCancel(PDevExt->StartTimerLowerRTSDpc, TRUE); + + return; +} + + + +BOOLEAN +SerialCancelTimer( + IN WDFTIMER Timer, + IN PSERIAL_DEVICE_EXTENSION PDevExt + ) +/*++ + +Routine Description: + + This function must be called to cancel timers for the serial driver. + +Arguments: + + Timer - pointer to timer dispatcher object + + PDevExt - Pointer to the device extension for the device that needs to + set a timer + +Return Value: + + True if timer was cancelled + +--*/ +{ + UNREFERENCED_PARAMETER(PDevExt); + + return WdfTimerStop(Timer, FALSE); +} + +SERIAL_MEM_COMPARES +SerialMemCompare( + IN PHYSICAL_ADDRESS A, + IN ULONG SpanOfA, + IN PHYSICAL_ADDRESS B, + IN ULONG SpanOfB + ) +/*++ + +Routine Description: + + Compare two phsical address. + +Arguments: + + A - One half of the comparison. + + SpanOfA - In units of bytes, the span of A. + + B - One half of the comparison. + + SpanOfB - In units of bytes, the span of B. + + +Return Value: + + The result of the comparison. + +--*/ +{ + LARGE_INTEGER a; + LARGE_INTEGER b; + + LARGE_INTEGER lower; + ULONG lowerSpan; + LARGE_INTEGER higher; + + PAGED_CODE(); + + a = A; + b = B; + + if (a.QuadPart == b.QuadPart) { + + return AddressesAreEqual; + + } + + if (a.QuadPart > b.QuadPart) { + + higher = a; + lower = b; + lowerSpan = SpanOfB; + + } else { + + higher = b; + lower = a; + lowerSpan = SpanOfA; + + } + + if ((higher.QuadPart - lower.QuadPart) >= lowerSpan) { + + return AddressesAreDisjoint; + + } + + return AddressesOverlap; + +} + + +VOID +SerialLogError( + _In_ PDRIVER_OBJECT DriverObject, + _In_opt_ PDEVICE_OBJECT DeviceObject, + _In_ PHYSICAL_ADDRESS P1, + _In_ PHYSICAL_ADDRESS P2, + _In_ ULONG SequenceNumber, + _In_ UCHAR MajorFunctionCode, + _In_ UCHAR RetryCount, + _In_ ULONG UniqueErrorValue, + _In_ NTSTATUS FinalStatus, + _In_ NTSTATUS SpecificIOStatus, + _In_ ULONG LengthOfInsert1, + _In_reads_bytes_opt_(LengthOfInsert1) PWCHAR Insert1, + _In_ ULONG LengthOfInsert2, + _In_reads_bytes_opt_(LengthOfInsert2) PWCHAR Insert2 + ) +/*++ + +Routine Description: + + This routine allocates an error log entry, copies the supplied data + to it, and requests that it be written to the error log file. + +Arguments: + + DriverObject - A pointer to the driver object for the device. + + DeviceObject - A pointer to the device object associated with the + device that had the error, early in initialization, one may not + yet exist. + + P1,P2 - If phyical addresses for the controller ports involved + with the error are available, put them through as dump data. + + SequenceNumber - A ulong value that is unique to an WDFREQUEST over the + life of the request in this driver - 0 generally means an error not + associated with an request. + + MajorFunctionCode - If there is an error associated with the request, + this is the major function code of that request. + + RetryCount - The number of times a particular operation has been + retried. + + UniqueErrorValue - A unique long word that identifies the particular + call to this function. + + FinalStatus - The final status given to the request that was associated + with this error. If this log entry is being made during one of + the retries this value will be STATUS_SUCCESS. + + SpecificIOStatus - The IO status for a particular error. + + LengthOfInsert1 - The length in bytes (including the terminating NULL) + of the first insertion string. + + Insert1 - The first insertion string. + + LengthOfInsert2 - The length in bytes (including the terminating NULL) + of the second insertion string. NOTE, there must + be a first insertion string for their to be + a second insertion string. + + Insert2 - The second insertion string. + +Return Value: + + None. + +--*/ + +{ + PIO_ERROR_LOG_PACKET errorLogEntry; + + PVOID objectToUse; + SHORT dumpToAllocate = 0; + PUCHAR ptrToFirstInsert; + PUCHAR ptrToSecondInsert; + + PAGED_CODE(); + + if (Insert1 == NULL) { + LengthOfInsert1 = 0; + } + + if (Insert2 == NULL) { + LengthOfInsert2 = 0; + } + + + if (ARGUMENT_PRESENT(DeviceObject)) { + + objectToUse = DeviceObject; + + } else { + + objectToUse = DriverObject; + + } + + if (SerialMemCompare( + P1, + (ULONG)1, + SerialPhysicalZero, + (ULONG)1 + ) != AddressesAreEqual) { + + dumpToAllocate = (SHORT)sizeof(PHYSICAL_ADDRESS); + + } + + if (SerialMemCompare( + P2, + (ULONG)1, + SerialPhysicalZero, + (ULONG)1 + ) != AddressesAreEqual) { + + dumpToAllocate += (SHORT)sizeof(PHYSICAL_ADDRESS); + + } + + errorLogEntry = IoAllocateErrorLogEntry( + objectToUse, + (UCHAR)(sizeof(IO_ERROR_LOG_PACKET) + + dumpToAllocate + + LengthOfInsert1 + + LengthOfInsert2) + ); + + if ( errorLogEntry != NULL ) { + + errorLogEntry->ErrorCode = SpecificIOStatus; + errorLogEntry->SequenceNumber = SequenceNumber; + errorLogEntry->MajorFunctionCode = MajorFunctionCode; + errorLogEntry->RetryCount = RetryCount; + errorLogEntry->UniqueErrorValue = UniqueErrorValue; + errorLogEntry->FinalStatus = FinalStatus; + errorLogEntry->DumpDataSize = dumpToAllocate; + + if (dumpToAllocate) { + + RtlCopyMemory( + &errorLogEntry->DumpData[0], + &P1, + sizeof(PHYSICAL_ADDRESS) + ); + + if (dumpToAllocate > sizeof(PHYSICAL_ADDRESS)) { + + RtlCopyMemory( + ((PUCHAR)&errorLogEntry->DumpData[0]) + +sizeof(PHYSICAL_ADDRESS), + &P2, + sizeof(PHYSICAL_ADDRESS) + ); + + ptrToFirstInsert = + ((PUCHAR)&errorLogEntry->DumpData[0])+(2*sizeof(PHYSICAL_ADDRESS)); + + } else { + + ptrToFirstInsert = + ((PUCHAR)&errorLogEntry->DumpData[0])+sizeof(PHYSICAL_ADDRESS); + + + } + + } else { + + ptrToFirstInsert = (PUCHAR)&errorLogEntry->DumpData[0]; + + } + + ptrToSecondInsert = ptrToFirstInsert + LengthOfInsert1; + + if (LengthOfInsert1) { + + errorLogEntry->NumberOfStrings = 1; + errorLogEntry->StringOffset = (USHORT)(ptrToFirstInsert - + (PUCHAR)errorLogEntry); + RtlCopyMemory( + ptrToFirstInsert, + Insert1, + LengthOfInsert1 + ); + + if (LengthOfInsert2) { + + errorLogEntry->NumberOfStrings = 2; + RtlCopyMemory( + ptrToSecondInsert, + Insert2, + LengthOfInsert2 + ); + + } + + } + + IoWriteErrorLogEntry(errorLogEntry); + + } + +} + +VOID +SerialMarkHardwareBroken(IN PSERIAL_DEVICE_EXTENSION PDevExt) +/*++ + +Routine Description: + + Marks a UART as broken. This causes the driver stack to stop accepting + requests and eventually be removed. + +Arguments: + PDevExt - Device extension attached to PDevObj + +Return Value: + + None. + +--*/ +{ + PAGED_CODE(); + + // + // Write a log entry + // + + SerialLogError(PDevExt->DriverObject, NULL, SerialPhysicalZero, + SerialPhysicalZero, 0, 0, 0, 88, STATUS_SUCCESS, + SERIAL_HARDWARE_FAILURE, PDevExt->DeviceName.Length + + sizeof(WCHAR), PDevExt->DeviceName.Buffer, 0, NULL); + + SerialDbgPrintEx(TRACE_LEVEL_ERROR, DBG_INIT, "Device is broken. Request a restart...\n"); + WdfDeviceSetFailed(PDevExt->WdfDevice, WdfDeviceFailedAttemptRestart); +} + +NTSTATUS +SerialGetDivisorFromBaud( + IN ULONG ClockRate, + IN LONG DesiredBaud, + OUT PSHORT AppropriateDivisor + ) + +/*++ + +Routine Description: + + This routine will determine a divisor based on an unvalidated + baud rate. + +Arguments: + + ClockRate - The clock input to the controller. + + DesiredBaud - The baud rate for whose divisor we seek. + + AppropriateDivisor - Given that the DesiredBaud is valid, the + LONG pointed to by this parameter will be set to the appropriate + value. NOTE: The long is undefined if the DesiredBaud is not + supported. + +Return Value: + + This function will return STATUS_SUCCESS if the baud is supported. + If the value is not supported it will return a status such that + NT_ERROR(Status) == FALSE. + +--*/ + +{ + + NTSTATUS status = STATUS_SUCCESS; + SHORT calculatedDivisor; + ULONG denominator; + ULONG remainder; + + // + // Allow up to a 1 percent error + // + + ULONG maxRemain18 = 18432; + ULONG maxRemain30 = 30720; + ULONG maxRemain42 = 42336; + ULONG maxRemain80 = 80000; + ULONG maxRemain; + + + + // + // Reject any non-positive bauds. + // + + denominator = DesiredBaud*(ULONG)16; + + if (DesiredBaud <= 0) { + + *AppropriateDivisor = -1; + + } else if ((LONG)denominator < DesiredBaud) { + + // + // If the desired baud was so huge that it cause the denominator + // calculation to wrap, don't support it. + // + + *AppropriateDivisor = -1; + + } else { + + if (ClockRate == 1843200) { + maxRemain = maxRemain18; + } else if (ClockRate == 3072000) { + maxRemain = maxRemain30; + } else if (ClockRate == 4233600) { + maxRemain = maxRemain42; + } else { + maxRemain = maxRemain80; + } + + calculatedDivisor = (SHORT)(ClockRate / denominator); + remainder = ClockRate % denominator; + + // + // Round up. + // + + if (((remainder*2) > ClockRate) && (DesiredBaud != 110)) { + + calculatedDivisor++; + } + + + // + // Only let the remainder calculations effect us if + // the baud rate is > 9600. + // + + if (DesiredBaud >= 9600) { + + // + // If the remainder is less than the maximum remainder (wrt + // the ClockRate) or the remainder + the maximum remainder is + // greater than or equal to the ClockRate then assume that the + // baud is ok. + // + + if ((remainder >= maxRemain) && ((remainder+maxRemain) < ClockRate)) { + calculatedDivisor = -1; + } + + } + + // + // Don't support a baud that causes the denominator to + // be larger than the clock. + // + + if (denominator > ClockRate) { + + calculatedDivisor = -1; + + } + + // + // Ok, Now do some special casing so that things can actually continue + // working on all platforms. + // + + if (ClockRate == 1843200) { + + if (DesiredBaud == 56000) { + calculatedDivisor = 2; + } + + } else if (ClockRate == 3072000) { + + if (DesiredBaud == 14400) { + calculatedDivisor = 13; + } + + } else if (ClockRate == 4233600) { + + if (DesiredBaud == 9600) { + calculatedDivisor = 28; + } else if (DesiredBaud == 14400) { + calculatedDivisor = 18; + } else if (DesiredBaud == 19200) { + calculatedDivisor = 14; + } else if (DesiredBaud == 38400) { + calculatedDivisor = 7; + } else if (DesiredBaud == 56000) { + calculatedDivisor = 5; + } + + } else if (ClockRate == 8000000) { + + if (DesiredBaud == 14400) { + calculatedDivisor = 35; + } else if (DesiredBaud == 56000) { + calculatedDivisor = 9; + } + + } + + *AppropriateDivisor = calculatedDivisor; + + } + + + if (*AppropriateDivisor == -1) { + + status = STATUS_INVALID_PARAMETER; + + } + + return status; + +} + + +BOOLEAN +IsQueueEmpty( + IN WDFQUEUE Queue + ) +{ + WDF_IO_QUEUE_STATE queueStatus; + + queueStatus = WdfIoQueueGetState( Queue, NULL, NULL ); + + return (WDF_IO_QUEUE_IDLE(queueStatus)) ? TRUE : FALSE; +} + +VOID +SerialSetCancelRoutine( + IN WDFREQUEST Request, + IN PFN_WDF_REQUEST_CANCEL CancelRoutine) +{ + PREQUEST_CONTEXT reqContext = SerialGetRequestContext(Request); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "-->SerialSetCancelRoutine %p \n", Request); + + WdfRequestMarkCancelable(Request, CancelRoutine); + SERIAL_SET_REFERENCE(reqContext, SERIAL_REF_CANCEL); + reqContext->CancelRoutine = CancelRoutine; + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "<-- SerialSetCancelRoutine \n"); + + return; +} + +NTSTATUS +SerialClearCancelRoutine( + IN WDFREQUEST Request, + IN BOOLEAN ClearReference + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PREQUEST_CONTEXT reqContext = SerialGetRequestContext(Request); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "-->SerialClearCancelRoutine %p %x\n", + Request, ClearReference); + + if(SERIAL_TEST_REFERENCE(reqContext, SERIAL_REF_CANCEL)) + { + status = WdfRequestUnmarkCancelable(Request); + if (NT_SUCCESS(status)) { + + reqContext->CancelRoutine = NULL; + if(ClearReference) { + + SERIAL_CLEAR_REFERENCE( reqContext, SERIAL_REF_CANCEL ); + + } + } else { + ASSERT(status == STATUS_CANCELLED); + } + } + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "-->SerialClearCancelRoutine %p\n", Request); + + return status; +} + + +VOID +SerialCompleteRequest( + IN WDFREQUEST Request, + IN NTSTATUS Status, + IN ULONG_PTR Info + ) +{ + PREQUEST_CONTEXT reqContext; + + reqContext = SerialGetRequestContext(Request); + + ASSERT(reqContext->RefCount == 0); + + SerialDbgPrintEx(TRACE_LEVEL_VERBOSE, DBG_PNP, + "Complete Request: %p %X 0x%I64x\n", + (Request), (Status), (Info)); + + WdfRequestCompleteWithInformation((Request), (Status), (Info)); + +} + + diff --git a/tests/projects/wdk/kmdf/serial/waitmask.c b/tests/projects/wdk/kmdf/serial/waitmask.c new file mode 100644 index 000000000..c3139679e --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/waitmask.c @@ -0,0 +1,574 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + waitmask.c + +Abstract: + + This module contains the code that is very specific to get/set/wait + on event mask operations in the serial driver + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "waitmask.tmh" +#endif + +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGrabWaitFromIsr; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGiveWaitToIsr; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialFinishOldWait; + + +VOID +SerialStartMask( + IN PSERIAL_DEVICE_EXTENSION Extension + ) + +/*++ + +Routine Description: + + This routine is used to process the set mask and wait + mask ioctls. Calls to this routine are serialized by + placing irps in the list under the protection of the + cancel spin lock. + +Arguments: + + Extension - A pointer to the serial device extension. + +Return Value: + + Will return pending for everything put the first + request that we actually process. Even in that + case it will return pending unless it can complete + it right away. + + +--*/ + +{ + + + WDFREQUEST NewRequest; + PREQUEST_CONTEXT reqContext; + WDF_REQUEST_PARAMETERS params; + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "In SerialStartMask\n"); + + ASSERT(Extension->CurrentMaskRequest); + + + do { + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "STARTMASK - CurrentMaskRequest: %p\n", + Extension->CurrentMaskRequest); + + WDF_REQUEST_PARAMETERS_INIT(¶ms); + + WdfRequestGetParameters( + Extension->CurrentMaskRequest, + ¶ms + ); + + + reqContext = SerialGetRequestContext(Extension->CurrentMaskRequest); + + ASSERT((params.Parameters.DeviceIoControl.IoControlCode == + IOCTL_SERIAL_WAIT_ON_MASK) || + (params.Parameters.DeviceIoControl.IoControlCode == + IOCTL_SERIAL_SET_WAIT_MASK)); + + if (params.Parameters.DeviceIoControl.IoControlCode == + IOCTL_SERIAL_SET_WAIT_MASK) { + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "SERIAL - %p is a SETMASK request\n", + Extension->CurrentMaskRequest); + + // + // Complete the old wait if there is one. + // + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialFinishOldWait, + Extension + ); + + // + // Any current waits should be on its way to completion + // at this point. There certainly shouldn't be any + // request mask location. + // + + ASSERT(!Extension->IrpMaskLocation); + + reqContext->Status = STATUS_SUCCESS; + + // + // The following call will also cause the current + // call to be completed. + // + + SerialGetNextRequest( + &Extension->CurrentMaskRequest, + Extension->MaskQueue, + &NewRequest, + TRUE, + Extension + ); + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "Perhaps another mask request was found in " + "the queue\n" + "------- %p/%p <- values should be the same\n", + Extension->CurrentMaskRequest, NewRequest); + + + } else { + + // + // First make sure that we have a non-zero mask. + // If the app queues a wait on a zero mask it can't + // be statisfied so it makes no sense to start it. + // + + if ((!Extension->IsrWaitMask) || (Extension->CurrentWaitRequest)) { + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "WaitIrp is invalid\n" + "------- IsrWaitMask: %x\n" + "------- CurrentWaitRequest: %p\n", + Extension->IsrWaitMask, + Extension->CurrentWaitRequest); + + reqContext->Status = STATUS_INVALID_PARAMETER; + + SerialGetNextRequest(&Extension->CurrentMaskRequest, + Extension->MaskQueue, &NewRequest, TRUE, + Extension); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "Perhaps another mask request was found " + "in the queue\n" + "------- %p/%p <- values should be the same\n", + Extension->CurrentMaskRequest,NewRequest); + + } else { + + // + // Make the current mask request the current wait request and + // get a new current mask request. Note that when we get + // the new current mask request we DO NOT complete the + // old current mask request (which is now the current wait + // request. + // + // Then under the protection of the cancel spin lock + // we check to see if the current wait request needs to + // be canceled + // + + SERIAL_INIT_REFERENCE(reqContext); + + SerialSetCancelRoutine(Extension->CurrentMaskRequest, + SerialCancelWait); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "%p will become the current " + "wait request\n", + Extension->CurrentMaskRequest); + // + // There should never be a mask location when + // there isn't a current wait request. At this point + // there shouldn't be a current wait request also. + // + + ASSERT(!Extension->IrpMaskLocation); + ASSERT(!Extension->CurrentWaitRequest); + + Extension->CurrentWaitRequest = Extension->CurrentMaskRequest; + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialGiveWaitToIsr, + Extension + ); + + // + // Since it isn't really the mask request anymore, + // null out that pointer. + // + Extension->CurrentMaskRequest = NULL; + + // + // This will release the cancel spinlock for us + // + + SerialGetNextRequest(&Extension->CurrentMaskRequest, + Extension->MaskQueue, &NewRequest, + FALSE, Extension); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "Perhaps another mask request was " + "found in the queue\n" + "------- %p/%p <- values should be the " + "same\n", Extension->CurrentMaskRequest, + NewRequest); + } + + } + + } while (NewRequest); + + return; + +} + +BOOLEAN +SerialGrabWaitFromIsr( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine will check to see if the ISR still knows about + a wait request by checking to see if the IrpMaskLocation is non-null. + If it is then it will zero the Irpmasklocation (which in effect + grabs the request away from the isr). This routine is only called + buy the cancel code for the wait. + + NOTE: This is called by WdfInterruptSynchronize. + +Arguments: + + Context - A pointer to the device extension + +Return Value: + + Always FALSE. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION Extension = Context; + + PREQUEST_CONTEXT reqContext; + + UNREFERENCED_PARAMETER(Interrupt); + + reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "In SerialGrabWaitFromIsr\n"); + + if (Extension->IrpMaskLocation) { + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "The isr still owns the request %p, mask " + "location is %p\n" + "------- and system buffer is %p\n", + Extension->CurrentWaitRequest,Extension->IrpMaskLocation, + reqContext->SystemBuffer); + + // + // The isr still "owns" the request. + // + + *Extension->IrpMaskLocation = 0; + Extension->IrpMaskLocation = NULL; + + reqContext->Information = sizeof(ULONG); + + // + // Since the isr no longer references the request we need to + // decrement the reference count. + // + + SERIAL_CLEAR_REFERENCE( + reqContext, + SERIAL_REF_ISR + ); + + } + + return FALSE; +} + +BOOLEAN +SerialGiveWaitToIsr( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine simply sets a variable in the device extension + so that the isr knows that we have a wait request. + + NOTE: This is called by WdfInterruptSynchronize. + + NOTE: This routine assumes that it is called with the + cancel spinlock held. + +Arguments: + + Context - Simply a pointer to the device extension. + +Return Value: + + Always FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + + PREQUEST_CONTEXT reqContext; + + UNREFERENCED_PARAMETER(Interrupt); + + reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "In SerialGiveWaitToIsr\n"); + // + // There certainly shouldn't be a current mask location at + // this point since we have a new current wait request. + // + + ASSERT(!Extension->IrpMaskLocation); + + // + // The isr may or may not actually reference this request. It + // won't if the wait can be satisfied immediately. However, + // since it will then go through the normal completion sequence, + // we need to have an incremented reference count anyway. + // + + SERIAL_SET_REFERENCE( + reqContext, + SERIAL_REF_ISR + ); + + if (!Extension->HistoryMask) { + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "No events occured prior to the wait call" + "\n"); + + // + // Although this wait might not be for empty transmit + // queue, it doesn't hurt anything to set it to false. + // + + Extension->EmptiedTransmit = FALSE; + + // + // Record where the "completion mask" should be set. + // + + Extension->IrpMaskLocation = reqContext->SystemBuffer; + SerialDbgPrintEx( TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "The isr owns the request %p, mask location is " + "%p\n" + "------- and system buffer is %p\n", + Extension->CurrentWaitRequest,Extension->IrpMaskLocation, + reqContext->SystemBuffer); + + } else { + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "%x occurred prior to the wait - starting " + "the\n" + "------- completion code for %p\n", + Extension->HistoryMask,Extension->CurrentWaitRequest); + + *((ULONG *)reqContext->SystemBuffer) = + Extension->HistoryMask; + Extension->HistoryMask = 0; + reqContext->Information = sizeof(ULONG); + reqContext->Status = STATUS_SUCCESS; + + SerialInsertQueueDpc(Extension->CommWaitDpc); + + } + + return FALSE; +} + +BOOLEAN +SerialFinishOldWait( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine will check to see if the ISR still knows about + a wait request by checking to see if the Irpmasklocation is non-null. + If it is then it will zero the Irpmasklocation (which in effect + grabs the request away from the isr). This routine is only called + buy the cancel code for the wait. + + NOTE: This is called by WdfInterruptSynchronize. + +Arguments: + + Context - A pointer to the device extension + +Return Value: + + Always FALSE. + +--*/ + +{ + PSERIAL_DEVICE_EXTENSION Extension = Context; + + PREQUEST_CONTEXT reqContext = NULL; + PREQUEST_CONTEXT reqContextMask; + + UNREFERENCED_PARAMETER(Interrupt); + + reqContextMask = SerialGetRequestContext(Extension->CurrentMaskRequest); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "In SerialFinishOldWait\n"); + + if (Extension->IrpMaskLocation) { + + reqContext = SerialGetRequestContext(Extension->CurrentWaitRequest); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "The isr still owns the request %p, mask " + "location is %p\n" + "------- and system buffer is %p\n", + Extension->CurrentWaitRequest,Extension->IrpMaskLocation, + reqContext->SystemBuffer); + // + // The isr still "owns" the request. + // + + *Extension->IrpMaskLocation = 0; + Extension->IrpMaskLocation = NULL; + + reqContext->Information = sizeof(ULONG); + + // + // We don't decrement the reference since the completion routine + // will do that. + // + + SerialInsertQueueDpc(Extension->CommWaitDpc); + + } + + // + // Don't wipe out any historical data we are still interested in. + // + + Extension->HistoryMask &= *((ULONG *)reqContextMask->SystemBuffer); + + Extension->IsrWaitMask = *((ULONG *)reqContextMask->SystemBuffer); + SerialDbgPrintEx( TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "Set mask location of %p, in request %p, with " + "system buffer of %p\n", + Extension->IrpMaskLocation, Extension->CurrentMaskRequest, + reqContextMask->SystemBuffer); + return FALSE; +} + +VOID +SerialCancelWait( + IN WDFREQUEST Request + ) + +/*++ + +Routine Description: + + This routine is used to cancel a request that is waiting on + a comm event. + +Arguments: + + Device - Wdf handle for the device + + Request - Pointer to the WDFREQUEST for the current request + +Return Value: + + None. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension; + WDFDEVICE device = WdfIoQueueGetDevice(WdfRequestGetIoQueue(Request)); + + UNREFERENCED_PARAMETER(Request); + + Extension = SerialGetDeviceExtension(device); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "Canceling wait for request %p\n", + Extension->CurrentWaitRequest); + + SerialTryToCompleteCurrent(Extension, + SerialGrabWaitFromIsr, + STATUS_CANCELLED, + &Extension->CurrentWaitRequest, + NULL, NULL, NULL, + NULL, NULL, SERIAL_REF_CANCEL); + +} + + +VOID +SerialCompleteWait( + IN WDFDPC Dpc + ) + +{ + + PSERIAL_DEVICE_EXTENSION Extension = NULL; + + Extension = SerialGetDeviceExtension(WdfDpcGetParentObject(Dpc)); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + ">SerialCompleteWait(%p)\n", + Extension); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + "Completing wait for request %p\n", + Extension->CurrentWaitRequest); + + SerialTryToCompleteCurrent(Extension, NULL, STATUS_SUCCESS, + &Extension->CurrentWaitRequest, NULL, NULL, NULL, + NULL, NULL, SERIAL_REF_ISR); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_IOCTLS, + " + +#if defined(EVENT_TRACING) +#include "wmi.tmh" +#endif + +EVT_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiQueryPortName; +EVT_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiQueryPortCommData; +EVT_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiQueryPortHWData; +EVT_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiQueryPortPerfData; +EVT_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiQueryPortPropData; + +NTSTATUS +SerialWmiRegisterInstance( + WDFDEVICE Device, + const GUID* Guid, + ULONG MinInstanceBufferSize, + PFN_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiInstanceQueryInstance + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGESRP0, SerialWmiRegistration) +#pragma alloc_text(PAGESRP0, SerialWmiRegisterInstance) +#pragma alloc_text(PAGESRP0, EvtWmiQueryPortName) +#pragma alloc_text(PAGESRP0, EvtWmiQueryPortCommData) +#pragma alloc_text(PAGESRP0, EvtWmiQueryPortHWData) +#pragma alloc_text(PAGESRP0, EvtWmiQueryPortPerfData) +#pragma alloc_text(PAGESRP0, EvtWmiQueryPortPropData) +#endif + +NTSTATUS +SerialWmiRegisterInstance( + WDFDEVICE Device, + const GUID* Guid, + ULONG MinInstanceBufferSize, + PFN_WDF_WMI_INSTANCE_QUERY_INSTANCE EvtWmiInstanceQueryInstance + ) +{ + WDF_WMI_PROVIDER_CONFIG providerConfig; + WDF_WMI_INSTANCE_CONFIG instanceConfig; + + PAGED_CODE(); + + // + // Create and register WMI providers and instances blocks + // + WDF_WMI_PROVIDER_CONFIG_INIT(&providerConfig, Guid); + providerConfig.MinInstanceBufferSize = MinInstanceBufferSize; + + WDF_WMI_INSTANCE_CONFIG_INIT_PROVIDER_CONFIG(&instanceConfig, &providerConfig); + instanceConfig.Register = TRUE; + instanceConfig.EvtWmiInstanceQueryInstance = EvtWmiInstanceQueryInstance; + + return WdfWmiInstanceCreate(Device, + &instanceConfig, + WDF_NO_OBJECT_ATTRIBUTES, + WDF_NO_HANDLE); +} + +NTSTATUS +SerialWmiRegistration( + WDFDEVICE Device +) +/*++ +Routine Description + + Registers with WMI as a data provider for this + instance of the device + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PSERIAL_DEVICE_EXTENSION pDevExt; + + PAGED_CODE(); + + pDevExt = SerialGetDeviceExtension (Device); + + // + // Fill in wmi perf data (all zero's) + // + RtlZeroMemory(&pDevExt->WmiPerfData, sizeof(pDevExt->WmiPerfData)); + + status = SerialWmiRegisterInstance(Device, + &MSSerial_PortName_GUID, + 0, + EvtWmiQueryPortName); + if (!NT_SUCCESS(status)) { + return status; + } + + status = SerialWmiRegisterInstance(Device, + &MSSerial_CommInfo_GUID, + sizeof(SERIAL_WMI_COMM_DATA), + EvtWmiQueryPortCommData); + if (!NT_SUCCESS(status)) { + return status; + } + + status = SerialWmiRegisterInstance(Device, + &MSSerial_HardwareConfiguration_GUID, + sizeof(SERIAL_WMI_HW_DATA), + EvtWmiQueryPortHWData); + if (!NT_SUCCESS(status)) { + return status; + } + + status = SerialWmiRegisterInstance(Device, + &MSSerial_PerformanceInformation_GUID, + sizeof(SERIAL_WMI_PERF_DATA), + EvtWmiQueryPortPerfData); + if (!NT_SUCCESS(status)) { + return status; + } + + status = SerialWmiRegisterInstance(Device, + &MSSerial_CommProperties_GUID, + sizeof(SERIAL_COMMPROP) + sizeof(ULONG), + EvtWmiQueryPortPropData); + + if (!NT_SUCCESS(status)) { + return status; + } + + return status; +} + +// +// WMI Call back functions +// + +NTSTATUS +EvtWmiQueryPortName( + IN WDFWMIINSTANCE WmiInstance, + IN ULONG OutBufferSize, + IN PVOID OutBuffer, + OUT PULONG BufferUsed + ) +{ + WDFDEVICE device; + WCHAR pRegName[SYMBOLIC_NAME_LENGTH]; + UNICODE_STRING string; + USHORT nameSize = sizeof(pRegName); + NTSTATUS status; + + PAGED_CODE(); + + device = WdfWmiInstanceGetDevice(WmiInstance); + + status = SerialReadSymName(device, pRegName, &nameSize); + if (!NT_SUCCESS(status)) { + return status; + } + + RtlInitUnicodeString(&string, pRegName); + + return WDF_WMI_BUFFER_APPEND_STRING(OutBuffer, + OutBufferSize, + &string, + BufferUsed); +} + +NTSTATUS +EvtWmiQueryPortCommData( + IN WDFWMIINSTANCE WmiInstance, + IN ULONG OutBufferSize, + IN PVOID OutBuffer, + OUT PULONG BufferUsed + ) +{ + PSERIAL_DEVICE_EXTENSION pDevExt; + + UNREFERENCED_PARAMETER(OutBufferSize); + + PAGED_CODE(); + + pDevExt = SerialGetDeviceExtension (WdfWmiInstanceGetDevice(WmiInstance)); + + *BufferUsed = sizeof(SERIAL_WMI_COMM_DATA); + + if (OutBufferSize < *BufferUsed) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + *(PSERIAL_WMI_COMM_DATA)OutBuffer = pDevExt->WmiCommData; + + return STATUS_SUCCESS; +} + +NTSTATUS +EvtWmiQueryPortHWData( + IN WDFWMIINSTANCE WmiInstance, + IN ULONG OutBufferSize, + IN PVOID OutBuffer, + OUT PULONG BufferUsed + ) +{ + PSERIAL_DEVICE_EXTENSION pDevExt; + + UNREFERENCED_PARAMETER(OutBufferSize); + + PAGED_CODE(); + + pDevExt = SerialGetDeviceExtension (WdfWmiInstanceGetDevice(WmiInstance)); + + *BufferUsed = sizeof(SERIAL_WMI_HW_DATA); + + if (OutBufferSize < *BufferUsed) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + *(PSERIAL_WMI_HW_DATA)OutBuffer = pDevExt->WmiHwData; + + return STATUS_SUCCESS; +} + +NTSTATUS +EvtWmiQueryPortPerfData( + IN WDFWMIINSTANCE WmiInstance, + IN ULONG OutBufferSize, + IN PVOID OutBuffer, + OUT PULONG BufferUsed + ) +{ + PSERIAL_DEVICE_EXTENSION pDevExt; + + UNREFERENCED_PARAMETER(OutBufferSize); + + PAGED_CODE(); + + pDevExt = SerialGetDeviceExtension (WdfWmiInstanceGetDevice(WmiInstance)); + + *BufferUsed = sizeof(SERIAL_WMI_PERF_DATA); + + if (OutBufferSize < *BufferUsed) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + *(PSERIAL_WMI_PERF_DATA)OutBuffer = pDevExt->WmiPerfData; + + return STATUS_SUCCESS; +} + +NTSTATUS +EvtWmiQueryPortPropData( + IN WDFWMIINSTANCE WmiInstance, + IN ULONG OutBufferSize, + IN PVOID OutBuffer, + OUT PULONG BufferUsed + ) +{ + PSERIAL_DEVICE_EXTENSION pDevExt; + + UNREFERENCED_PARAMETER(OutBufferSize); + + PAGED_CODE(); + + pDevExt = SerialGetDeviceExtension (WdfWmiInstanceGetDevice(WmiInstance)); + + *BufferUsed = sizeof(SERIAL_COMMPROP) + sizeof(ULONG); + + if (OutBufferSize < *BufferUsed) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + SerialGetProperties( + pDevExt, + (PSERIAL_COMMPROP)OutBuffer + ); + + *((PULONG)(((PSERIAL_COMMPROP)OutBuffer)->ProvChar)) = 0; + + return STATUS_SUCCESS; +} + diff --git a/tests/projects/wdk/kmdf/serial/write.c b/tests/projects/wdk/kmdf/serial/write.c new file mode 100644 index 000000000..c67c062b4 --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/write.c @@ -0,0 +1,1195 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + write.c + +Abstract: + + This module contains the code that is very specific to write + operations in the serial driver + +Environment: + + Kernel mode + +--*/ + +#include "precomp.h" + +#if defined(EVENT_TRACING) +#include "write.tmh" +#endif + +EVT_WDF_REQUEST_CANCEL SerialCancelCurrentWrite; +EVT_WDF_REQUEST_CANCEL SerialCancelCurrentXoff; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGiveWriteToIsr; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGiveXoffToIsr; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGrabWriteFromIsr; +EVT_WDF_INTERRUPT_SYNCHRONIZE SerialGrabXoffFromIsr; + + +VOID +SerialEvtIoWrite( + IN WDFQUEUE Queue, + IN WDFREQUEST Request, + IN size_t Length + ) + +/*++ + +Routine Description: + + This is the dispatch routine for write. It validates the parameters + for the write request and if all is ok then it places the request + on the work queue. + +Arguments: + + Queue - Handle to the framework queue object that is associated + with the I/O request. + Request - Pointer to the WDFREQUEST for the current request + + Length - Length of the IO operation + The default property of the queue is to not dispatch + zero lenght read & write requests to the driver and + complete is with status success. So we will never get + a zero length request. + +Return Value: + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION extension; + NTSTATUS status; + WDFDEVICE hDevice; + WDF_REQUEST_PARAMETERS params; + PREQUEST_CONTEXT reqContext; + size_t bufLen; + + hDevice = WdfIoQueueGetDevice(Queue); + extension = SerialGetDeviceExtension(hDevice); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, + ">SerialEvtIoWrite(%p, 0x%I64x)\n", Request, Length); + + if (SerialCompleteIfError(extension, Request) != STATUS_SUCCESS) { + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "MajorFunction = params.Type; + reqContext->Length = (ULONG) Length; + + status = WdfRequestRetrieveInputBuffer (Request, Length, &reqContext->SystemBuffer, &bufLen); + + if (!NT_SUCCESS (status)) { + + SerialCompleteRequest(Request , status, 0); + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "WriteQueue, + &extension->CurrentWriteRequest, + SerialStartWrite); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "SerialStartWrite(%p)\n", Extension); + + TotalTime.QuadPart = 0; + + do { + + reqContext = SerialGetRequestContext(Extension->CurrentWriteRequest); + + // + // If there is an xoff counter then complete it. + // + + // + // We see if there is a actually an Xoff counter request. + // + // If there is, we put the write request back on the head + // of the write list. We then complete the xoff counter. + // The xoff counter completing code will actually make the + // xoff counter back into the current write request, and + // in the course of completing the xoff (which is now + // the current write) we will restart this request. + // + + if (Extension->CurrentXoffRequest) { + + reqContextXoff = + SerialGetRequestContext(Extension->CurrentXoffRequest); + + if (SERIAL_REFERENCE_COUNT(reqContextXoff)) { + + // + // The reference count is non-zero. This implies that + // the xoff request has not made it through the completion + // path yet. We will increment the reference count + // and attempt to complete it ourseleves. + // + + SERIAL_SET_REFERENCE( + reqContextXoff, + SERIAL_REF_XOFF_REF + ); + + reqContextXoff->Information = 0; + + // + // The following call will actually release the + // cancel spin lock. + // + + SerialTryToCompleteCurrent( + Extension, + SerialGrabXoffFromIsr, + STATUS_SERIAL_MORE_WRITES, + &Extension->CurrentXoffRequest, + NULL, + NULL, + Extension->XoffCountTimer, + NULL, + NULL, + SERIAL_REF_XOFF_REF + ); + + } else { + + // + // The request is well on its way to being finished. + // We can let the regular completion code do the + // work. Just release the spin lock. + // + + } + + } + + UseATimer = FALSE; + + // + // Calculate the timeout value needed for the + // request. Note that the values stored in the + // timeout record are in milliseconds. Note that + // if the timeout values are zero then we won't start + // the timer. + // + + Timeouts = Extension->Timeouts; + + if (Timeouts.WriteTotalTimeoutConstant || + Timeouts.WriteTotalTimeoutMultiplier) { + + UseATimer = TRUE; + + // + // We have some timer values to calculate. + // + // Take care, we might have an xoff counter masquerading + // as a write. + // + + TotalTime.QuadPart = + ((LONGLONG)((UInt32x32To64( + (reqContext->MajorFunction == IRP_MJ_WRITE)? + (reqContext->Length) : (1), + Timeouts.WriteTotalTimeoutMultiplier + ) + + Timeouts.WriteTotalTimeoutConstant))) + * -10000; + + } + + // + // The request may be going to the isr shortly. Now + // is a good time to initialize its reference counts. + // + + SERIAL_INIT_REFERENCE(reqContext); + + // + // We give the request to to the isr to write out. + // We set a cancel routine that knows how to + // grab the current write away from the isr. + // + SerialSetCancelRoutine(Extension->CurrentWriteRequest, + SerialCancelCurrentWrite); + + if (UseATimer) { + BOOLEAN result; + + result = SerialSetTimer( + Extension->WriteRequestTotalTimer, + TotalTime + ); + if(result == FALSE) { + // + // This timer now has a reference to the request. + // + + SERIAL_SET_REFERENCE( reqContext, SERIAL_REF_TOTAL_TIMER ); + } + } + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialGiveWriteToIsr, + Extension + ); + + } WHILE (FALSE); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "SerialGetNextWrite\n"); + + + do { + + reqContext = SerialGetRequestContext(*CurrentOpRequest); + + // + // We could be completing a flush. + // + + if (reqContext->MajorFunction == IRP_MJ_WRITE) { + + ASSERT(Extension->TotalCharsQueued >= reqContext->Length); + + Extension->TotalCharsQueued -= reqContext->Length; + + } else if (reqContext->MajorFunction == IRP_MJ_DEVICE_CONTROL) { + + WDFREQUEST request = *CurrentOpRequest; + PSERIAL_XOFF_COUNTER Xc; + + Xc = reqContext->SystemBuffer; + + // + // We should never have a xoff counter when we + // get to this point. + // + + ASSERT(!Extension->CurrentXoffRequest); + + // + // This could only be a xoff counter masquerading as + // a write request. + // + + Extension->TotalCharsQueued--; + + // + // Check to see of the xoff request has been set with success. + // This means that the write completed normally. If that + // is the case, and it hasn't been set to cancel in the + // meanwhile, then go on and make it the CurrentXoffRequest. + // + + if (reqContext->Status != STATUS_SUCCESS || reqContext->Cancelled) { + + // TODO: I see Xoff request getting abandoned due to loss of + // Total timer - SERIAL_REF_TOTAL_TIMER + // + // Oh well, we can just finish it off. + // + NOTHING; + + } else { + + SerialSetCancelRoutine(request, SerialCancelCurrentXoff); + + // + // We don't want to complete the current request now. This + // will now get completed by the Xoff counter code. + // + + CompleteCurrent = FALSE; + + // + // Give the counter to the isr. + // + + Extension->CurrentXoffRequest = request; + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialGiveXoffToIsr, + Extension + ); + + // + // Start the timer for the counter and increment + // the reference count since the timer has a + // reference to the request. + // + + if (Xc->Timeout) { + + LARGE_INTEGER delta; + BOOLEAN result; + + delta.QuadPart = -((LONGLONG)UInt32x32To64( + 1000, + Xc->Timeout + )); + + result = SerialSetTimer( + Extension->XoffCountTimer, + delta + + ); + if(result == FALSE) { + SERIAL_SET_REFERENCE( + reqContext, + SERIAL_REF_TOTAL_TIMER + ); + } + } + + } + + + } + + // + // Note that the following call will (probably) also cause + // the current request to be completed. + // + + SerialGetNextRequest( + CurrentOpRequest, + QueueToProcess, + NewRequest, + CompleteCurrent, + Extension + ); + + if (!*NewRequest) { + + + WdfInterruptSynchronize( + Extension->WdfInterrupt, + SerialProcessEmptyTransmit, + Extension + ); + + break; + + } else if (SerialGetRequestContext(*NewRequest)->MajorFunction + == IRP_MJ_FLUSH_BUFFERS) { + + // + // If we encounter a flush request we just want to get + // the next request and complete the flush. + // + // Note that if NewRequest is non-null then it is also + // equal to CurrentWriteRequest. + // + + + ASSERT((*NewRequest) == (*CurrentOpRequest)); + SerialGetRequestContext(*NewRequest)->Status = STATUS_SUCCESS; + + } else { + + break; + + } + + } WHILE (TRUE); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "SerialCompleteWrite(%p)\n", + Extension); + + + SerialTryToCompleteCurrent(Extension, NULL, STATUS_SUCCESS, + &Extension->CurrentWriteRequest, + Extension->WriteQueue, NULL, + Extension->WriteRequestTotalTimer, + SerialStartWrite, SerialGetNextWrite, + SERIAL_REF_ISR); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "IsrWaitMask && (Extension->IsrWaitMask & SERIAL_EV_TXEMPTY) && + Extension->EmptiedTransmit && (!Extension->TransmitImmediate) && + (!Extension->CurrentWriteRequest) && IsQueueEmpty(Extension->WriteQueue)) { + + Extension->HistoryMask |= SERIAL_EV_TXEMPTY; + if (Extension->IrpMaskLocation) { + + *Extension->IrpMaskLocation = Extension->HistoryMask; + Extension->IrpMaskLocation = NULL; + Extension->HistoryMask = 0; + + SerialGetRequestContext(Extension->CurrentWaitRequest)->Information = sizeof(ULONG); + SerialInsertQueueDpc( + Extension->CommWaitDpc + ); + + } + + Extension->CountOfTryingToLowerRTS++; + SerialPerhapsLowerRTS(Extension->WdfInterrupt, Extension); + + } + + return FALSE; + +} + + +BOOLEAN +SerialGiveWriteToIsr( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + Try to start off the write by slipping it in behind + a transmit immediate char, or if that isn't available + and the transmit holding register is empty, "tickle" + the UART into interrupting with a transmit buffer + empty. + + NOTE: This routine is called by WdfInterruptSynchronize. + + NOTE: This routine assumes that it is called with the + cancel spin lock held. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + + // + // The current stack location. This contains all of the + // information we need to process this particular request. + // + PREQUEST_CONTEXT reqContext; + + UNREFERENCED_PARAMETER(Interrupt); + + reqContext = SerialGetRequestContext(Extension->CurrentWriteRequest); + + // + // We might have a xoff counter request masquerading as a + // write. The length of these requests will always be one + // and we can get a pointer to the actual character from + // the data supplied by the user. + // + + if (reqContext->MajorFunction == IRP_MJ_WRITE) { + + Extension->WriteLength = reqContext->Length; + Extension->WriteCurrentChar = reqContext->SystemBuffer; + + } else { + + Extension->WriteLength = 1; + Extension->WriteCurrentChar = + ((PUCHAR)reqContext->SystemBuffer) + + FIELD_OFFSET( + SERIAL_XOFF_COUNTER, + XoffChar + ); + + } + + // + // The isr now has a reference to the request. + // + + SERIAL_SET_REFERENCE( + reqContext, + SERIAL_REF_ISR + ); + + // + // Check first to see if an immediate char is transmitting. + // If it is then we'll just slip in behind it when its + // done. + // + + if (!Extension->TransmitImmediate) { + + // + // If there is no immediate char transmitting then we + // will "re-enable" the transmit holding register empty + // interrupt. The 8250 family of devices will always + // signal a transmit holding register empty interrupt + // *ANY* time this bit is set to one. By doing things + // this way we can simply use the normal interrupt code + // to start off this write. + // + // We've been keeping track of whether the transmit holding + // register is empty so it we only need to do this + // if the register is empty. + // + + if (Extension->HoldingEmpty) { + + DISABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + ENABLE_ALL_INTERRUPTS(Extension, Extension->Controller); + + } + + } + + // + // The rts line may already be up from previous writes, + // however, it won't take much additional time to turn + // on the RTS line if we are doing transmit toggling. + // + + if ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) == + SERIAL_TRANSMIT_TOGGLE) { + + SerialSetRTS(Extension->WdfInterrupt, Extension); + + } + + return FALSE; + +} + + +VOID +SerialCancelCurrentWrite( + IN WDFREQUEST Request + ) + +/*++ + +Routine Description: + + This routine is used to cancel the current write. + +Arguments: + + Device - Wdf handle for the device + + Request - Pointer to the WDFREQUEST to be canceled. + +Return Value: + + None. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension; + WDFDEVICE device = WdfIoQueueGetDevice(WdfRequestGetIoQueue(Request)); + + UNREFERENCED_PARAMETER(Request); + + Extension = SerialGetDeviceExtension(device); + + SerialTryToCompleteCurrent( + Extension, + SerialGrabWriteFromIsr, + STATUS_CANCELLED, + &Extension->CurrentWriteRequest, + Extension->WriteQueue, + NULL, + Extension->WriteRequestTotalTimer, + SerialStartWrite, + SerialGetNextWrite, + SERIAL_REF_CANCEL + ); + +} + + +VOID +SerialWriteTimeout( + IN WDFTIMER Timer + ) + +/*++ + +Routine Description: + + This routine will try to timeout the current write. + +Arguments: + +Return Value: + + None. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = NULL; + + Extension = SerialGetDeviceExtension(WdfTimerGetParentObject(Timer)); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, ">SerialWriteTimeout(%p)\n", + Extension); + + SerialTryToCompleteCurrent(Extension, SerialGrabWriteFromIsr, + STATUS_TIMEOUT, &Extension->CurrentWriteRequest, + Extension->WriteQueue, NULL, + Extension->WriteRequestTotalTimer, + SerialStartWrite, SerialGetNextWrite, + SERIAL_REF_TOTAL_TIMER); + + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "CurrentWriteRequest); + + // + // Check if the write length is non-zero. If it is non-zero + // then the ISR still owns the request. We calculate the the number + // of characters written and update the information field of the + // request with the characters written. We then clear the write length + // the isr sees. + // + + if (Extension->WriteLength) { + + // + // We could have an xoff counter masquerading as a + // write request. If so, don't update the write length. + // + + if (reqContext->MajorFunction == IRP_MJ_WRITE) { + + reqContext->Information = reqContext->Length -Extension->WriteLength; + + } else { + + reqContext->Information = 0; + + } + + // + // Since the isr no longer references this request, we can + // decrement it's reference count. + // + + SERIAL_CLEAR_REFERENCE( + reqContext, + SERIAL_REF_ISR + ); + + Extension->WriteLength = 0; + + } + + return FALSE; + +} + + +BOOLEAN +SerialGrabXoffFromIsr( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + This routine is used to grab an xoff counter request from the + isr when it is no longer masquerading as a write request. This + routine is called by the cancel and timeout code for the + xoff counter ioctl. + + + NOTE: This routine is being called from WdfInterruptSynchronize. + + NOTE: This routine assumes that the cancel spin lock is held + when this routine is called. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + Always false. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + + PREQUEST_CONTEXT reqContext; + + UNREFERENCED_PARAMETER(Interrupt); + + reqContext = SerialGetRequestContext(Extension->CurrentXoffRequest); + + if (Extension->CountSinceXoff) { + + // + // This is only non-zero when there actually is a Xoff ioctl + // counting down. + // + + Extension->CountSinceXoff = 0; + + // + // We decrement the count since the isr no longer owns + // the request. + // + + SERIAL_CLEAR_REFERENCE( + reqContext, + SERIAL_REF_ISR + ); + + } + + return FALSE; + +} + + +VOID +SerialCompleteXoff( + IN WDFDPC Dpc + ) + +/*++ + +Routine Description: + + This routine is merely used to truely complete an xoff counter request. It + assumes that the status and the information fields of the request are + already correctly filled in. + +Arguments: + + Dpc - Not Used. + + DeferredContext - Really points to the device extension. + + SystemContext1 - Not Used. + + SystemContext2 - Not Used. + +Return Value: + + None. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = NULL; + + Extension = SerialGetDeviceExtension(WdfDpcGetParentObject(Dpc)); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, ">SerialCompleteXoff(%p)\n", + Extension); + + + SerialTryToCompleteCurrent(Extension, NULL, STATUS_SUCCESS, + &Extension->CurrentXoffRequest, NULL, NULL, + Extension->XoffCountTimer, NULL, NULL, + SERIAL_REF_ISR); + + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "SerialTimeoutXoff(%p)\n", Extension); + + SerialTryToCompleteCurrent(Extension, SerialGrabXoffFromIsr, + STATUS_SERIAL_COUNTER_TIMEOUT, + &Extension->CurrentXoffRequest, NULL, NULL, NULL, + NULL, NULL, SERIAL_REF_TOTAL_TIMER); + + SerialDbgPrintEx(TRACE_LEVEL_INFORMATION, DBG_WRITE, "CurrentXoffRequest, + NULL, + NULL, + Extension->XoffCountTimer, + NULL, + NULL, + SERIAL_REF_CANCEL + ); + +} + + +BOOLEAN +SerialGiveXoffToIsr( + IN WDFINTERRUPT Interrupt, + IN PVOID Context + ) + +/*++ + +Routine Description: + + + This routine starts off the xoff counter. It merely + has to set the xoff count and increment the reference + count to denote that the isr has a reference to the request. + + NOTE: This routine is called by WdfInterruptSynchronize. + + NOTE: This routine assumes that it is called with the + cancel spin lock held. + +Arguments: + + Context - Really a pointer to the device extension. + +Return Value: + + This routine always returns FALSE. + +--*/ + +{ + + PSERIAL_DEVICE_EXTENSION Extension = Context; + PREQUEST_CONTEXT reqContext; + PSERIAL_XOFF_COUNTER Xc = NULL; + + UNREFERENCED_PARAMETER(Interrupt); + + reqContext = SerialGetRequestContext(Extension->CurrentXoffRequest); + Xc = reqContext->SystemBuffer; + + // + // The current stack location. This contains all of the + // information we need to process this particular request. + // + + ASSERT(Extension->CurrentXoffRequest); + Extension->CountSinceXoff = Xc->Counter; + + // + // The isr now has a reference to the request. + // + + SERIAL_SET_REFERENCE( + reqContext, + SERIAL_REF_ISR + ); + + return FALSE; + +} + + diff --git a/tests/projects/wdk/kmdf/serial/xmake.lua b/tests/projects/wdk/kmdf/serial/xmake.lua new file mode 100644 index 000000000..49bf2215e --- /dev/null +++ b/tests/projects/wdk/kmdf/serial/xmake.lua @@ -0,0 +1,23 @@ + +-- add modes: debug and release +add_rules("mode.debug", "mode.release") + +-- add include directories +add_includedirs(".") + +-- add target +target("serial") + + -- add rules + add_rules("wdk.kmdf.driver") + + -- add flags for rule: wdk.tracewpp + add_values("wdk.tracewpp.flags", "-func:SerialDbgPrintEx(LEVEL,FLAGS,MSG,...)") + + -- add header file name for rule: wdk.mc + add_values("wdk.mc.header", "serlog.h") + + -- add files + add_files("*.c", {rule = "wdk.tracewpp"}) + add_files("*.mc", "*.rc", "*.inx") + diff --git a/xmake/rules/wdk/inf/xmake.lua b/xmake/rules/wdk/inf/xmake.lua index 3cec2660b..d612e51ea 100644 --- a/xmake/rules/wdk/inf/xmake.lua +++ b/xmake/rules/wdk/inf/xmake.lua @@ -22,14 +22,14 @@ -- @file xmake.lua -- --- define rule: *.inf -rule("wdk.inf") +-- define rule: *.mc +rule("wdk.mc") -- add rule: wdk environment add_deps("wdk.env") -- set extensions - set_extensions(".inf", ".inx") + set_extensions(".mc") -- on load on_load(function (target) @@ -40,50 +40,81 @@ rule("wdk.inf") -- get arch local arch = assert(config.arch(), "arch not found!") - -- get stampinf - local stampinf = path.join(target:data("wdk").bindir, arch, is_host("windows") and "stampinf.exe" or "stampinf") - assert(stampinf and os.isexec(stampinf), "stampinf not found!") + -- get mc + local mc = path.join(target:data("wdk").bindir, arch, is_host("windows") and "mc.exe" or "mc") + assert(mc and os.isexec(mc), "mc not found!") - -- save uic - target:data_set("wdk.stampinf", stampinf) + -- save mc + target:data_set("wdk.mc", mc) + + -- save output directory + target:data_set("wdk.mc.outputdir", path.join(config.buildir(), ".wdk", "mc", config.get("mode") or "generic", config.get("arch") or os.arch(), target:name())) end) - -- on build file - on_build_file(function (target, sourcefile, opt) + -- before build file + before_build_file(function (target, sourcefile, opt) -- imports import("core.base.option") import("core.project.depend") - -- the target file - local targetfile = path.join(target:targetdir(), path.basename(sourcefile) .. ".inf") + -- get mc + local mc = target:data("wdk.mc") + + -- get output directory + local outputdir = target:data("wdk.mc.outputdir") - -- add clean files - target:data_add("wdk.cleanfiles", targetfile) + -- init args + local args = {} + local flags = target:values("wdk.mc.flags") + if flags then + table.join2(args, flags) + end + table.insert(args, "-h") + table.insert(args, outputdir) + table.insert(args, "-r") + table.insert(args, outputdir) + + -- add header file + local header = target:values("wdk.mc.header") + local headerfile = header and path.join(outputdir, header) or nil + if headerfile then + table.insert(args, "-z") + table.insert(args, path.basename(headerfile)) + target:data_add("wdk.cleanfiles", headerfile) + else + headerfile = path.join(outputdir, path.basename(sourcefile) .. ".h") + end + + -- add source file + table.insert(args, sourcefile) + + -- add includedirs + target:add("includedirs", outputdir) -- need build this object? - local dependfile = target:dependfile(targetfile) + local dependfile = target:dependfile(headerfile) local dependinfo = option.get("rebuild") and {} or (depend.load(dependfile) or {}) - if not depend.is_changed(dependinfo, {lastmtime = os.mtime(targetfile)}) then + if not depend.is_changed(dependinfo, {lastmtime = os.mtime(headerfile), values = args}) then return end -- trace progress info if option.get("verbose") then - cprint("${green}[%02d%%]:${dim} compiling.wdk.inf %s", opt.progress, sourcefile) + cprint("${green}[%02d%%]:${dim} compiling.wdk.mc %s", opt.progress, sourcefile) else - cprint("${green}[%02d%%]:${clear} compiling.wdk.inf %s", opt.progress, sourcefile) + cprint("${green}[%02d%%]:${clear} compiling.wdk.mc %s", opt.progress, sourcefile) end - -- get stampinf - local stampinf = target:data("wdk.stampinf") - - -- update the timestamp - os.cp(sourcefile, targetfile) - os.vrunv(stampinf, {"-d", "*", "-a", is_arch("x64") and "arm64" or "x86", "-v", "*", "-f", targetfile}, {wildcards = false}) + -- do message compile + if not os.isdir(outputdir) then + os.mkdir(outputdir) + end + os.vrunv(mc, args) -- update files and values to the dependent file - dependinfo.files = {sourcefile} + dependinfo.files = {sourcefile} + dependinfo.values = args depend.save(dependinfo, dependfile) end) diff --git a/xmake/rules/wdk/mc/xmake.lua b/xmake/rules/wdk/mc/xmake.lua new file mode 100644 index 000000000..3765bf151 --- /dev/null +++ b/xmake/rules/wdk/mc/xmake.lua @@ -0,0 +1,120 @@ +--!A cross-platform build utility based on Lua +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +-- Copyright (C) 2015 - 2018, TBOOX Open Source Group. +-- +-- @author ruki +-- @file xmake.lua +-- + +-- define rule: *.mc +rule("wdk.mc") + + -- add rule: wdk environment + add_deps("wdk.env") + + -- set extensions + set_extensions(".mc") + + -- on load + on_load(function (target) + + -- imports + import("core.project.config") + + -- get arch + local arch = assert(config.arch(), "arch not found!") + + -- get mc + local mc = path.join(target:data("wdk").bindir, arch, is_host("windows") and "mc.exe" or "mc") + assert(mc and os.isexec(mc), "mc not found!") + + -- save mc + target:data_set("wdk.mc", mc) + + -- save output directory + target:data_set("wdk.mc.outputdir", path.join(config.buildir(), ".wdk", "mc", config.get("mode") or "generic", config.get("arch") or os.arch(), target:name())) + end) + + -- before build file + before_build_file(function (target, sourcefile, opt) + + -- imports + import("core.base.option") + import("core.project.depend") + + -- get mc + local mc = target:data("wdk.mc") + + -- get output directory + local outputdir = target:data("wdk.mc.outputdir") + + -- init args + local args = {} + local flags = target:values("wdk.mc.flags") + if flags then + table.join2(args, flags) + end + table.insert(args, "-h") + table.insert(args, outputdir) + table.insert(args, "-r") + table.insert(args, outputdir) + table.insert(args, sourcefile) + + -- add includedirs + target:add("includedirs", outputdir) + + -- add header file + local header = target:values("wdk.mc.header") + local headerfile = header and path.join(outputdir, header) or nil + if headerfile then + table.insert(args, "-z") + table.insert(args, path.basename(headerfile)) + table.insert(args, "-e") + table.insert(args, path.extension(headerfile)) + target:data_add("wdk.cleanfiles", headerfile) + else + headerfile = path.join(outputdir, path.basename(sourcefile) .. ".h") + end + + -- need build this object? + local dependfile = target:dependfile(headerfile) + local dependinfo = option.get("rebuild") and {} or (depend.load(dependfile) or {}) + if not depend.is_changed(dependinfo, {lastmtime = os.mtime(headerfile), values = args}) then + return + end + + -- trace progress info + if option.get("verbose") then + cprint("${green}[%02d%%]:${dim} compiling.wdk.mc %s", opt.progress, sourcefile) + else + cprint("${green}[%02d%%]:${clear} compiling.wdk.mc %s", opt.progress, sourcefile) + end + + -- do message compile + if not os.isdir(outputdir) then + os.mkdir(outputdir) + end + os.vrunv(mc, args) + + -- update files and values to the dependent file + dependinfo.files = {sourcefile} + dependinfo.values = args + depend.save(dependinfo, dependfile) + end) + diff --git a/xmake/rules/wdk/xmake.lua b/xmake/rules/wdk/xmake.lua index 402cd1c64..931803f44 100644 --- a/xmake/rules/wdk/xmake.lua +++ b/xmake/rules/wdk/xmake.lua @@ -26,7 +26,7 @@ rule("wdk.umdf.driver") -- add rules - add_deps("wdk.inf", "wdk.man") + add_deps("wdk.inf", "wdk.man", "wdk.mc") -- on load on_load(function (target) @@ -37,7 +37,7 @@ rule("wdk.umdf.driver") rule("wdk.umdf.binary") -- add rules - add_deps("wdk.inf", "wdk.man") + add_deps("wdk.inf", "wdk.man", "wdk.mc") -- on load on_load(function (target) @@ -48,7 +48,7 @@ rule("wdk.umdf.binary") rule("wdk.kmdf.driver") -- add rules - add_deps("wdk.inf", "wdk.man") + add_deps("wdk.inf", "wdk.man", "wdk.mc") -- on load on_load(function (target) @@ -59,7 +59,7 @@ rule("wdk.kmdf.driver") rule("wdk.kmdf.binary") -- add rules - add_deps("wdk.inf", "wdk.man") + add_deps("wdk.inf", "wdk.man", "wdk.mc") -- on load on_load(function (target) -- cgit v1.3.1