mirror of
https://github.com/reactos/reactos.git
synced 2025-08-03 21:45:41 +00:00
- Move IopInitialize/StartDevice to PnP Manager
- I/O Packet APIs fixes Part 1: - Clear the current IRP before parsing the Device Queues - Respect Cancelable parameter in IoStartNextPacket(ByKey) instead of ignoring it (acquire the cancel lock when it's requested) - Raise IRQL to DISPATCH_LEVEL in IoStartPacket instead of expecting the caller to do it and crashing. Also only use Cancel Lock if a Cancel Function was specified. - Actually handle the case where the IRP Was cancelled right after insert and the Cancel Routine has to be called. svn path=/trunk/; revision=22744
This commit is contained in:
parent
036139907e
commit
51989fa847
2 changed files with 226 additions and 206 deletions
|
@ -82,111 +82,6 @@ IoShutdownRegisteredDevices(VOID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NTSTATUS
|
|
||||||
FASTCALL
|
|
||||||
IopInitializeDevice(PDEVICE_NODE DeviceNode,
|
|
||||||
PDRIVER_OBJECT DriverObject)
|
|
||||||
{
|
|
||||||
PDEVICE_OBJECT Fdo;
|
|
||||||
NTSTATUS Status;
|
|
||||||
BOOLEAN IsPnpDriver = FALSE;
|
|
||||||
|
|
||||||
if (DriverObject->DriverExtension->AddDevice)
|
|
||||||
{
|
|
||||||
/* This is a Plug and Play driver */
|
|
||||||
DPRINT("Plug and Play driver found\n");
|
|
||||||
|
|
||||||
ASSERT(DeviceNode->PhysicalDeviceObject);
|
|
||||||
|
|
||||||
DPRINT("Calling driver AddDevice entrypoint at %08lx\n",
|
|
||||||
DriverObject->DriverExtension->AddDevice);
|
|
||||||
|
|
||||||
IsPnpDriver = !IopDeviceNodeHasFlag(DeviceNode, DNF_LEGACY_DRIVER);
|
|
||||||
Status = DriverObject->DriverExtension->AddDevice(
|
|
||||||
DriverObject, IsPnpDriver ? DeviceNode->PhysicalDeviceObject : NULL);
|
|
||||||
|
|
||||||
if (!NT_SUCCESS(Status))
|
|
||||||
{
|
|
||||||
return Status;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsPnpDriver)
|
|
||||||
{
|
|
||||||
Fdo = IoGetAttachedDeviceReference(DeviceNode->PhysicalDeviceObject);
|
|
||||||
|
|
||||||
if (Fdo == DeviceNode->PhysicalDeviceObject)
|
|
||||||
{
|
|
||||||
/* FIXME: What do we do? Unload the driver or just disable the device? */
|
|
||||||
DbgPrint("An FDO was not attached\n");
|
|
||||||
IopDeviceNodeSetFlag(DeviceNode, DNF_DISABLED);
|
|
||||||
return STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Fdo->DeviceType == FILE_DEVICE_ACPI)
|
|
||||||
{
|
|
||||||
static BOOLEAN SystemPowerDeviceNodeCreated = FALSE;
|
|
||||||
|
|
||||||
/* There can be only one system power device */
|
|
||||||
if (!SystemPowerDeviceNodeCreated)
|
|
||||||
{
|
|
||||||
PopSystemPowerDeviceNode = DeviceNode;
|
|
||||||
SystemPowerDeviceNodeCreated = TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ObDereferenceObject(Fdo);
|
|
||||||
}
|
|
||||||
|
|
||||||
IopDeviceNodeSetFlag(DeviceNode, DNF_ADDED);
|
|
||||||
IopDeviceNodeSetFlag(DeviceNode, DNF_NEED_ENUMERATION_ONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
return STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
NTSTATUS
|
|
||||||
IopStartDevice(
|
|
||||||
PDEVICE_NODE DeviceNode)
|
|
||||||
{
|
|
||||||
IO_STATUS_BLOCK IoStatusBlock;
|
|
||||||
IO_STACK_LOCATION Stack;
|
|
||||||
PDEVICE_OBJECT Fdo;
|
|
||||||
NTSTATUS Status;
|
|
||||||
|
|
||||||
DPRINT("Sending IRP_MN_START_DEVICE to driver\n");
|
|
||||||
|
|
||||||
Fdo = IoGetAttachedDeviceReference(DeviceNode->PhysicalDeviceObject);
|
|
||||||
Stack.Parameters.StartDevice.AllocatedResources = DeviceNode->ResourceList;
|
|
||||||
Stack.Parameters.StartDevice.AllocatedResourcesTranslated = DeviceNode->ResourceListTranslated;
|
|
||||||
|
|
||||||
Status = IopInitiatePnpIrp(
|
|
||||||
Fdo,
|
|
||||||
&IoStatusBlock,
|
|
||||||
IRP_MN_START_DEVICE,
|
|
||||||
&Stack);
|
|
||||||
|
|
||||||
if (!NT_SUCCESS(Status))
|
|
||||||
{
|
|
||||||
DPRINT("IopInitiatePnpIrp() failed\n");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (IopDeviceNodeHasFlag(DeviceNode, DNF_NEED_ENUMERATION_ONLY))
|
|
||||||
{
|
|
||||||
DPRINT("Device needs enumeration, invalidating bus relations\n");
|
|
||||||
Status = IopInvalidateDeviceRelations(DeviceNode, BusRelations);
|
|
||||||
IopDeviceNodeClearFlag(DeviceNode, DNF_NEED_ENUMERATION_ONLY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ObDereferenceObject(Fdo);
|
|
||||||
|
|
||||||
if (NT_SUCCESS(Status))
|
|
||||||
DeviceNode->Flags |= DN_STARTED;
|
|
||||||
|
|
||||||
return Status;
|
|
||||||
}
|
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTAPI
|
NTAPI
|
||||||
IopGetDeviceObjectPointer(IN PUNICODE_STRING ObjectName,
|
IopGetDeviceObjectPointer(IN PUNICODE_STRING ObjectName,
|
||||||
|
@ -1247,137 +1142,157 @@ IoSetStartIoAttributes(IN PDEVICE_OBJECT DeviceObject,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @implemented
|
* @implemented
|
||||||
*
|
|
||||||
* FUNCTION: Dequeues the next packet from the given device object's
|
|
||||||
* associated device queue according to a specified sort-key value and calls
|
|
||||||
* the drivers StartIo routine with that IRP
|
|
||||||
* ARGUMENTS:
|
|
||||||
* DeviceObject = Device object for which the irp is to dequeued
|
|
||||||
* Cancelable = True if IRPs in the key can be canceled
|
|
||||||
* Key = Sort key specifing which entry to remove from the queue
|
|
||||||
*/
|
*/
|
||||||
VOID
|
VOID
|
||||||
STDCALL
|
NTAPI
|
||||||
IoStartNextPacketByKey(PDEVICE_OBJECT DeviceObject,
|
IoStartNextPacketByKey(IN PDEVICE_OBJECT DeviceObject,
|
||||||
BOOLEAN Cancelable,
|
IN BOOLEAN Cancelable,
|
||||||
ULONG Key)
|
IN ULONG Key)
|
||||||
{
|
{
|
||||||
PKDEVICE_QUEUE_ENTRY entry;
|
PKDEVICE_QUEUE_ENTRY Entry;
|
||||||
PIRP Irp;
|
PIRP Irp;
|
||||||
|
KIRQL OldIrql;
|
||||||
|
|
||||||
entry = KeRemoveByKeyDeviceQueue(&DeviceObject->DeviceQueue,
|
/* Acquire the cancel lock if this is cancelable */
|
||||||
Key);
|
if (Cancelable) IoAcquireCancelSpinLock(&OldIrql);
|
||||||
|
|
||||||
if (entry != NULL)
|
/* Clear the current IRP */
|
||||||
{
|
DeviceObject->CurrentIrp = NULL;
|
||||||
Irp = CONTAINING_RECORD(entry,
|
|
||||||
IRP,
|
/* Remove an entry from the queue */
|
||||||
Tail.Overlay.DeviceQueueEntry);
|
Entry = KeRemoveByKeyDeviceQueue(&DeviceObject->DeviceQueue, Key);
|
||||||
|
if (Entry)
|
||||||
|
{
|
||||||
|
/* Get the IRP and set it */
|
||||||
|
Irp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.DeviceQueueEntry);
|
||||||
DeviceObject->CurrentIrp = Irp;
|
DeviceObject->CurrentIrp = Irp;
|
||||||
DPRINT("Next irp is 0x%p\n", Irp);
|
|
||||||
DeviceObject->DriverObject->DriverStartIo(DeviceObject, Irp);
|
/* Release the cancel lock if we had acquired it */
|
||||||
}
|
if (Cancelable) IoReleaseCancelSpinLock(OldIrql);
|
||||||
else
|
|
||||||
{
|
/* Call the Start I/O Routine */
|
||||||
DPRINT("No next irp\n");
|
DeviceObject->DriverObject->DriverStartIo(DeviceObject, Irp);
|
||||||
DeviceObject->CurrentIrp = NULL;
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
/* Otherwise, release the cancel lock if we had acquired it */
|
||||||
|
if (Cancelable) IoReleaseCancelSpinLock(OldIrql);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @implemented
|
* @implemented
|
||||||
*
|
|
||||||
* FUNCTION: Removes the next packet from the device's queue and calls
|
|
||||||
* the driver's StartIO
|
|
||||||
* ARGUMENTS:
|
|
||||||
* DeviceObject = Device
|
|
||||||
* Cancelable = True if irps in the queue can be canceled
|
|
||||||
*/
|
*/
|
||||||
VOID
|
VOID
|
||||||
STDCALL
|
NTAPI
|
||||||
IoStartNextPacket(PDEVICE_OBJECT DeviceObject,
|
IoStartNextPacket(IN PDEVICE_OBJECT DeviceObject,
|
||||||
BOOLEAN Cancelable)
|
IN BOOLEAN Cancelable)
|
||||||
{
|
{
|
||||||
PKDEVICE_QUEUE_ENTRY entry;
|
PKDEVICE_QUEUE_ENTRY Entry;
|
||||||
PIRP Irp;
|
PIRP Irp;
|
||||||
|
KIRQL OldIrql;
|
||||||
|
|
||||||
DPRINT("IoStartNextPacket(DeviceObject 0x%p, Cancelable %d)\n",
|
/* Acquire the cancel lock if this is cancelable */
|
||||||
DeviceObject, Cancelable);
|
if (Cancelable) IoAcquireCancelSpinLock(&OldIrql);
|
||||||
|
|
||||||
entry = KeRemoveDeviceQueue(&DeviceObject->DeviceQueue);
|
/* Clear the current IRP */
|
||||||
|
DeviceObject->CurrentIrp = NULL;
|
||||||
|
|
||||||
if (entry!=NULL)
|
/* Remove an entry from the queue */
|
||||||
{
|
Entry = KeRemoveDeviceQueue(&DeviceObject->DeviceQueue);
|
||||||
Irp = CONTAINING_RECORD(entry,IRP,Tail.Overlay.DeviceQueueEntry);
|
if (Entry)
|
||||||
|
{
|
||||||
|
/* Get the IRP and set it */
|
||||||
|
Irp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.DeviceQueueEntry);
|
||||||
DeviceObject->CurrentIrp = Irp;
|
DeviceObject->CurrentIrp = Irp;
|
||||||
DeviceObject->DriverObject->DriverStartIo(DeviceObject,Irp);
|
|
||||||
}
|
/* Call the Start I/O Routine */
|
||||||
else
|
DeviceObject->DriverObject->DriverStartIo(DeviceObject, Irp);
|
||||||
{
|
}
|
||||||
DeviceObject->CurrentIrp = NULL;
|
else
|
||||||
}
|
{
|
||||||
|
/* Otherwise, release the cancel lock if we had acquired it */
|
||||||
|
if (Cancelable) IoReleaseCancelSpinLock(OldIrql);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @implemented
|
* @implemented
|
||||||
*
|
|
||||||
* FUNCTION: Either call the device's StartIO routine with the packet or,
|
|
||||||
* if the device is busy, queue it.
|
|
||||||
* ARGUMENTS:
|
|
||||||
* DeviceObject = Device to start the packet on
|
|
||||||
* Irp = Irp to queue
|
|
||||||
* Key = Where to insert the irp
|
|
||||||
* If zero then insert in the tail of the queue
|
|
||||||
* CancelFunction = Optional function to cancel the irqp
|
|
||||||
*/
|
*/
|
||||||
VOID
|
VOID
|
||||||
STDCALL
|
NTAPI
|
||||||
IoStartPacket(PDEVICE_OBJECT DeviceObject,
|
IoStartPacket(IN PDEVICE_OBJECT DeviceObject,
|
||||||
PIRP Irp,
|
IN PIRP Irp,
|
||||||
PULONG Key,
|
IN PULONG Key,
|
||||||
PDRIVER_CANCEL CancelFunction)
|
IN PDRIVER_CANCEL CancelFunction)
|
||||||
{
|
{
|
||||||
BOOLEAN stat;
|
BOOLEAN Stat;
|
||||||
KIRQL oldirql;
|
KIRQL OldIrql, CancelIrql;
|
||||||
|
|
||||||
DPRINT("IoStartPacket(Irp 0x%p)\n", Irp);
|
/* Raise to dispatch level */
|
||||||
|
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
|
||||||
|
|
||||||
ASSERT_IRQL(DISPATCH_LEVEL);
|
/* Check if we should acquire the cancel lock */
|
||||||
|
if (CancelFunction)
|
||||||
|
{
|
||||||
|
/* Acquire and set it */
|
||||||
|
IoAcquireCancelSpinLock(&CancelIrql);
|
||||||
|
Irp->CancelRoutine = CancelFunction;
|
||||||
|
}
|
||||||
|
|
||||||
IoAcquireCancelSpinLock(&oldirql);
|
/* Check if we have a key */
|
||||||
|
if (Key)
|
||||||
|
{
|
||||||
|
/* Insert by key */
|
||||||
|
Stat = KeInsertByKeyDeviceQueue(&DeviceObject->DeviceQueue,
|
||||||
|
&Irp->Tail.Overlay.DeviceQueueEntry,
|
||||||
|
*Key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Insert without a key */
|
||||||
|
Stat = KeInsertDeviceQueue(&DeviceObject->DeviceQueue,
|
||||||
|
&Irp->Tail.Overlay.DeviceQueueEntry);
|
||||||
|
}
|
||||||
|
|
||||||
if (CancelFunction != NULL)
|
/* Check if this was a first insert */
|
||||||
{
|
if (!Stat)
|
||||||
Irp->CancelRoutine = CancelFunction;
|
{
|
||||||
}
|
/* Set the IRP */
|
||||||
|
|
||||||
if (Key!=0)
|
|
||||||
{
|
|
||||||
stat = KeInsertByKeyDeviceQueue(&DeviceObject->DeviceQueue,
|
|
||||||
&Irp->Tail.Overlay.DeviceQueueEntry,
|
|
||||||
*Key);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stat = KeInsertDeviceQueue(&DeviceObject->DeviceQueue,
|
|
||||||
&Irp->Tail.Overlay.DeviceQueueEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!stat)
|
|
||||||
{
|
|
||||||
IoReleaseCancelSpinLock(DISPATCH_LEVEL);
|
|
||||||
DeviceObject->CurrentIrp = Irp;
|
DeviceObject->CurrentIrp = Irp;
|
||||||
DeviceObject->DriverObject->DriverStartIo(DeviceObject,Irp);
|
|
||||||
if (oldirql < DISPATCH_LEVEL)
|
/* Release the cancel lock if we had a cancel function */
|
||||||
{
|
if (CancelFunction) IoReleaseCancelSpinLock(CancelIrql);
|
||||||
KeLowerIrql(oldirql);
|
|
||||||
|
/* Call the Start I/O function */
|
||||||
|
DeviceObject->DriverObject->DriverStartIo(DeviceObject, Irp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* The packet was inserted... check if we have a cancel function */
|
||||||
|
if (CancelFunction)
|
||||||
|
{
|
||||||
|
/* Check if the IRP got cancelled */
|
||||||
|
if (Irp->Cancel)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Set the cancel IRQL, clear the currnet cancel routine and
|
||||||
|
* call ours
|
||||||
|
*/
|
||||||
|
Irp->CancelIrql = CancelIrql;
|
||||||
|
Irp->CancelRoutine = NULL;
|
||||||
|
CancelFunction(DeviceObject, Irp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Otherwise, release the lock */
|
||||||
|
IoReleaseCancelSpinLock(CancelIrql);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
/* Return back to previous IRQL */
|
||||||
IoReleaseCancelSpinLock(oldirql);
|
KeLowerIrql(OldIrql);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* EOF */
|
/* EOF */
|
||||||
|
|
|
@ -44,6 +44,111 @@ IopGetDeviceNode(PDEVICE_OBJECT DeviceObject)
|
||||||
return ((PEXTENDED_DEVOBJ_EXTENSION)DeviceObject->DeviceObjectExtension)->DeviceNode;
|
return ((PEXTENDED_DEVOBJ_EXTENSION)DeviceObject->DeviceObjectExtension)->DeviceNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NTSTATUS
|
||||||
|
FASTCALL
|
||||||
|
IopInitializeDevice(PDEVICE_NODE DeviceNode,
|
||||||
|
PDRIVER_OBJECT DriverObject)
|
||||||
|
{
|
||||||
|
PDEVICE_OBJECT Fdo;
|
||||||
|
NTSTATUS Status;
|
||||||
|
BOOLEAN IsPnpDriver = FALSE;
|
||||||
|
|
||||||
|
if (DriverObject->DriverExtension->AddDevice)
|
||||||
|
{
|
||||||
|
/* This is a Plug and Play driver */
|
||||||
|
DPRINT("Plug and Play driver found\n");
|
||||||
|
|
||||||
|
ASSERT(DeviceNode->PhysicalDeviceObject);
|
||||||
|
|
||||||
|
DPRINT("Calling driver AddDevice entrypoint at %08lx\n",
|
||||||
|
DriverObject->DriverExtension->AddDevice);
|
||||||
|
|
||||||
|
IsPnpDriver = !IopDeviceNodeHasFlag(DeviceNode, DNF_LEGACY_DRIVER);
|
||||||
|
Status = DriverObject->DriverExtension->AddDevice(
|
||||||
|
DriverObject, IsPnpDriver ? DeviceNode->PhysicalDeviceObject : NULL);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsPnpDriver)
|
||||||
|
{
|
||||||
|
Fdo = IoGetAttachedDeviceReference(DeviceNode->PhysicalDeviceObject);
|
||||||
|
|
||||||
|
if (Fdo == DeviceNode->PhysicalDeviceObject)
|
||||||
|
{
|
||||||
|
/* FIXME: What do we do? Unload the driver or just disable the device? */
|
||||||
|
DbgPrint("An FDO was not attached\n");
|
||||||
|
IopDeviceNodeSetFlag(DeviceNode, DNF_DISABLED);
|
||||||
|
return STATUS_UNSUCCESSFUL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Fdo->DeviceType == FILE_DEVICE_ACPI)
|
||||||
|
{
|
||||||
|
static BOOLEAN SystemPowerDeviceNodeCreated = FALSE;
|
||||||
|
|
||||||
|
/* There can be only one system power device */
|
||||||
|
if (!SystemPowerDeviceNodeCreated)
|
||||||
|
{
|
||||||
|
PopSystemPowerDeviceNode = DeviceNode;
|
||||||
|
SystemPowerDeviceNodeCreated = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ObDereferenceObject(Fdo);
|
||||||
|
}
|
||||||
|
|
||||||
|
IopDeviceNodeSetFlag(DeviceNode, DNF_ADDED);
|
||||||
|
IopDeviceNodeSetFlag(DeviceNode, DNF_NEED_ENUMERATION_ONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS
|
||||||
|
IopStartDevice(
|
||||||
|
PDEVICE_NODE DeviceNode)
|
||||||
|
{
|
||||||
|
IO_STATUS_BLOCK IoStatusBlock;
|
||||||
|
IO_STACK_LOCATION Stack;
|
||||||
|
PDEVICE_OBJECT Fdo;
|
||||||
|
NTSTATUS Status;
|
||||||
|
|
||||||
|
DPRINT("Sending IRP_MN_START_DEVICE to driver\n");
|
||||||
|
|
||||||
|
Fdo = IoGetAttachedDeviceReference(DeviceNode->PhysicalDeviceObject);
|
||||||
|
Stack.Parameters.StartDevice.AllocatedResources = DeviceNode->ResourceList;
|
||||||
|
Stack.Parameters.StartDevice.AllocatedResourcesTranslated = DeviceNode->ResourceListTranslated;
|
||||||
|
|
||||||
|
Status = IopInitiatePnpIrp(
|
||||||
|
Fdo,
|
||||||
|
&IoStatusBlock,
|
||||||
|
IRP_MN_START_DEVICE,
|
||||||
|
&Stack);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(Status))
|
||||||
|
{
|
||||||
|
DPRINT("IopInitiatePnpIrp() failed\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (IopDeviceNodeHasFlag(DeviceNode, DNF_NEED_ENUMERATION_ONLY))
|
||||||
|
{
|
||||||
|
DPRINT("Device needs enumeration, invalidating bus relations\n");
|
||||||
|
Status = IopInvalidateDeviceRelations(DeviceNode, BusRelations);
|
||||||
|
IopDeviceNodeClearFlag(DeviceNode, DNF_NEED_ENUMERATION_ONLY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ObDereferenceObject(Fdo);
|
||||||
|
|
||||||
|
if (NT_SUCCESS(Status))
|
||||||
|
DeviceNode->Flags |= DN_STARTED;
|
||||||
|
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
STDCALL
|
STDCALL
|
||||||
IopQueryDeviceCapabilities(PDEVICE_NODE DeviceNode,
|
IopQueryDeviceCapabilities(PDEVICE_NODE DeviceNode,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue