Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1072 lines
30 KiB

/*****************************************************************************
* adapter.cpp - SB16 adapter driver implementation.
*****************************************************************************
* Copyright (c) 1997-2000 Microsoft Corporation. All rights reserved.
*
* This files does setup and resource allocation/verification for the SB16
* card. It controls which miniports are started and which resources are
* given to each miniport. It also deals with interrupt sharing between
* miniports by hooking the interrupt and calling the correct DPC.
*/
//
// All the GUIDS for all the miniports end up in this object.
//
#define PUT_GUIDS_HERE
#define STR_MODULENAME "sb16Adapter: "
#include "common.h"
/*****************************************************************************
* Defines
*/
#define MAX_MINIPORTS 5
#if (DBG)
#define SUCCEEDS(s) ASSERT(NT_SUCCESS(s))
#else
#define SUCCEEDS(s) (s)
#endif
/*****************************************************************************
* Externals
*/
NTSTATUS
CreateMiniportWaveCyclicSB16
(
OUT PUNKNOWN * Unknown,
IN REFCLSID,
IN PUNKNOWN UnknownOuter OPTIONAL,
IN POOL_TYPE PoolType
);
NTSTATUS
CreateMiniportTopologySB16
(
OUT PUNKNOWN * Unknown,
IN REFCLSID,
IN PUNKNOWN UnknownOuter OPTIONAL,
IN POOL_TYPE PoolType
);
/*****************************************************************************
* Referenced forward
*/
extern "C"
NTSTATUS
AddDevice
(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
);
NTSTATUS
StartDevice
(
IN PDEVICE_OBJECT DeviceObject, // Device object.
IN PIRP Irp, // IO request packet.
IN PRESOURCELIST ResourceList // List of hardware resources.
);
NTSTATUS
AssignResources
(
IN PRESOURCELIST ResourceList, // All resources.
OUT PRESOURCELIST * ResourceListWave, // Wave resources.
OUT PRESOURCELIST * ResourceListWaveTable, // Wave table resources.
OUT PRESOURCELIST * ResourceListFmSynth, // FM synth resources.
OUT PRESOURCELIST * ResourceListUart, // UART resources.
OUT PRESOURCELIST * ResourceListAdapter // a copy needed by the adapter
);
#ifdef DO_RESOURCE_FILTERING
extern "C"
NTSTATUS
AdapterDispatchPnp
(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
);
#endif
DWORD DeterminePlatform(PPORTTOPOLOGY Port);
#pragma code_seg("INIT")
/*****************************************************************************
* DriverEntry()
*****************************************************************************
* This function is called by the operating system when the driver is loaded.
* All adapter drivers can use this code without change.
*/
extern "C"
NTSTATUS
DriverEntry
(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPathName
)
{
PAGED_CODE();
//
// Tell the class driver to initialize the driver.
//
NTSTATUS ntStatus = PcInitializeAdapterDriver( DriverObject,
RegistryPathName,
AddDevice );
#ifdef DO_RESOURCE_FILTERING
//
// We want to do resource filtering, so we'll install our own PnP IRP handler.
//
if(NT_SUCCESS(ntStatus))
{
DriverObject->MajorFunction[IRP_MJ_PNP] = AdapterDispatchPnp;
}
#endif
return ntStatus;
}
#pragma code_seg("PAGE")
/*****************************************************************************
* AddDevice()
*****************************************************************************
* This function is called by the operating system when the device is added.
* All adapter drivers can use this code without change.
*/
extern "C"
NTSTATUS
AddDevice
(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
)
{
PAGED_CODE();
//
// Tell the class driver to add the device.
//
return PcAddAdapterDevice( DriverObject,
PhysicalDeviceObject,
PCPFNSTARTDEVICE( StartDevice ),
MAX_MINIPORTS,
0 );
}
/*****************************************************************************
* InstallSubdevice()
*****************************************************************************
* This function creates and registers a subdevice consisting of a port
* driver, a minport driver and a set of resources bound together. It will
* also optionally place a pointer to an interface on the port driver in a
* specified location before initializing the port driver. This is done so
* that a common ISR can have access to the port driver during initialization,
* when the ISR might fire.
*/
NTSTATUS
InstallSubdevice
(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PWCHAR Name,
IN REFGUID PortClassId,
IN REFGUID MiniportClassId,
IN PFNCREATEINSTANCE MiniportCreate OPTIONAL,
IN PUNKNOWN UnknownAdapter OPTIONAL,
IN PRESOURCELIST ResourceList,
IN REFGUID PortInterfaceId,
OUT PUNKNOWN * OutPortInterface OPTIONAL,
OUT PUNKNOWN * OutPortUnknown OPTIONAL
)
{
PAGED_CODE();
_DbgPrintF(DEBUGLVL_VERBOSE, ("InstallSubdevice"));
ASSERT(DeviceObject);
ASSERT(Irp);
ASSERT(Name);
//
// Create the port driver object
//
PPORT port;
NTSTATUS ntStatus = PcNewPort(&port,PortClassId);
if (NT_SUCCESS(ntStatus))
{
//
// Deposit the port somewhere if it's needed.
//
if (OutPortInterface)
{
//
// Failure here doesn't cause the entire routine to fail.
//
(void) port->QueryInterface
(
PortInterfaceId,
(PVOID *) OutPortInterface
);
}
PUNKNOWN miniport;
//
// Create the miniport object
//
if (MiniportCreate)
{
ntStatus = MiniportCreate
(
&miniport,
MiniportClassId,
NULL,
NonPagedPool
);
}
else
{
ntStatus = PcNewMiniport((PMINIPORT*) &miniport,MiniportClassId);
}
if (NT_SUCCESS(ntStatus))
{
//
// Init the port driver and miniport in one go.
//
ntStatus = port->Init( DeviceObject,
Irp,
miniport,
UnknownAdapter,
ResourceList );
if (NT_SUCCESS(ntStatus))
{
//
// Register the subdevice (port/miniport combination).
//
ntStatus = PcRegisterSubdevice( DeviceObject,
Name,
port );
if (!(NT_SUCCESS(ntStatus)))
{
_DbgPrintF(DEBUGLVL_TERSE, ("StartDevice: PcRegisterSubdevice failed"));
}
}
else
{
_DbgPrintF(DEBUGLVL_TERSE, ("InstallSubdevice: port->Init failed"));
}
//
// We don't need the miniport any more. Either the port has it,
// or we've failed, and it should be deleted.
//
miniport->Release();
}
else
{
_DbgPrintF(DEBUGLVL_TERSE, ("InstallSubdevice: PcNewMiniport failed"));
}
if (NT_SUCCESS(ntStatus))
{
//
// Deposit the port as an unknown if it's needed.
//
if (OutPortUnknown)
{
//
// Failure here doesn't cause the entire routine to fail.
//
(void) port->QueryInterface
(
IID_IUnknown,
(PVOID *) OutPortUnknown
);
}
}
else
{
//
// Retract previously delivered port interface.
//
if (OutPortInterface && (*OutPortInterface))
{
(*OutPortInterface)->Release();
*OutPortInterface = NULL;
}
}
//
// Release the reference which existed when PcNewPort() gave us the
// pointer in the first place. This is the right thing to do
// regardless of the outcome.
//
port->Release();
}
else
{
_DbgPrintF(DEBUGLVL_TERSE, ("InstallSubdevice: PcNewPort failed"));
}
return ntStatus;
}
/*****************************************************************************
* StartDevice()
*****************************************************************************
* This function is called by the operating system when the device is started.
* It is responsible for starting the miniports. This code is specific to
* the adapter because it calls out miniports for functions that are specific
* to the adapter.
*/
NTSTATUS
StartDevice
(
IN PDEVICE_OBJECT DeviceObject, // Device object.
IN PIRP Irp, // IO request packet.
IN PRESOURCELIST ResourceList // List of hardware resources.
)
{
PAGED_CODE();
ASSERT(DeviceObject);
ASSERT(Irp);
ASSERT(ResourceList);
//
// These are the sub-lists of resources that will be handed to the
// miniports.
//
PRESOURCELIST resourceListWave = NULL;
PRESOURCELIST resourceListWaveTable = NULL;
PRESOURCELIST resourceListFmSynth = NULL;
PRESOURCELIST resourceListUart = NULL;
PRESOURCELIST resourceListAdapter = NULL;
//
// These are the port driver pointers we are keeping around for registering
// physical connections.
//
PUNKNOWN unknownTopology = NULL;
PUNKNOWN unknownWave = NULL;
PUNKNOWN unknownWaveTable = NULL;
PUNKNOWN unknownFmSynth = NULL;
//
// Assign resources to individual miniports. Each sub-list is a copy
// of the resources from the master list. Each sublist must be released.
//
NTSTATUS ntStatus = AssignResources( ResourceList,
&resourceListWave,
&resourceListWaveTable,
&resourceListFmSynth,
&resourceListUart,
&resourceListAdapter );
//
// if AssignResources succeeded...
//
if(NT_SUCCESS(ntStatus))
{
//
// If the adapter has resources...
//
PADAPTERCOMMON pAdapterCommon = NULL;
if (resourceListAdapter)
{
PUNKNOWN pUnknownCommon;
// create a new adapter common object
ntStatus = NewAdapterCommon( &pUnknownCommon,
IID_IAdapterCommon,
NULL,
NonPagedPool );
if (NT_SUCCESS(ntStatus))
{
ASSERT( pUnknownCommon );
// query for the IAdapterCommon interface
ntStatus = pUnknownCommon->QueryInterface( IID_IAdapterCommon,
(PVOID *)&pAdapterCommon );
if (NT_SUCCESS(ntStatus))
{
// Initialize the object
ntStatus = pAdapterCommon->Init( resourceListAdapter,
DeviceObject );
if (NT_SUCCESS(ntStatus))
{
// register with PortCls for power-management services
ntStatus = PcRegisterAdapterPowerManagement( (PUNKNOWN)pAdapterCommon,
DeviceObject );
}
}
// release the IUnknown on adapter common
pUnknownCommon->Release();
}
// release the adapter common resource list
resourceListAdapter->Release();
}
//
// Start the topology miniport.
//
if (NT_SUCCESS(ntStatus))
{
ntStatus = InstallSubdevice( DeviceObject,
Irp,
L"Topology",
CLSID_PortTopology,
CLSID_PortTopology, // not used
CreateMiniportTopologySB16,
pAdapterCommon,
NULL,
GUID_NULL,
NULL,
&unknownTopology );
}
//
// Start the SB wave miniport if it exists.
//
if (resourceListWave)
{
if (NT_SUCCESS(ntStatus))
{
ntStatus = InstallSubdevice( DeviceObject,
Irp,
L"Wave",
CLSID_PortWaveCyclic,
CLSID_PortWaveCyclic, // not used
CreateMiniportWaveCyclicSB16,
pAdapterCommon,
resourceListWave,
IID_IPortWaveCyclic,
NULL,
&unknownWave );
}
// release the wave resource list
resourceListWave->Release();
}
// Start the wave table miniport if it exists.
if (resourceListWaveTable)
{
//
// NOTE: The wavetable is not currently supported in this sample driver.
//
// release the wavetable resource list
resourceListWaveTable->Release();
}
//
// Start the FM synth miniport if it exists.
//
if (resourceListFmSynth)
{
//
// Synth not working yet.
//
if (NT_SUCCESS(ntStatus))
{
//
// Failure here is not fatal.
//
InstallSubdevice( DeviceObject,
Irp,
L"FMSynth",
CLSID_PortMidi,
CLSID_MiniportDriverFmSynth,
NULL,
pAdapterCommon,
resourceListFmSynth,
GUID_NULL,
NULL,
&unknownFmSynth );
}
// release the FM synth resource list
resourceListFmSynth->Release();
}
//
// Start the UART miniport if it exists.
//
if (resourceListUart)
{
if (NT_SUCCESS(ntStatus))
{
//
// Failure here is not fatal.
//
InstallSubdevice( DeviceObject,
Irp,
L"Uart",
CLSID_PortDMus,
CLSID_MiniportDriverDMusUART,
NULL,
pAdapterCommon->GetInterruptSync(),
resourceListUart,
IID_IPortDMus,
NULL, // interface to port not needed
NULL ); // not physically connected to anything
}
resourceListUart->Release();
}
//
// Establish physical connections between filters as shown.
//
// +------+ +------+
// | Wave | | Topo |
// Capture <---|0 1|<===|6 2|<--- CD
// | | | |
// Render --->|2 3|===>|0 3|<--- Line In
// +------+ | |
// +------+ | 4|<--- Mic
// | FM | | |
// MIDI --->|0 1|===>|1 5|---> Line Out
// +------+ +------+
//
if (unknownTopology)
{
DWORD version = DeterminePlatform((PPORTTOPOLOGY)unknownTopology);
_DbgPrintF(DEBUGLVL_VERBOSE,("Detected platform version 0x%02X",version));
if (unknownWave)
{
// register wave <=> topology connections
PcRegisterPhysicalConnection( (PDEVICE_OBJECT)DeviceObject,
unknownTopology,
6,
unknownWave,
1 );
PcRegisterPhysicalConnection( (PDEVICE_OBJECT)DeviceObject,
unknownWave,
3,
unknownTopology,
0 );
}
if (unknownFmSynth)
{
// register fmsynth <=> topology connection
PcRegisterPhysicalConnection( (PDEVICE_OBJECT)DeviceObject,
unknownFmSynth,
1,
unknownTopology,
1 );
}
}
//
// Release the adapter common object. It either has other references,
// or we need to delete it anyway.
//
if (pAdapterCommon)
{
pAdapterCommon->Release();
}
//
// Release the unknowns.
//
if (unknownTopology)
{
unknownTopology->Release();
}
if (unknownWave)
{
unknownWave->Release();
}
if (unknownWaveTable)
{
unknownWaveTable->Release();
}
if (unknownFmSynth)
{
unknownFmSynth->Release();
}
}
return ntStatus;
}
/*****************************************************************************
* AssignResources()
*****************************************************************************
* This function assigns the list of resources to the various functions on
* the card. This code is specific to the adapter. All the non-NULL resource
* lists handed back must be released by the caller.
*/
NTSTATUS
AssignResources
(
IN PRESOURCELIST ResourceList, // All resources.
OUT PRESOURCELIST * ResourceListWave, // Wave resources.
OUT PRESOURCELIST * ResourceListWaveTable, // Wave table resources.
OUT PRESOURCELIST * ResourceListFmSynth, // FM synth resources.
OUT PRESOURCELIST * ResourceListUart, // Uart resources.
OUT PRESOURCELIST * ResourceListAdapter // For the adapter
)
{
PAGED_CODE();
BOOLEAN detectedWaveTable = FALSE;
BOOLEAN detectedUart = FALSE;
BOOLEAN detectedFmSynth = FALSE;
//
// Get counts for the types of resources.
//
ULONG countIO = ResourceList->NumberOfPorts();
ULONG countIRQ = ResourceList->NumberOfInterrupts();
ULONG countDMA = ResourceList->NumberOfDmas();
//
// Determine the type of card based on port resources.
// TODO: Detect wave table.
//
NTSTATUS ntStatus = STATUS_SUCCESS;
switch (countIO)
{
case 1:
//
// No FM synth or UART.
//
if ( (ResourceList->FindTranslatedPort(0)->u.Port.Length < 16)
|| (countIRQ < 1)
|| (countDMA < 1)
)
{
ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR;
}
break;
case 2:
//
// MPU-401 or FM synth, not both.
//
if ( (ResourceList->FindTranslatedPort(0)->u.Port.Length < 16)
|| (countIRQ < 1)
|| (countDMA < 1)
)
{
ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR;
}
else
{
//
// Length of second port indicates which function.
//
switch (ResourceList->FindTranslatedPort(1)->u.Port.Length)
{
case 2:
detectedUart = TRUE;
break;
case 4:
detectedFmSynth = TRUE;
break;
default:
ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR;
break;
}
}
break;
case 3:
//
// Both MPU-401 and FM synth.
//
if ( (ResourceList->FindTranslatedPort(0)->u.Port.Length < 16)
|| (ResourceList->FindTranslatedPort(1)->u.Port.Length != 2)
|| (ResourceList->FindTranslatedPort(2)->u.Port.Length != 4)
|| (countIRQ < 1)
|| (countDMA < 1)
)
{
ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR;
}
else
{
detectedUart = TRUE;
detectedFmSynth = TRUE;
}
break;
default:
ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR;
break;
}
//
// Build the resource list for the SB wave I/O.
//
*ResourceListWave = NULL;
if (NT_SUCCESS(ntStatus))
{
ntStatus =
PcNewResourceSublist
(
ResourceListWave,
NULL,
PagedPool,
ResourceList,
countDMA + countIRQ + 1
);
if (NT_SUCCESS(ntStatus))
{
ULONG i;
//
// Add the base address
//
ntStatus = (*ResourceListWave)->
AddPortFromParent(ResourceList,0);
//
// Add the DMA channel(s).
//
if (NT_SUCCESS(ntStatus))
{
for (i = 0; i < countDMA; i++)
{
ntStatus = (*ResourceListWave)->
AddDmaFromParent(ResourceList,i);
}
}
//
// Add the IRQ lines.
//
if (NT_SUCCESS(ntStatus))
{
for (i = 0; i < countIRQ; i++)
{
SUCCEEDS((*ResourceListWave)->
AddInterruptFromParent(ResourceList,i));
}
}
}
}
//
// Build list of resources for wave table.
//
*ResourceListWaveTable = NULL;
if (NT_SUCCESS(ntStatus) && detectedWaveTable)
{
//
// TODO: Assign wave table resources.
//
}
//
// Build list of resources for UART.
//
*ResourceListUart = NULL;
if (NT_SUCCESS(ntStatus) && detectedUart)
{
ntStatus =
PcNewResourceSublist
(
ResourceListUart,
NULL,
PagedPool,
ResourceList,
2
);
if (NT_SUCCESS(ntStatus))
{
ntStatus = (*ResourceListUart)->
AddPortFromParent(ResourceList,1);
if (NT_SUCCESS(ntStatus))
{
ntStatus = (*ResourceListUart)->
AddInterruptFromParent(ResourceList,0);
}
}
}
//
// Build list of resources for FM synth.
//
*ResourceListFmSynth = NULL;
if (NT_SUCCESS(ntStatus) && detectedFmSynth)
{
ntStatus =
PcNewResourceSublist
(
ResourceListFmSynth,
NULL,
PagedPool,
ResourceList,
1
);
if (NT_SUCCESS(ntStatus))
{
ntStatus = (*ResourceListFmSynth)->
AddPortFromParent(ResourceList,detectedUart ? 2 : 1);
}
}
//
// Build list of resources for the adapter.
//
*ResourceListAdapter = NULL;
if (NT_SUCCESS(ntStatus))
{
ntStatus =
PcNewResourceSublist
(
ResourceListAdapter,
NULL,
PagedPool,
ResourceList,
3
);
if (NT_SUCCESS(ntStatus))
{
//
// The interrupt to share.
//
ntStatus = (*ResourceListAdapter)->
AddInterruptFromParent(ResourceList,0);
//
// The base IO port (to tell who's interrupt it is)
//
if (NT_SUCCESS(ntStatus))
{
ntStatus = (*ResourceListAdapter)->
AddPortFromParent(ResourceList,0);
}
if (detectedUart && NT_SUCCESS(ntStatus))
{
//
// The Uart port
//
ntStatus = (*ResourceListAdapter)->
AddPortFromParent(ResourceList,1);
}
}
}
//
// Clean up if failure occurred.
//
if (! NT_SUCCESS(ntStatus))
{
if (*ResourceListWave)
{
(*ResourceListWave)->Release();
*ResourceListWave = NULL;
}
if (*ResourceListWaveTable)
{
(*ResourceListWaveTable)->Release();
*ResourceListWaveTable = NULL;
}
if (*ResourceListUart)
{
(*ResourceListUart)->Release();
*ResourceListUart = NULL;
}
if (*ResourceListFmSynth)
{
(*ResourceListFmSynth)->Release();
*ResourceListFmSynth = NULL;
}
if(*ResourceListAdapter)
{
(*ResourceListAdapter)->Release();
*ResourceListAdapter = NULL;
}
}
return ntStatus;
}
#ifdef DO_RESOURCE_FILTERING
/*****************************************************************************
* AdapterDispatchPnp()
*****************************************************************************
* Supplying your PnP resource filtering needs.
*/
extern "C"
NTSTATUS
AdapterDispatchPnp
(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PAGED_CODE();
ASSERT(pDeviceObject);
ASSERT(pIrp);
NTSTATUS ntStatus = STATUS_SUCCESS;
PIO_STACK_LOCATION pIrpStack =
IoGetCurrentIrpStackLocation(pIrp);
if( pIrpStack->MinorFunction == IRP_MN_FILTER_RESOURCE_REQUIREMENTS )
{
//
// Do your resource requirements filtering here!!
//
_DbgPrintF(DEBUGLVL_VERBOSE,("[AdapterDispatchPnp] - IRP_MN_FILTER_RESOURCE_REQUIREMENTS"));
// set the return status
pIrp->IoStatus.Status = ntStatus;
}
//
// Pass the IRPs on to PortCls
//
ntStatus = PcDispatchIrp( pDeviceObject,
pIrp );
return ntStatus;
}
#endif
/*****************************************************************************
* DeterminePlatform()
*****************************************************************************
* Figure out which WDM platform we are currently running on.
* Note: the Port parameter could be WAVECYCLIC, WAVEPCI, DMUS or MIDI instead.
*
* TODO: Make this work on old DDK.
*
*/
DWORD DeterminePlatform(PPORTTOPOLOGY Port)
{
PAGED_CODE();
ASSERT(Port);
//
// The generally accepted way of determining audio stack vintage:
//
PPORTCLSVERSION pPortClsVersion;
PDRMPORT pDrmPort;
PPORTEVENTS pPortEvents;
DWORD dwVersion;
(void) Port->QueryInterface( IID_IPortClsVersion, (PVOID *) &pPortClsVersion);
(void) Port->QueryInterface( IID_IDrmPort, (PVOID *) &pDrmPort);
(void) Port->QueryInterface( IID_IPortEvents, (PVOID *) &pPortEvents);
//
// Try for the exact release (Win98SE QFE3, WinME QFE, Win2KSP2, WinXP, or later).
//
if (pPortClsVersion)
{
dwVersion = pPortClsVersion->GetVersion();
pPortClsVersion->Release();
}
//
// Try for WinME
//
else if (pDrmPort)
{
dwVersion = kVersionWinME;
ASSERT(IoIsWdmVersionAvailable(0x01,0x05));
//
// TODO: Look for registry entries that denote WinME QFEs
// HKLM\Software\Microsoft\Windows\CurrentVersion\Setup\Updates\..., etc.
//
}
//
// Try for Win2K family.
// Note that SP1 contains no real audio stack changes,
// while SP2 contains non-PCM support and other fixes.
//
else if (IoIsWdmVersionAvailable(0x01,0x10))
{
dwVersion = kVersionWin2K;
//
// TODO: Detect whether SP1 or earlier.
//
}
//
// Must be Win98 or Win98SE.
// IPortEvents was new in Win98SE.
//
else if (pPortEvents)
{
dwVersion = kVersionWin98SE; // or older QFEs
//
// TODO: Look for registry entries that denote older Win98SE QFEs
// HKLM\Software\Microsoft\Windows\CurrentVersion\Setup\Updates\W98.SE\UPD\269601, etc.
//
}
//
// Process of elimination tells us it is Win98.
//
else
{
dwVersion = kVersionWin98;
//
// TODO: Look for registry entries that denote older Win98 QFEs
// HKLM\Software\Microsoft\Windows\CurrentVersion\Setup\Updates\..., etc.
//
}
// dwVersion contains enum with version of audio stack
return dwVersion;
}
#pragma code_seg()
/*****************************************************************************
* _purecall()
*****************************************************************************
* The C++ compiler loves me.
* TODO: Figure out how to put this into portcls.sys
*/
int __cdecl
_purecall( void )
{
ASSERT( !"Pure virutal function called" );
return 0;
}