|
|
/*++
Copyright (c) 1991, 1992, 1993 - 1997 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.
Author:
Anthony V. Ercolano 26-Sep-1991
Environment:
Kernel mode
--*/
#include "precomp.h"
BOOLEAN SerialMarkOpen( IN PVOID Context );
BOOLEAN SerialCheckOpen( IN PVOID Context );
BOOLEAN SerialNullSynch( IN PVOID Context );
NTSTATUS SerialCreateOpen( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp );
#ifdef ALLOC_PRAGMA
//
// Paged for open and PnP transactions
//
#pragma alloc_text(PAGESER,SerialGetCharTime)
#pragma alloc_text(PAGESER,SerialCleanup)
#pragma alloc_text(PAGESER,SerialClose)
#pragma alloc_text(PAGESER, SerialCheckOpen)
#pragma alloc_text(PAGESER, SerialMarkOpen)
//
// Always paged
//
#pragma alloc_text(PAGESRP0,SerialCreateOpen)
#pragma alloc_text(PAGESRP0, SerialDrainUART)
#endif // ALLOC_PRAGMA
typedef struct _SERIAL_CHECK_OPEN { PSERIAL_DEVICE_EXTENSION Extension; NTSTATUS *StatusOfOpen; } SERIAL_CHECK_OPEN,*PSERIAL_CHECK_OPEN;
//
// Just a bogus little routine to make sure that we
// can synch with the ISR.
//
BOOLEAN SerialNullSynch( IN PVOID Context ) {
UNREFERENCED_PARAMETER(Context); return FALSE; }
NTSTATUS SerialCreateOpen( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
/*++
Routine Description:
We connect up to the interrupt for the create/open and initialize the structures needed to maintain an open for a device.
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
--*/
{
PSERIAL_DEVICE_EXTENSION extension = DeviceObject->DeviceExtension; SERIAL_CHECK_OPEN checkOpen; NTSTATUS localStatus;
PAGED_CODE();
if (extension->PNPState != SERIAL_PNP_STARTED) { Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_INSUFFICIENT_RESOURCES; }
//
// Lock out changes to PnP state until we have our open state decided
//
ExAcquireFastMutex(&extension->OpenMutex);
if ((localStatus = SerialIRPPrologue(Irp, extension)) != STATUS_SUCCESS) { ExReleaseFastMutex(&extension->OpenMutex); SerialCompleteRequest(extension, Irp, IO_NO_INCREMENT); return localStatus; }
if (InterlockedIncrement(&extension->OpenCount) != 1) { ExReleaseFastMutex(&extension->OpenMutex); InterlockedDecrement(&extension->OpenCount); Irp->IoStatus.Status = STATUS_ACCESS_DENIED; SerialCompleteRequest(extension, Irp, IO_NO_INCREMENT); return STATUS_ACCESS_DENIED; }
SerialDump( SERIRPPATH, ("SERIAL: Dispatch entry for: %x\n",Irp) ); SerialDump( SERDIAG3, ("SERIAL: In SerialCreateOpen\n") );
//
// Before we do anything, let's make sure they aren't trying
// to create a directory. what's a driver to do!?
//
if (IoGetCurrentIrpStackLocation(Irp)->Parameters.Create.Options & FILE_DIRECTORY_FILE) { ExReleaseFastMutex(&extension->OpenMutex);
Irp->IoStatus.Status = STATUS_NOT_A_DIRECTORY; Irp->IoStatus.Information = 0;
SerialDump( SERIRPPATH, ("SERIAL: Complete Irp: %x\n",Irp) );
InterlockedDecrement(&extension->OpenCount); SerialCompleteRequest(extension, Irp, IO_NO_INCREMENT);
return STATUS_NOT_A_DIRECTORY;
}
//
// 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 = ExAllocatePool( NonPagedPool, extension->BufferSize );
if (extension->InterruptReadBuffer) {
break;
}
}
case MmMediumSystem: {
extension->BufferSize = 1024; extension->InterruptReadBuffer = ExAllocatePool( NonPagedPool, extension->BufferSize );
if (extension->InterruptReadBuffer) {
break;
}
}
case MmSmallSystem: {
extension->BufferSize = 128; extension->InterruptReadBuffer = ExAllocatePool( NonPagedPool, extension->BufferSize );
}
}
if (!extension->InterruptReadBuffer) { ExReleaseFastMutex(&extension->OpenMutex);
extension->BufferSize = 0; Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; Irp->IoStatus.Information = 0;
SerialDump( SERIRPPATH, ("SERIAL: Complete Irp: %x\n",Irp) );
InterlockedDecrement(&extension->OpenCount); SerialCompleteRequest(extension, Irp, IO_NO_INCREMENT);
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Ok, it looks like we really are going to open. Lock down the
// driver.
//
SerialLockPagableSectionByHandle(SerialGlobals.PAGESER_Handle);
//
// Power up the stack
//
(void)SerialGotoPowerState(DeviceObject, extension, PowerDeviceD0);
//
// Not currently waiting for wake up
//
extension->SendWaitWake = 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.
//
KeSynchronizeExecution( extension->Interrupt, SerialClearStats, extension ); #endif
//
// The escape char replacement must be reset upon every open.
//
extension->EscapeChar = 0;
if (!extension->PermitShare) {
if (!extension->InterruptShareable) {
checkOpen.Extension = extension; checkOpen.StatusOfOpen = &Irp->IoStatus.Status;
KeSynchronizeExecution( extension->Interrupt, SerialCheckOpen, &checkOpen );
} else {
KeSynchronizeExecution( extension->Interrupt, SerialMarkOpen, extension );
Irp->IoStatus.Status = STATUS_SUCCESS;
}
} else {
//
// Synchronize with the ISR and let it know that the device
// has been successfully opened.
//
KeSynchronizeExecution( extension->Interrupt, SerialMarkOpen, extension );
Irp->IoStatus.Status = STATUS_SUCCESS;
}
//
// We have been marked open, so now the PnP state can change
//
ExReleaseFastMutex(&extension->OpenMutex);
localStatus = Irp->IoStatus.Status; Irp->IoStatus.Information=0L;
SerialDump( SERIRPPATH, ("SERIAL: Complete Irp: %x\n",Irp) );
if (!NT_SUCCESS(localStatus)) { if (extension->InterruptReadBuffer != NULL) { ExFreePool(extension->InterruptReadBuffer); extension->InterruptReadBuffer = NULL; }
InterlockedDecrement(&extension->OpenCount); }
SerialCompleteRequest(extension, Irp, IO_NO_INCREMENT);
return localStatus;
}
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->Controller) & (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) != (SERIAL_LSR_THRE | SERIAL_LSR_TEMT)) {
KeDelayExecutionThread(KernelMode, FALSE, PDrainTime); } }
NTSTATUS SerialClose( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
/*++
Routine Description:
We simply disconnect the interrupt for now.
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
--*/
{
//
// 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;
//
// Just what it says. This is the serial specific device
// extension of the device object create for the serial driver.
//
PSERIAL_DEVICE_EXTENSION extension = DeviceObject->DeviceExtension;
NTSTATUS status;
//
// Number of opens still active
//
LONG openCount;
//
// Number of DPC's still pending
//
ULONG pendingDPCs;
ULONG flushCount;
//
// Grab a mutex
//
ExAcquireFastMutex(&extension->CloseMutex);
//
// We succeed a close on a removing device
//
if ((status = SerialIRPPrologue(Irp, extension)) != STATUS_SUCCESS) { SerialDump(SERERRORS, ("SERIAL: Close prologue failed for: %x\n",Irp)); if (status == STATUS_DELETE_PENDING) { extension->BufferSize = 0; ExFreePool(extension->InterruptReadBuffer); extension->InterruptReadBuffer = NULL; status = Irp->IoStatus.Status = STATUS_SUCCESS; }
SerialCompleteRequest(extension, Irp, IO_NO_INCREMENT); openCount = InterlockedDecrement(&extension->OpenCount); ASSERT(openCount == 0); ExReleaseFastMutex(&extension->CloseMutex); return status; }
ASSERT(extension->OpenCount == 1);
if (extension->OpenCount != 1) { SerialDump(SERERRORS, ("SERIAL: Close open count bad for: 0x%x\n",Irp)); SerialDump(SERERRORS, ("------: Count: %x Addr: 0x%x\n", extension->OpenCount, &extension->OpenCount)); ExReleaseFastMutex(&extension->CloseMutex); Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST; SerialCompleteRequest(extension, Irp, IO_NO_INCREMENT); return STATUS_INVALID_DEVICE_REQUEST; }
SerialDump( SERIRPPATH, ("SERIAL: Dispatch entry for: %x\n",Irp) ); SerialDump( SERDIAG3, ("SERIAL: In SerialClose\n") );
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.
//
extension->DeviceIsOpened = FALSE;
//
// Synchronize with the isr to turn off break if it
// is already on.
//
KeSynchronizeExecution( extension->Interrupt, SerialTurnOffBreak, extension );
//
// Wait a reasonable amount of time (20 * fifodepth) until all characters
// have been emptied out of the hardware.
//
#if defined(NEC_98)
//
// all characters not have been emptied out of the hardware.
//
#else
for (flushCount = (20 * 16); flushCount != 0; flushCount--) { if ((READ_LINE_STATUS(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); } #endif //defined(NEC_98)
//
// Synchronize with the ISR to let it know that interrupts are
// no longer important.
//
KeSynchronizeExecution( extension->Interrupt, 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->Controller) & SERIAL_LSR_THRE)) {
KeDelayExecutionThread( KernelMode, FALSE, &charTime );
}
WRITE_TRANSMIT_HOLDING( 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->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 );
SerialClrDTR(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 {
KeDelayExecutionThread( KernelMode, FALSE, &charTime );
} while (extension->CountOfTryingToLowerRTS);
KeSynchronizeExecution( extension->Interrupt, SerialNullSynch, NULL );
//
// 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.
//
}
SerialClrRTS(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;
//
// 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;
//
// Stop waiting for wakeup
//
extension->SendWaitWake = FALSE;
if (extension->PendingWakeIrp != NULL) { IoCancelIrp(extension->PendingWakeIrp); }
//
// Power down our device stack
//
(void)SerialGotoPowerState(DeviceObject, extension, PowerDeviceD3);
Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information=0L;
SerialDump( SERIRPPATH, ("SERIAL: Complete Irp: %x\n",Irp) ); SerialCompleteRequest(extension, Irp, IO_NO_INCREMENT);
//
// Unlock the pages. If this is the last reference to the section
// then the driver code will be flushed out.
//
//
// First, we have to let the DPC's drain. No more should be queued
// since we aren't taking interrupts now....
//
pendingDPCs = InterlockedDecrement(&extension->DpcCount);
if (pendingDPCs) { SerialDump(SERDIAG1, ("SERIAL: Drainging DPC's: %x\n",Irp)); KeWaitForSingleObject(&extension->PendingDpcEvent, Executive, KernelMode, FALSE, NULL); }
SerialDump(SERDIAG1, ("SERIAL: DPC's drained: %x\n",Irp));
//
// Pages must be locked to release the mutex, so don't unlock
// them until after we release the mutex
//
ExReleaseFastMutex(&extension->CloseMutex);
//
// Reset for next open
//
InterlockedIncrement(&extension->DpcCount);
openCount = InterlockedDecrement(&extension->OpenCount);
ASSERT(openCount == 0); SerialUnlockPagableImageSection(SerialGlobals.PAGESER_Handle);
return STATUS_SUCCESS;
}
BOOLEAN SerialCheckOpen( IN PVOID Context )
/*++
Routine Description:
This routine will traverse the circular doubly linked list of devices that are using the same interrupt object. It will look for other devices that are open. If it doesn't find any it will indicate that it is ok to open this device.
If it finds another device open we have two cases:
1) The device we are trying to open is on a multiport card.
If the already open device is part of a multiport device this code will indicate it is ok to open. We do this on the theory that the multiport devices are daisy chained and the cards can correctly arbitrate the interrupt line. Note this assumption could be wrong. Somebody could put two non-daisychained multiports on the same interrupt. However, only a total clod would do such a thing, and in my opinion deserves everthing they get.
2) The device we are trying to open is not on a multiport card.
We indicate that it is not ok to open.
Arguments:
Context - This is a structure that contains a pointer to the extension of the device we are trying to open, and a pointer to an NTSTATUS that will indicate whether the device was opened or not.
Return Value:
This routine always returns FALSE.
--*/
{
PSERIAL_DEVICE_EXTENSION extensionToOpen = ((PSERIAL_CHECK_OPEN)Context)->Extension; NTSTATUS *status = ((PSERIAL_CHECK_OPEN)Context)->StatusOfOpen; PLIST_ENTRY firstEntry = &extensionToOpen->CommonInterruptObject; PLIST_ENTRY currentEntry = firstEntry; PSERIAL_DEVICE_EXTENSION currentExtension;
do {
currentExtension = CONTAINING_RECORD( currentEntry, SERIAL_DEVICE_EXTENSION, CommonInterruptObject );
if (currentExtension->DeviceIsOpened) {
break;
}
currentEntry = currentExtension->CommonInterruptObject.Flink;
} while (currentEntry != firstEntry);
if (currentEntry == firstEntry) {
//
// We searched the whole list and found no other opens
// mark the status as successful and call the regular
// opening routine.
//
*status = STATUS_SUCCESS; SerialMarkOpen(extensionToOpen);
} else {
if (!extensionToOpen->PortOnAMultiportCard) {
*status = STATUS_SHARED_IRQ_BUSY;
} else {
if (!currentExtension->PortOnAMultiportCard) {
*status = STATUS_SHARED_IRQ_BUSY;
} else {
*status = STATUS_SUCCESS; SerialMarkOpen(extensionToOpen);
}
}
}
return FALSE;
}
BOOLEAN SerialMarkOpen( 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; #if defined(NEC_98)
//
// This argument use at MACRO only.
//
PSERIAL_DEVICE_EXTENSION Extension = Context; #else
#endif //defined(NEC_98)
SerialReset(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.
//
// For the Jensen, we will ALWAYS leave the line high. That's
// the way the hardware engineers want it.
//
WRITE_MODEM_CONTROL( extension->Controller, (UCHAR)(READ_MODEM_CONTROL(extension->Controller) | SERIAL_MCR_OUT2) );
extension->DeviceIsOpened = TRUE; extension->ErrorWord = 0;
return FALSE;
}
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; #if defined(NEC_98)
//
// This argument use at MACRO only.
//
PSERIAL_DEVICE_EXTENSION Extension = Context; #else
#endif //defined(NEC_98)
//
// 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.
//
// The line should stay high on the Jensen because that's the
// way the hardware engineers did it.
//
if (!extension->Jensen) { WRITE_MODEM_CONTROL(extension->Controller, (UCHAR)(READ_MODEM_CONTROL(extension->Controller) & ~SERIAL_MCR_OUT2)); }
if (extension->FifoPresent) { WRITE_FIFO_CONTROL(extension->Controller, (UCHAR)0); } }
BOOLEAN SerialMarkClose( 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;
SerialDisableUART(Context); extension->DeviceIsOpened = FALSE;
return FALSE;
}
NTSTATUS SerialCleanup( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
/*++
Routine Description:
This function is used to kill all longstanding IO operations.
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
--*/
{
PSERIAL_DEVICE_EXTENSION extension = DeviceObject->DeviceExtension; NTSTATUS status;
PAGED_CODE();
//
// We succeed a cleanup on a removing device
//
if ((status = SerialIRPPrologue(Irp, extension)) != STATUS_SUCCESS) { if (status == STATUS_DELETE_PENDING) { status = Irp->IoStatus.Status = STATUS_SUCCESS; } SerialCompleteRequest(extension, Irp, IO_NO_INCREMENT); return status; }
SerialDump( SERIRPPATH, ("SERIAL: Dispatch entry for: %x\n",Irp) );
SerialKillPendingIrps(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information=0L;
SerialDump( SERIRPPATH, ("SERIAL: Complete Irp: %x\n",Irp) ); SerialCompleteRequest(extension, Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
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; ULONG paritySize; ULONG stopSize; ULONG charTime; ULONG bitTime; LARGE_INTEGER tmp;
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;
}
|