windows-nt/Source/XPSP1/NT/net/homenet/bridge/sys/bridge.c

1300 lines
32 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright(c) 1999-2000 Microsoft Corporation
Module Name:
bridge.c
Abstract:
Ethernet MAC level bridge.
Author:
Mark Aiken
(original bridge by Jameel Hyder)
Environment:
Kernel mode driver
Revision History:
Sept 1999 - Original version
Feb 2000 - Overhaul
--*/
#define NDIS_WDM 1
#pragma warning( push, 3 )
#include <ndis.h>
#include <ntddk.h>
#include <tdikrnl.h>
#pragma warning( pop )
#include "bridge.h"
#include "brdgprot.h"
#include "brdgmini.h"
#include "brdgbuf.h"
#include "brdgtbl.h"
#include "brdgfwd.h"
#include "brdgctl.h"
#include "brdgsta.h"
#include "brdgcomp.h"
#include "brdgtdi.h"
// ===========================================================================
//
// GLOBALS
//
// ===========================================================================
// Our driver object
PDRIVER_OBJECT gDriverObject;
// Our registry path
UNICODE_STRING gRegistryPath;
// Size of the allocated memory at gRegistryPath->Buffer
ULONG gRegistryPathBufferSize;
// Whether we're in the process of shutting down (non-zero means true)
LONG gShuttingDown = 0L;
// Whether we successfully initialized each subsystem
BOOLEAN gInitedSTA = FALSE;
BOOLEAN gInitedControl = FALSE;
BOOLEAN gInitedTbl = FALSE;
BOOLEAN gInitedBuf = FALSE;
BOOLEAN gInitedFwd = FALSE;
BOOLEAN gInitedProt = FALSE;
BOOLEAN gInitedMini = FALSE;
BOOLEAN gInitedComp = FALSE;
BOOLEAN gInitedTdiGpo = FALSE;
extern BOOLEAN gBridging;
const PWCHAR gDisableForwarding = L"DisableForwarding";
#if DBG
// Support for optional "soft asserts"
BOOLEAN gSoftAssert = FALSE;
// Fields used for printing current date and time in DBGPRINT
LARGE_INTEGER gTime;
const LARGE_INTEGER gCorrection = { 0xAC5ED800, 0x3A }; // 7 hours in 100-nanoseconds
TIME_FIELDS gTimeFields;
// Used for throttling debug messages that risk overloading the debugger console
ULONG gLastThrottledPrint = 0L;
// Spew flags
ULONG gSpewFlags = 0L;
// Name of registry value that holds the spew flags settings
const PWCHAR gDebugFlagRegValueName = L"DebugFlags";
// Used to bypass Tdi/Gpo code if it's breaking on startup.
BOOLEAN gGpoTesting = TRUE;
#endif
// ===========================================================================
//
// PRIVATE DECLARATIONS
//
// ===========================================================================
// Structure for deferring a function call
typedef struct _DEFER_REC
{
NDIS_WORK_ITEM nwi;
VOID (*pFunc)(PVOID); // The function to defer
} DEFER_REC, *PDEFER_REC;
// ===========================================================================
//
// LOCAL PROTOTYPES
//
// ===========================================================================
NTSTATUS
BrdgDispatchRequest(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
);
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
);
NTSTATUS
BrdgAllocateBuffers(
VOID
);
VOID
BrdgDeferredShutdown(
PVOID pUnused
);
VOID
BrdgDoShutdown(
VOID
);
// ===========================================================================
//
// PUBLIC FUNCTIONS
//
// ===========================================================================
VOID
BrdgDeferredFunction(
IN PNDIS_WORK_ITEM pNwi,
IN PVOID arg
)
/*++
Routine Description:
NDIS worker function for deferring a function call
Arguments:
pNwi Structure describing the function to call
arg Argument to pass to the deferred function
Return Value:
None
--*/
{
PDEFER_REC pdr = (PDEFER_REC)pNwi;
// Call the originally supplied function
(*pdr->pFunc)(arg);
// Release the memory used to store the work item
NdisFreeMemory( pdr, sizeof(DEFER_REC), 0 );
}
NDIS_STATUS
BrdgDeferFunction(
VOID (*pFunc)(PVOID),
PVOID arg
)
/*++
Routine Description:
Defers the indicated function, calling it at low IRQL with the indicated argument.
Arguments:
pFunc The function to call later
arg The argument to pass it when it is called
Return Value:
Status of the attempt to defer the function
--*/
{
PDEFER_REC pdr;
NDIS_STATUS Status;
Status = NdisAllocateMemoryWithTag( &pdr, sizeof(DEFER_REC), 'gdrB' );
if( Status != NDIS_STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Allocation failed in BrdgDeferFunction(): %08x\n", Status));
return Status;
}
SAFEASSERT( pdr != NULL );
pdr->pFunc = pFunc;
NdisInitializeWorkItem( &pdr->nwi, BrdgDeferredFunction, arg );
Status = NdisScheduleWorkItem( &pdr->nwi );
if( Status != NDIS_STATUS_SUCCESS )
{
NdisFreeMemory( pdr, sizeof(DEFER_REC), 0 );
}
return Status;
}
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING pRegistryPath
)
/*++
Routine Description:
Main driver entry point. Called at driver load time
Arguments:
DriverObject Our driver
RegistryPath A reg key where we can keep parameters
Return Value:
Status of our initialization. A status != STATUS_SUCCESS aborts the
driver load and we don't get called again.
Each component is responsible for logging any error that causes the
driver load to fail.
--*/
{
NTSTATUS Status;
NDIS_STATUS NdisStatus;
PUCHAR pRegistryPathCopy;
DBGPRINT(GENERAL, ("DriverEntry\n"));
// Remember our driver object pointer
gDriverObject = DriverObject;
do
{
ULONG ulDisableForwarding = 0L;
// Make a copy of our registry path
pRegistryPathCopy = NULL;
gRegistryPathBufferSize = pRegistryPath->Length + sizeof(WCHAR);
NdisStatus = NdisAllocateMemoryWithTag( &pRegistryPathCopy, gRegistryPathBufferSize, 'gdrB' );
if( (NdisStatus != NDIS_STATUS_SUCCESS) || (pRegistryPathCopy == NULL) )
{
DBGPRINT(GENERAL, ("Unable to allocate memory for saving the registry path: %08x\n", NdisStatus));
NdisWriteEventLogEntry( gDriverObject, EVENT_BRIDGE_INIT_MALLOC_FAILED, 0, 0, NULL, 0L, NULL );
Status = NdisStatus;
// Make the structure valid even though we failed the malloc
RtlInitUnicodeString( &gRegistryPath, NULL );
gRegistryPathBufferSize = 0L;
break;
}
// Copy the registry name
NdisMoveMemory( pRegistryPathCopy, pRegistryPath->Buffer, pRegistryPath->Length );
// Make sure it's NULL-terminated
*((PWCHAR)(pRegistryPathCopy + pRegistryPath->Length)) = UNICODE_NULL;
// Make the UNICODE_STRING structure point to the string
RtlInitUnicodeString( &gRegistryPath, (PWCHAR)pRegistryPathCopy );
// Set our debug flags
#if DBG
BrdgReadRegDWord(&gRegistryPath, gDebugFlagRegValueName, &gSpewFlags);
#endif
// Initialize the STA part of the driver
Status = BrdgSTADriverInit();
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Unable to initialize STA functionality: %08x\n", Status));
break;
}
gInitedSTA = TRUE;
// Initialize the control part of the driver
Status = BrdgCtlDriverInit();
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Unable to initialize user-mode control functionality: %08x\n", Status));
break;
}
gInitedControl = TRUE;
// Initialize the MAC table part of our driver
Status = BrdgTblDriverInit();
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Unable to initialize MAC table functionality: %08x\n", Status));
break;
}
gInitedTbl = TRUE;
// Initialize the forwarding engine
Status = BrdgFwdDriverInit();
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Unable to initialize forwarding engine functionality: %08x\n", Status));
break;
}
gInitedFwd = TRUE;
// Initialize the buffer management part of our driver
Status = BrdgBufDriverInit();
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Unable to initialize miniport functionality: %08x\n", Status));
break;
}
gInitedBuf = TRUE;
// Initialize the miniport part of our driver
Status = BrdgMiniDriverInit();
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Unable to initialize miniport functionality: %08x\n", Status));
break;
}
gInitedMini = TRUE;
// Initialize the protocol part of our driver
Status = BrdgProtDriverInit();
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Unable to initialize protocol functionality: %08x\n", Status));
break;
}
gInitedProt = TRUE;
// Initialize the compatibility-mode code
Status = BrdgCompDriverInit();
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Unable to initialize compatibility-mode functionality: %08x\n", Status));
break;
}
gInitedComp = TRUE;
Status = BrdgReadRegDWord(&gRegistryPath, gDisableForwarding, &ulDisableForwarding);
if ((!NT_SUCCESS(Status) || !ulDisableForwarding))
{
//
// Group policies are only in effect on Professional and up.
//
if (!BrdgIsRunningOnPersonal())
{
#if DBG
if (gGpoTesting)
{
#endif
// Initialize the tdi code
Status = BrdgTdiDriverInit();
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Unable to initialize tdi functionality: %08x\n", Status));
break;
}
gInitedTdiGpo = TRUE;
#if DBG
}
#endif
}
else
{
gBridging = TRUE;
Status = STATUS_SUCCESS;
}
}
// Associate the miniport to the protocol
BrdgMiniAssociate();
} while (FALSE);
if (Status != STATUS_SUCCESS)
{
BrdgDoShutdown();
}
return(Status);
}
NTSTATUS
BrdgDispatchRequest(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
/*++
Routine Description:
Receives control requests from the outside
Arguments:
pDeviceObject Our driver
pIrp The IRP to handle
Return Value:
Status of the operation
--*/
{
PVOID Buffer;
PIO_STACK_LOCATION IrpSp;
ULONG Size = 0;
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
Buffer = pIrp->AssociatedIrp.SystemBuffer;
IrpSp = IoGetCurrentIrpStackLocation(pIrp);
if( IrpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL )
{
// Don't accept IRPs when we're shutting down
if( gShuttingDown )
{
status = STATUS_UNSUCCESSFUL;
pIrp->IoStatus.Information = 0;
}
else
{
status = BrdgCtlHandleIoDeviceControl( pIrp, IrpSp->FileObject, Buffer,
IrpSp->Parameters.DeviceIoControl.InputBufferLength,
IrpSp->Parameters.DeviceIoControl.OutputBufferLength,
IrpSp->Parameters.DeviceIoControl.IoControlCode, &Size );
}
}
else
{
if( IrpSp->MajorFunction == IRP_MJ_CREATE )
{
BrdgCtlHandleCreate();
}
else if( IrpSp->MajorFunction == IRP_MJ_CLEANUP )
{
BrdgCtlHandleCleanup();
}
// Leave status == STATUS_SUCCESS and Size == 0
}
if( status != STATUS_PENDING )
{
pIrp->IoStatus.Information = Size;
pIrp->IoStatus.Status = status;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
return status;
}
VOID
BrdgDeferredShutdown(
PVOID pUnused
)
/*++
Routine Description:
Orderly-shutdown routine if we need to defer that task from high IRQL
Arguments:
pUnused Ignored
Return Value:
None
--*/
{
BrdgDoShutdown();
}
VOID
BrdgDoShutdown(
VOID
)
/*++
Routine Description:
Called to do an orderly shutdown at unload time
Arguments:
None
Return Value:
None
--*/
{
DBGPRINT(GENERAL, ("==> BrdgDoShutdown()!\n"));
// Clean up each of the sections
if ( gInitedTdiGpo )
{
gInitedTdiGpo = FALSE;
BrdgTdiCleanup();
}
if( gInitedControl )
{
gInitedControl = FALSE;
BrdgCtlCleanup();
}
if( gInitedProt )
{
gInitedProt = FALSE;
BrdgProtCleanup();
}
// This needs to be cleaned up after the protocol section
if( gInitedSTA )
{
gInitedSTA = FALSE;
BrdgSTACleanup();
}
if( gInitedMini )
{
gInitedMini = FALSE;
BrdgMiniCleanup();
}
if( gInitedTbl )
{
gInitedTbl = FALSE;
BrdgTblCleanup();
}
if( gInitedBuf )
{
gInitedBuf = FALSE;
BrdgBufCleanup();
}
if( gInitedFwd )
{
gInitedFwd = FALSE;
BrdgFwdCleanup();
}
if( gInitedComp )
{
gInitedComp = FALSE;
BrdgCompCleanup();
}
if( gRegistryPath.Buffer != NULL )
{
NdisFreeMemory( gRegistryPath.Buffer, gRegistryPathBufferSize, 0 );
gRegistryPath.Buffer = NULL;
}
DBGPRINT(GENERAL, ("<== BrdgDoShutdown()\n"));
}
VOID
BrdgUnload(
IN PDRIVER_OBJECT DriverObject
)
/*++
Routine Description:
Called to indicate that we are being unloaded and to cause an orderly
shutdown
Arguments:
DriverObject Our driver
Return Value:
None
--*/
{
if( ! InterlockedExchange(&gShuttingDown, 1L) )
{
BrdgDoShutdown();
}
// else was already shutting down; do nothing
}
VOID BrdgShutdown(
VOID
)
{
if( ! InterlockedExchange(&gShuttingDown, 1L) )
{
BrdgDoShutdown();
}
// else was already shutting down; do nothing
}
NTSTATUS
BrdgReadRegUnicode(
IN PUNICODE_STRING KeyName,
IN PWCHAR pValueName,
OUT PWCHAR *String, // The string from the registry, freshly allocated
OUT PULONG StringSize // Size of allocated memory at String
)
/*++
Routine Description:
Reads a Unicode string from a specific registry key and value. Allocates memory
for the string and returns it.
Arguments:
KeyName The key holding the string
pValueName The name of the value holding the string
String A pointer to indicate a freshly allocated buffer containing
the requested string on return
StringSize Size of the returned buffer
Return Value:
Status of the operation. String is not valid if return != STATUS_SUCCESS
--*/
{
NDIS_STATUS NdisStatus;
HANDLE KeyHandle;
OBJECT_ATTRIBUTES ObjAttrs;
NTSTATUS Status;
ULONG RequiredSize;
KEY_VALUE_PARTIAL_INFORMATION *pInfo;
UNICODE_STRING ValueName;
// Turn the string into a UNICODE_STRING
RtlInitUnicodeString( &ValueName, pValueName );
// Describe the key to open
InitializeObjectAttributes( &ObjAttrs, KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL );
// Open it
Status = ZwOpenKey( &KeyHandle, KEY_READ, &ObjAttrs );
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Failed to open registry key \"%ws\": %08x\n", KeyName->Buffer, Status));
return Status;
}
// Find out how much memory is necessary to hold the value information
Status = ZwQueryValueKey( KeyHandle, &ValueName, KeyValuePartialInformation, NULL,
0L, &RequiredSize );
if( (Status != STATUS_BUFFER_OVERFLOW) &&
(Status != STATUS_BUFFER_TOO_SMALL) )
{
DBGPRINT(GENERAL, ("Failed to query for the size of value \"%ws\": %08x\n", ValueName.Buffer, Status));
ZwClose( KeyHandle );
return Status;
}
// Allocate the indicated amount of memory
NdisStatus = NdisAllocateMemoryWithTag( (PVOID*)&pInfo, RequiredSize, 'gdrB' );
if( NdisStatus != NDIS_STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("NdisAllocateMemoryWithTag failed: %08x\n", NdisStatus));
ZwClose( KeyHandle );
return STATUS_UNSUCCESSFUL;
}
// Actually read out the string
Status = ZwQueryValueKey( KeyHandle, &ValueName, KeyValuePartialInformation, pInfo,
RequiredSize, &RequiredSize );
ZwClose( KeyHandle );
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("ZwQueryValueKey failed: %08x\n", Status));
NdisFreeMemory( pInfo, RequiredSize, 0 );
return Status;
}
// This had better be a Unicode string with something in it
if( pInfo->Type != REG_SZ && pInfo->Type != REG_MULTI_SZ)
{
SAFEASSERT(FALSE);
NdisFreeMemory( pInfo, RequiredSize, 0 );
return STATUS_UNSUCCESSFUL;
}
// Allocate memory for the string
*StringSize = pInfo->DataLength + sizeof(WCHAR);
NdisStatus = NdisAllocateMemoryWithTag( (PVOID*)String, *StringSize, 'gdrB' );
if( NdisStatus != NDIS_STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("NdisAllocateMemoryWithTag failed: %08x\n", NdisStatus));
NdisFreeMemory( pInfo, RequiredSize, 0 );
return STATUS_UNSUCCESSFUL;
}
SAFEASSERT( *String != NULL );
// Copy the string to the freshly allocated memory
NdisMoveMemory( *String, &pInfo->Data, pInfo->DataLength );
// Put a two-byte NULL character at the end
((PUCHAR)*String)[pInfo->DataLength] = '0';
((PUCHAR)*String)[pInfo->DataLength + 1] = '0';
// Let go of resources we used on the way
NdisFreeMemory( pInfo, RequiredSize, 0 );
return STATUS_SUCCESS;
}
NTSTATUS
BrdgReadRegDWord(
IN PUNICODE_STRING KeyName,
IN PWCHAR pValueName,
OUT PULONG Value
)
/*++
Routine Description:
Reads a DWORD value out of the registry
Arguments:
KeyName The name of the key holding the value
pValueName The name of the value holding the value
Value Receives the value
Return Value:
Status of the operation. Value is junk if return value != STATUS_SUCCESS
--*/
{
HANDLE KeyHandle;
OBJECT_ATTRIBUTES ObjAttrs;
NTSTATUS Status;
UCHAR InfoBuffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(ULONG)];
ULONG RequiredSize;
UNICODE_STRING ValueName;
// Turn the PWCHAR into a UNICODE_STRING
RtlInitUnicodeString( &ValueName, pValueName );
// Describe the key to open
InitializeObjectAttributes( &ObjAttrs, KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL );
// Open it
Status = ZwOpenKey( &KeyHandle, KEY_READ, &ObjAttrs );
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("Failed to open registry key \"%ws\": %08x\n", KeyName->Buffer, Status));
return Status;
}
// Actually read out the value
Status = ZwQueryValueKey( KeyHandle, &ValueName, KeyValuePartialInformation,
(PKEY_VALUE_PARTIAL_INFORMATION)&InfoBuffer,
sizeof(InfoBuffer), &RequiredSize );
ZwClose( KeyHandle );
if( Status != STATUS_SUCCESS )
{
DBGPRINT(GENERAL, ("ZwQueryValueKey failed: %08x\n", Status));
return Status;
}
// This had better be a DWORD value
if( (((PKEY_VALUE_PARTIAL_INFORMATION)&InfoBuffer)->Type != REG_DWORD) ||
(((PKEY_VALUE_PARTIAL_INFORMATION)&InfoBuffer)->DataLength != sizeof(ULONG)) )
{
DBGPRINT(GENERAL, ("Registry parameter %ws not of the requested type!\n"));
return STATUS_UNSUCCESSFUL;
}
*Value = *((PULONG)((PKEY_VALUE_PARTIAL_INFORMATION)&InfoBuffer)->Data);
return STATUS_SUCCESS;
}
NTSTATUS
BrdgOpenDevice (
IN LPWSTR pDeviceNameStr,
OUT PDEVICE_OBJECT *ppDeviceObject,
OUT HANDLE *pFileHandle,
OUT PFILE_OBJECT *ppFileObject
)
/*++
Routine Description:
Opens specified device driver (control channel) and returns a file object
and a driver object. The caller should call BrdgCloseDevice() to shut
down the connection when it's done.
Arguments:
DeviceNameStr device to open.
pFileHandle Receives a file handle
ppFileObject Receives a pointer to the file object
ppDeviceObject Receives a pointer to the device object
Return Value:
NTSTATUS -- Indicates whether the device was opened OK
--*/
{
NTSTATUS status;
UNICODE_STRING DeviceName;
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK iosb;
// We make calls that can only be performed at PASSIVE_LEVEL.
SAFEASSERT( CURRENT_IRQL <= PASSIVE_LEVEL );
RtlInitUnicodeString(&DeviceName, pDeviceNameStr);
InitializeObjectAttributes(
&objectAttributes,
&DeviceName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, // attributes
NULL,
NULL
);
status = IoCreateFile(
pFileHandle,
MAXIMUM_ALLOWED,
&objectAttributes,
&iosb, // returned status information.
0, // block size (unused).
0, // file attributes.
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_CREATE, // create disposition.
0, // create options.
NULL, // eaInfo
0, // eaLength
CreateFileTypeNone, // CreateFileType
NULL, // ExtraCreateParameters
IO_NO_PARAMETER_CHECKING // Options
| IO_FORCE_ACCESS_CHECK
);
if (NT_SUCCESS(status))
{
status = ObReferenceObjectByHandle (
*pFileHandle,
0L,
*IoFileObjectType,
KernelMode,
(PVOID *)ppFileObject,
NULL
);
if (! NT_SUCCESS(status))
{
DBGPRINT(ALWAYS_PRINT, ("ObReferenceObjectByHandle FAILED while opening a device: %8x\n", status));
ZwClose (*pFileHandle);
}
else
{
// Recover the driver object
*ppDeviceObject = IoGetRelatedDeviceObject ( *ppFileObject );
SAFEASSERT( *ppDeviceObject != NULL );
// Reference the driver handle, too.
ObReferenceObject( *ppDeviceObject );
}
}
else
{
DBGPRINT(ALWAYS_PRINT, ("IoCreateFile FAILED while opening a device: %8x\n", status));
}
return status;
}
VOID
BrdgCloseDevice(
IN HANDLE FileHandle,
IN PFILE_OBJECT pFileObject,
IN PDEVICE_OBJECT pDeviceObject
)
/*++
Routine Description:
Closes a device
Arguments:
FileHandle The file handle
pFileObject The file object of the device
pDeviceObject The device object of the device
Return Value:
None
--*/
{
NTSTATUS status;
// We make calls that can only be performed at PASSIVE_LEVEL.
SAFEASSERT( CURRENT_IRQL <= PASSIVE_LEVEL );
ObDereferenceObject( pFileObject );
ObDereferenceObject( pDeviceObject );
status = ZwClose( FileHandle );
SAFEASSERT( NT_SUCCESS(status) );
}
VOID
BrdgTimerExpiry(
IN PVOID ignored1,
IN PVOID data,
IN PVOID ignored2,
IN PVOID ignored3
)
/*++
Routine Description:
Master device expiry function. Calls a timer-specific expiry
function if one was specified for this timer.
Arguments:
data The timer pointer
Return Value:
None
--*/
{
PBRIDGE_TIMER pTimer = (PBRIDGE_TIMER)data;
NdisAcquireSpinLock( &pTimer->Lock );
SAFEASSERT( pTimer->bRunning );
if( pTimer->bCanceled )
{
// This is the rare codepath where a call to NdisCancelTimer() was unable to
// dequeue our timer entry because we were about to be called.
DBGPRINT(GENERAL, ("Timer expiry function called with cancelled timer!\n"));
// Don't call the timer function; just bail out
pTimer->bRunning = FALSE;
NdisReleaseSpinLock( &pTimer->Lock );
// Unblock BrdgShutdownTimer()
NdisSetEvent( &pTimer->Event );
}
else
{
BOOLEAN bRecurring;
UINT interval;
// Read protected values inside the lock
bRecurring = pTimer->bRecurring;
interval = pTimer->Interval;
// Update bRunning inside the spin lock
pTimer->bRunning = bRecurring;
NdisReleaseSpinLock( &pTimer->Lock );
// Call the timer function
(*pTimer->pFunc)(pTimer->data);
if( bRecurring )
{
// Start it up again
NdisSetTimer( &pTimer->Timer, interval );
}
}
}
VOID
BrdgInitializeTimer(
IN PBRIDGE_TIMER pTimer,
IN PBRIDGE_TIMER_FUNC pFunc,
IN PVOID data
)
/*++
Routine Description:
Sets up a BRIDGE_TIMER.
Arguments:
pTimer The timer
pFunc Expiry function
data Cookie to pass to pFunc
Return Value:
None
--*/
{
pTimer->bShuttingDown = FALSE;
pTimer->bRunning = FALSE;
pTimer->bCanceled = FALSE;
pTimer->pFunc = pFunc;
pTimer->data = data;
NdisInitializeTimer( &pTimer->Timer, BrdgTimerExpiry, (PVOID)pTimer );
NdisInitializeEvent( &pTimer->Event );
NdisResetEvent( &pTimer->Event );
NdisAllocateSpinLock( &pTimer->Lock );
// Leave pTimer->bRecurring alone; it gets a value when the timer is started.
}
VOID
BrdgSetTimer(
IN PBRIDGE_TIMER pTimer,
IN UINT interval,
IN BOOLEAN bRecurring
)
/*++
Routine Description:
Starts a BRIDGE_TIMER ticking.
Arguments:
pTimer The timer
interval Time before expiry in ms
bRecurring TRUE to restart the timer when it expires
Return Value:
None
--*/
{
NdisAcquireSpinLock( &pTimer->Lock );
if( !pTimer->bShuttingDown )
{
pTimer->bRunning = TRUE;
pTimer->bCanceled = FALSE;
pTimer->Interval = interval;
pTimer->bRecurring = bRecurring;
NdisReleaseSpinLock( &pTimer->Lock );
// Actually start the timer
NdisSetTimer( &pTimer->Timer, interval );
}
else
{
NdisReleaseSpinLock( &pTimer->Lock );
DBGPRINT(ALWAYS_PRINT, ("WARNING: Ignoring an attempt to restart a timer in final shutdown!\n"));
}
}
VOID
BrdgShutdownTimer(
IN PBRIDGE_TIMER pTimer
)
/*++
Routine Description:
Safely shuts down a timer, waiting to make sure that the timer has been
completely dequeued or its expiry function has started executing (there
is no way to guarantee that the expiry function is completely done
executing, however).
Must be called at PASSIVE_LEVEL.
Arguments:
pTimer The timer
Return Value:
None
--*/
{
// We wait on an event
SAFEASSERT( CURRENT_IRQL <= PASSIVE_LEVEL );
NdisAcquireSpinLock( &pTimer->Lock );
// Forbid future calls to BrdgSetTimer().
pTimer->bShuttingDown = TRUE;
if( pTimer->bRunning )
{
BOOLEAN bCanceled;
// Make sure the timer expiry function will bail out if it's too late to
// dequeue the timer and it ends up getting called
pTimer->bCanceled = TRUE;
// This will unblock the timer expiry function, but even if it executes
// between now and the call to NdisCancelTimer, it should still end up
// signalling the event we will wait on below.
NdisReleaseSpinLock( &pTimer->Lock );
// Try to cancel the timer.
NdisCancelTimer( &pTimer->Timer, &bCanceled );
if( !bCanceled )
{
//
// bCancelled can be FALSE if the timer wasn't running in the first place,
// or if the OS couldn't dequeue the timer (but our expiry function will
// still be called). Our use of our timer structure's spin lock should
// guarantee that the timer expiry function will be executed after we
// released the spin lock above, if we are on this code path. This means
// that the event we wait on below will be signalled by the timer expiry
// function.
//
DBGPRINT(GENERAL, ("Couldn't dequeue timer; blocking on completion\n"));
// Wait for the completion function to finish its work
NdisWaitEvent( &pTimer->Event, 0 /*Wait forever*/ );
// The completion function should have cleared this
SAFEASSERT( !pTimer->bRunning );
}
else
{
pTimer->bRunning = FALSE;
}
}
else
{
// Tried to shutdown a timer that was not running. This is allowed (it does nothing).
NdisReleaseSpinLock( &pTimer->Lock );
}
}
VOID
BrdgCancelTimer(
IN PBRIDGE_TIMER pTimer
)
/*++
Routine Description:
Attempts to cancel a timer, but provides no guarantee that the timer is
actually stopped on return. It is possible for the timer expiry function
to fire after this function returns.
Arguments:
pTimer The timer
Return Value:
None.
--*/
{
NdisAcquireSpinLock( &pTimer->Lock );
if( pTimer->bRunning )
{
BOOLEAN bCanceled;
pTimer->bCanceled = TRUE;
NdisCancelTimer( &pTimer->Timer, &bCanceled );
if( bCanceled )
{
pTimer->bRunning = FALSE;
}
// else timer expiry function will set bRunning to FALSE when it completes.
}
// else tried to cancel a timer that was not running. This is allowed (it does nothing).
NdisReleaseSpinLock( &pTimer->Lock );
}
BOOLEAN
BrdgIsRunningOnPersonal(
VOID
)
/*++
Routine Description:
Determines if we're running on a Personal build.
Arguments:
None.
Return Value:
TRUE if we're on Personal, FALSE if we're not.
--*/
{
OSVERSIONINFOEXW OsVer = {0};
ULONGLONG ConditionMask = 0;
BOOLEAN IsPersonal = TRUE;
OsVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
OsVer.wSuiteMask = VER_SUITE_PERSONAL;
OsVer.wProductType = VER_NT_WORKSTATION;
VER_SET_CONDITION(ConditionMask, VER_PRODUCT_TYPE, VER_EQUAL);
VER_SET_CONDITION(ConditionMask, VER_SUITENAME, VER_AND);
if (RtlVerifyVersionInfo(&OsVer, VER_PRODUCT_TYPE | VER_SUITENAME,
ConditionMask) == STATUS_REVISION_MISMATCH) {
IsPersonal = FALSE;
}
return IsPersonal;
}