2024-02-13 19:08:38 +01:00
|
|
|
#include "hw.h"
|
|
|
|
|
2024-05-15 11:48:09 +02:00
|
|
|
#include "crypt.h"
|
|
|
|
#include "imports.h"
|
2024-07-22 12:43:09 +02:00
|
|
|
#include "lib/stdlib.h"
|
2024-08-04 08:30:31 +02:00
|
|
|
#include "modules.h"
|
2024-07-22 12:43:09 +02:00
|
|
|
|
2024-02-13 19:08:38 +01:00
|
|
|
#define PCI_VENDOR_ID_OFFSET 0x00
|
2024-02-14 17:16:27 +01:00
|
|
|
#define PCI_DEVICE_ID_OFFSET 0x02
|
|
|
|
|
|
|
|
#define FLAGGED_DEVICE_ID_COUNT 2
|
|
|
|
|
|
|
|
USHORT FLAGGED_DEVICE_IDS[FLAGGED_DEVICE_ID_COUNT] = {
|
|
|
|
0x0666, // default PCIe Squirrel DeviceID (used by PCI Leech)
|
|
|
|
0xffff};
|
|
|
|
|
2024-08-01 06:21:53 +02:00
|
|
|
typedef NTSTATUS (*PCI_DEVICE_CALLBACK)(
|
|
|
|
_In_ PDEVICE_OBJECT DeviceObject, _In_opt_ PVOID Context);
|
2024-02-13 19:08:38 +01:00
|
|
|
|
|
|
|
/*
|
2024-04-13 06:40:51 +02:00
|
|
|
* Every PCI device has a set of registers commonly referred to as the PCI
|
|
|
|
* configuration space. In modern PCI-e devices an extended configuration space
|
|
|
|
* was implemented. These configuration spaces are mapped into main memory and
|
|
|
|
* this allows us to read/write to the registers.
|
2024-02-13 19:08:38 +01:00
|
|
|
*
|
2024-04-13 06:40:51 +02:00
|
|
|
* The configuration space consists of a standard header, containing information
|
|
|
|
* such as the DeviceID, VendorID, Status and so on. Below is the header schema
|
|
|
|
* including offsets.
|
2024-02-13 19:08:38 +01:00
|
|
|
*
|
|
|
|
* | Offset 0x00: Header Type
|
|
|
|
* | Offset 0x01: Multi-Function Device Indicator
|
|
|
|
* | Offset 0x02: Device ID (Low Byte)
|
|
|
|
* | Offset 0x03: Device ID (High Byte)
|
|
|
|
* | Offset 0x04: Status Register (16 bits)
|
|
|
|
* | Offset 0x06: Command Register (16 bits)
|
|
|
|
* | Offset 0x08: Class Code
|
|
|
|
* | Offset 0x09: Subclass Code
|
|
|
|
* | Offset 0x0A: Prog IF (Programming Interface)
|
|
|
|
* | Offset 0x0B: Revision ID
|
|
|
|
* | Offset 0x0C: BIST (Built-in Self-Test)
|
|
|
|
* | Offset 0x0D: Header Type (Secondary)
|
|
|
|
* | Offset 0x0E: Latency Timer
|
|
|
|
* | Offset 0x0F: Cache Line Size
|
|
|
|
* | Offset 0x10: Base Address Register 0 (BAR0) - 32 bits
|
|
|
|
* | Offset 0x14: Base Address Register 1 (BAR1) - 32 bits
|
|
|
|
* | Offset 0x18: Base Address Register 2 (BAR2) - 32 bits
|
|
|
|
* | Offset 0x1C: Base Address Register 3 (BAR3) - 32 bits
|
|
|
|
* | Offset 0x20: Base Address Register 4 (BAR4) - 32 bits
|
|
|
|
* | Offset 0x24: Base Address Register 5 (BAR5) - 32 bits
|
|
|
|
* | Offset 0x28: Cardbus CIS Pointer (for Cardbus bridges)
|
|
|
|
* | Offset 0x2C: Subsystem Vendor ID
|
|
|
|
* | Offset 0x2E: Subsystem ID
|
|
|
|
* | Offset 0x30: Expansion ROM Base Address
|
|
|
|
* | Offset 0x34: Reserved
|
|
|
|
* | Offset 0x38: Reserved
|
|
|
|
* | Offset 0x3C: Max_Lat (Maximum Latency)
|
|
|
|
* | Offset 0x3D: Min_Gnt (Minimum Grant)
|
|
|
|
* | Offset 0x3E: Interrupt Pin
|
|
|
|
* | Offset 0x3F: Interrupt Line
|
|
|
|
*
|
2024-04-13 06:40:51 +02:00
|
|
|
* We can use this to then query important information from PCI devices within
|
|
|
|
* the device tree. To keep up with modern windows kernel programming, we can
|
|
|
|
* make use of the IRP_MN_READ_CONFIG code, which as the name suggests, reads
|
|
|
|
* from a PCI devices configuration space.
|
2024-02-13 19:08:38 +01:00
|
|
|
*/
|
|
|
|
STATIC
|
|
|
|
NTSTATUS
|
2024-08-01 06:21:53 +02:00
|
|
|
QueryPciDeviceConfigurationSpace(
|
|
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
|
|
_In_ UINT32 Offset,
|
|
|
|
_Out_opt_ PVOID Buffer,
|
|
|
|
_In_ UINT32 BufferLength)
|
2024-02-13 19:08:38 +01:00
|
|
|
{
|
2024-08-01 06:21:53 +02:00
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
|
|
KEVENT event = {0};
|
|
|
|
IO_STATUS_BLOCK io = {0};
|
|
|
|
PIRP irp = NULL;
|
2024-05-15 11:48:09 +02:00
|
|
|
PIO_STACK_LOCATION packet = NULL;
|
2024-04-13 10:23:14 +02:00
|
|
|
|
|
|
|
if (BufferLength == 0)
|
|
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
|
|
|
|
|
|
KeInitializeEvent(&event, NotificationEvent, FALSE);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* we dont need to free this IRP as the IO manager will free it when the
|
|
|
|
* request is completed
|
|
|
|
*/
|
|
|
|
irp = IoBuildSynchronousFsdRequest(
|
2024-08-01 06:21:53 +02:00
|
|
|
IRP_MJ_PNP,
|
|
|
|
DeviceObject,
|
|
|
|
NULL,
|
|
|
|
0,
|
|
|
|
NULL,
|
|
|
|
&event,
|
|
|
|
&io);
|
2024-04-13 10:23:14 +02:00
|
|
|
|
|
|
|
if (!irp) {
|
|
|
|
DEBUG_ERROR("IoBuildSynchronousFsdRequest failed with no status.");
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
}
|
|
|
|
|
2024-08-01 06:21:53 +02:00
|
|
|
packet = IoGetNextIrpStackLocation(irp);
|
2024-05-15 11:48:09 +02:00
|
|
|
packet->MinorFunction = IRP_MN_READ_CONFIG;
|
|
|
|
packet->Parameters.ReadWriteConfig.WhichSpace = PCI_WHICHSPACE_CONFIG;
|
2024-08-01 06:21:53 +02:00
|
|
|
packet->Parameters.ReadWriteConfig.Offset = Offset;
|
|
|
|
packet->Parameters.ReadWriteConfig.Buffer = Buffer;
|
|
|
|
packet->Parameters.ReadWriteConfig.Length = BufferLength;
|
2024-04-13 10:23:14 +02:00
|
|
|
|
|
|
|
status = IoCallDriver(DeviceObject, irp);
|
|
|
|
|
2024-05-11 14:54:58 +02:00
|
|
|
if (status == STATUS_PENDING) {
|
2024-04-13 10:23:14 +02:00
|
|
|
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
|
|
|
|
status = io.Status;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!NT_SUCCESS(status))
|
2024-08-01 06:21:53 +02:00
|
|
|
DEBUG_ERROR(
|
|
|
|
"Failed to read configuration space with status %x",
|
|
|
|
status);
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
return status;
|
2024-02-13 19:08:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* NOTE: Caller is responsible for freeing the array.
|
|
|
|
*/
|
|
|
|
STATIC
|
|
|
|
NTSTATUS
|
2024-08-01 06:21:53 +02:00
|
|
|
EnumerateDriverObjectDeviceObjects(
|
|
|
|
_In_ PDRIVER_OBJECT DriverObject,
|
|
|
|
_Out_ PDEVICE_OBJECT** DeviceObjectArray,
|
|
|
|
_Out_ PUINT32 ArrayEntries)
|
2024-02-13 19:08:38 +01:00
|
|
|
{
|
2024-08-01 06:21:53 +02:00
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
|
|
UINT32 object_count = 0;
|
|
|
|
PDEVICE_OBJECT* buffer = NULL;
|
|
|
|
UINT32 buffer_size = 0;
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
*DeviceObjectArray = NULL;
|
2024-08-01 06:21:53 +02:00
|
|
|
*ArrayEntries = 0;
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
status = IoEnumerateDeviceObjectList(DriverObject, NULL, 0, &object_count);
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
if (status != STATUS_BUFFER_TOO_SMALL) {
|
2024-08-01 06:21:53 +02:00
|
|
|
DEBUG_ERROR(
|
|
|
|
"IoEnumerateDeviceObjectList failed with status %x",
|
|
|
|
status);
|
2024-04-13 10:23:14 +02:00
|
|
|
return status;
|
|
|
|
}
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
buffer_size = object_count * sizeof(UINT64);
|
|
|
|
buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, buffer_size, POOL_TAG_HW);
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
if (!buffer)
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
status = IoEnumerateDeviceObjectList(
|
2024-08-01 06:21:53 +02:00
|
|
|
DriverObject,
|
|
|
|
buffer,
|
|
|
|
buffer_size,
|
|
|
|
&object_count);
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
if (!NT_SUCCESS(status)) {
|
2024-08-01 06:21:53 +02:00
|
|
|
DEBUG_ERROR(
|
|
|
|
"IoEnumerateDeviceObjectList failed with status %x",
|
|
|
|
status);
|
2024-04-13 10:23:14 +02:00
|
|
|
ExFreePoolWithTag(buffer, POOL_TAG_HW);
|
|
|
|
return status;
|
|
|
|
}
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-08-01 06:21:53 +02:00
|
|
|
DEBUG_VERBOSE(
|
|
|
|
"EnumerateDriverObjectDeviceObjects: Object Count: %lx",
|
|
|
|
object_count);
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
*DeviceObjectArray = buffer;
|
2024-08-01 06:21:53 +02:00
|
|
|
*ArrayEntries = object_count;
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
return status;
|
2024-02-13 19:08:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2024-04-13 06:40:51 +02:00
|
|
|
* While this isnt a perfect check to determine whether a DEVICE_OBJECT is
|
|
|
|
* indeed a PDO or FDO, this is Peters preferred method... hence it is now my
|
|
|
|
* preferred method... :smiling_imp:
|
2024-02-13 19:08:38 +01:00
|
|
|
*/
|
|
|
|
STATIC
|
|
|
|
BOOLEAN
|
|
|
|
IsDeviceObjectValidPdo(_In_ PDEVICE_OBJECT DeviceObject)
|
|
|
|
{
|
2024-04-13 10:23:14 +02:00
|
|
|
return DeviceObject->Flags & DO_BUS_ENUMERATED_DEVICE ? TRUE : FALSE;
|
2024-02-13 19:08:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Windows splits DEVICE_OBJECTS up into 2 categories:
|
|
|
|
*
|
|
|
|
* Physical Device Object (PDO)
|
2024-02-13 19:29:19 +01:00
|
|
|
* Functional Device Object (FDO)
|
2024-02-13 19:08:38 +01:00
|
|
|
*
|
2024-04-13 06:40:51 +02:00
|
|
|
* A PDO represents each device that is connected to a physical bus. Each PDO
|
|
|
|
* has an associated DEVICE_NODE. An FDO represents the functionality of the
|
|
|
|
* device. Its how the system interacts with the device objects.
|
2024-02-13 19:08:38 +01:00
|
|
|
*
|
|
|
|
* More information can be found here:
|
|
|
|
* https://learn.microsoft.com/en-gb/windows-hardware/drivers/gettingstarted/device-nodes-and-device-stacks
|
2024-02-13 19:13:56 +01:00
|
|
|
*
|
2024-04-13 06:40:51 +02:00
|
|
|
* A device stack can have multiple PDO's, but can only have one FDO. This means
|
|
|
|
* to access each PCI device on the system, we can enumerate all device objects
|
|
|
|
* given the PCI FDO which is called pci.sys.
|
2024-02-13 19:08:38 +01:00
|
|
|
*/
|
|
|
|
NTSTATUS
|
2024-08-01 06:21:53 +02:00
|
|
|
EnumeratePciDeviceObjects(
|
|
|
|
_In_ PCI_DEVICE_CALLBACK CallbackRoutine, _In_opt_ PVOID Context)
|
2024-02-13 19:08:38 +01:00
|
|
|
{
|
2024-08-01 06:21:53 +02:00
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
|
|
UNICODE_STRING pci = RTL_CONSTANT_STRING(L"\\Driver\\pci");
|
|
|
|
PDRIVER_OBJECT pci_driver_object = NULL;
|
2024-04-13 10:23:14 +02:00
|
|
|
PDEVICE_OBJECT* pci_device_objects = NULL;
|
2024-08-01 06:21:53 +02:00
|
|
|
PDEVICE_OBJECT current_device = NULL;
|
|
|
|
UINT32 pci_device_objects_count = 0;
|
2024-04-13 10:23:14 +02:00
|
|
|
|
|
|
|
status = GetDriverObjectByDriverName(&pci, &pci_driver_object);
|
|
|
|
|
|
|
|
if (!NT_SUCCESS(status)) {
|
2024-08-01 06:21:53 +02:00
|
|
|
DEBUG_ERROR(
|
|
|
|
"GetDriverObjectByDriverName failed with status %x",
|
|
|
|
status);
|
2024-04-13 10:23:14 +02:00
|
|
|
return status;
|
|
|
|
}
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
status = EnumerateDriverObjectDeviceObjects(
|
2024-08-01 06:21:53 +02:00
|
|
|
pci_driver_object,
|
|
|
|
&pci_device_objects,
|
|
|
|
&pci_device_objects_count);
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
if (!NT_SUCCESS(status)) {
|
2024-08-01 06:21:53 +02:00
|
|
|
DEBUG_ERROR(
|
|
|
|
"EnumerateDriverObjectDeviceObjects failed with status %x",
|
|
|
|
status);
|
2024-04-13 10:23:14 +02:00
|
|
|
return status;
|
|
|
|
}
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
for (UINT32 index = 0; index < pci_device_objects_count; index++) {
|
|
|
|
current_device = pci_device_objects[index];
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
/* make sure we have a valid PDO */
|
|
|
|
if (!IsDeviceObjectValidPdo(current_device)) {
|
|
|
|
ObDereferenceObject(current_device);
|
|
|
|
continue;
|
|
|
|
}
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
status = CallbackRoutine(current_device, Context);
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
if (!NT_SUCCESS(status))
|
|
|
|
DEBUG_ERROR(
|
|
|
|
"EnumeratePciDeviceObjects CallbackRoutine failed with status %x",
|
|
|
|
status);
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
ObDereferenceObject(current_device);
|
|
|
|
}
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
if (pci_device_objects)
|
|
|
|
ExFreePoolWithTag(pci_device_objects, POOL_TAG_HW);
|
2024-02-13 19:08:38 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
return status;
|
2024-02-14 17:16:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN
|
|
|
|
IsPciConfigurationSpaceFlagged(_In_ PPCI_COMMON_HEADER Configuration)
|
|
|
|
{
|
2024-04-13 10:23:14 +02:00
|
|
|
for (UINT32 index = 0; index < FLAGGED_DEVICE_ID_COUNT; index++) {
|
|
|
|
if (Configuration->DeviceID == FLAGGED_DEVICE_IDS[index])
|
|
|
|
return TRUE;
|
|
|
|
}
|
2024-02-14 17:16:27 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
return FALSE;
|
2024-02-14 17:16:27 +01:00
|
|
|
}
|
|
|
|
|
2024-05-15 11:48:09 +02:00
|
|
|
STATIC
|
|
|
|
VOID
|
2024-08-01 06:21:53 +02:00
|
|
|
ReportBlacklistedPcieDevice(
|
|
|
|
_In_ PDEVICE_OBJECT DeviceObject, _In_ PPCI_COMMON_HEADER Header)
|
2024-05-15 11:48:09 +02:00
|
|
|
{
|
2024-08-01 06:21:53 +02:00
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
2024-08-04 08:30:31 +02:00
|
|
|
UINT32 len = 0;
|
|
|
|
PBLACKLISTED_PCIE_DEVICE_REPORT report = NULL;
|
2024-05-15 11:48:09 +02:00
|
|
|
|
2024-08-04 08:30:31 +02:00
|
|
|
len = CryptRequestRequiredBufferLength(
|
|
|
|
sizeof(BLACKLISTED_PCIE_DEVICE_REPORT));
|
|
|
|
report = ImpExAllocatePool2(POOL_FLAG_NON_PAGED, len, REPORT_POOL_TAG);
|
2024-05-15 11:48:09 +02:00
|
|
|
|
|
|
|
if (!report)
|
|
|
|
return;
|
|
|
|
|
|
|
|
INIT_REPORT_PACKET(report, REPORT_BLACKLISTED_PCIE_DEVICE, 0);
|
|
|
|
|
|
|
|
report->device_object = (UINT64)DeviceObject;
|
2024-08-01 06:21:53 +02:00
|
|
|
report->device_id = Header->DeviceID;
|
|
|
|
report->vendor_id = Header->VendorID;
|
2024-05-15 11:48:09 +02:00
|
|
|
|
2024-08-04 08:30:31 +02:00
|
|
|
status = CryptEncryptBuffer(report, len);
|
2024-05-15 11:48:09 +02:00
|
|
|
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
DEBUG_ERROR("CryptEncryptBuffer: %lx", status);
|
2024-08-04 08:30:31 +02:00
|
|
|
ImpExFreePoolWithTag(report, len);
|
2024-05-15 11:48:09 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-08-04 08:30:31 +02:00
|
|
|
IrpQueueSchedulePacket(report, len);
|
2024-05-15 11:48:09 +02:00
|
|
|
}
|
|
|
|
|
2024-02-14 17:16:27 +01:00
|
|
|
STATIC
|
|
|
|
NTSTATUS
|
|
|
|
PciDeviceQueryCallback(_In_ PDEVICE_OBJECT DeviceObject, _In_opt_ PVOID Context)
|
|
|
|
{
|
2024-05-11 14:54:58 +02:00
|
|
|
UNREFERENCED_PARAMETER(Context);
|
|
|
|
|
2024-08-01 06:21:53 +02:00
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
2024-04-13 10:23:14 +02:00
|
|
|
PCI_COMMON_HEADER header = {0};
|
2024-02-14 17:16:27 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
status = QueryPciDeviceConfigurationSpace(
|
2024-08-01 06:21:53 +02:00
|
|
|
DeviceObject,
|
|
|
|
PCI_VENDOR_ID_OFFSET,
|
|
|
|
&header,
|
|
|
|
sizeof(PCI_COMMON_HEADER));
|
2024-02-14 17:16:27 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
if (!NT_SUCCESS(status)) {
|
2024-08-01 06:21:53 +02:00
|
|
|
DEBUG_ERROR(
|
|
|
|
"QueryPciDeviceConfigurationSpace failed with status %x",
|
|
|
|
status);
|
2024-02-14 17:16:27 +01:00
|
|
|
return status;
|
2024-04-13 10:23:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (IsPciConfigurationSpaceFlagged(&header)) {
|
2024-08-01 06:21:53 +02:00
|
|
|
DEBUG_VERBOSE(
|
|
|
|
"Flagged DeviceID found. Device: %llx, DeviceId: %lx",
|
|
|
|
(UINT64)DeviceObject,
|
|
|
|
header.DeviceID);
|
2024-06-09 09:22:22 +02:00
|
|
|
ReportBlacklistedPcieDevice(DeviceObject, &header);
|
2024-04-13 10:23:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return status;
|
2024-02-14 17:16:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
|
|
ValidatePciDevices()
|
|
|
|
{
|
2024-04-13 10:23:14 +02:00
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
2024-02-14 17:16:27 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
status = EnumeratePciDeviceObjects(PciDeviceQueryCallback, NULL);
|
2024-02-14 17:16:27 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
if (!NT_SUCCESS(status))
|
|
|
|
DEBUG_ERROR("EnumeratePciDeviceObjects failed with status %x", status);
|
2024-02-14 17:16:27 +01:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
return status;
|
2024-02-13 19:08:38 +01:00
|
|
|
}
|