summaryrefslogtreecommitdiffstats
path: root/lib/win32-kldbg.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/win32-kldbg.c')
-rw-r--r--lib/win32-kldbg.c796
1 files changed, 796 insertions, 0 deletions
diff --git a/lib/win32-kldbg.c b/lib/win32-kldbg.c
new file mode 100644
index 0000000..3890b6b
--- /dev/null
+++ b/lib/win32-kldbg.c
@@ -0,0 +1,796 @@
+/*
+ * The PCI Library -- PCI config space access using Kernel Local Debugging Driver
+ *
+ * Copyright (c) 2022 Pali Rohár <pali@kernel.org>
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <windows.h>
+#include <winioctl.h>
+
+#include <stdio.h> /* for sprintf() */
+#include <string.h> /* for memset() and memcpy() */
+
+#include "internal.h"
+#include "i386-io-windows.h"
+
+#ifndef LOAD_LIBRARY_AS_IMAGE_RESOURCE
+#define LOAD_LIBRARY_AS_IMAGE_RESOURCE 0x20
+#endif
+#ifndef LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE
+#define LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE 0x40
+#endif
+
+#ifndef IOCTL_KLDBG
+#define IOCTL_KLDBG CTL_CODE(FILE_DEVICE_UNKNOWN, 0x1, METHOD_NEITHER, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#endif
+
+#ifndef BUS_DATA_TYPE
+#define BUS_DATA_TYPE LONG
+#endif
+#ifndef PCIConfiguration
+#define PCIConfiguration (BUS_DATA_TYPE)4
+#endif
+
+#ifndef SYSDBG_COMMAND
+#define SYSDBG_COMMAND ULONG
+#endif
+#ifndef SysDbgReadBusData
+#define SysDbgReadBusData (SYSDBG_COMMAND)18
+#endif
+#ifndef SysDbgWriteBusData
+#define SysDbgWriteBusData (SYSDBG_COMMAND)19
+#endif
+
+#ifndef SYSDBG_BUS_DATA
+typedef struct _SYSDBG_BUS_DATA {
+ ULONG Address;
+ PVOID Buffer;
+ ULONG Request;
+ BUS_DATA_TYPE BusDataType;
+ ULONG BusNumber;
+ ULONG SlotNumber;
+} SYSDBG_BUS_DATA, *PSYSDBG_BUS_DATA;
+#define SYSDBG_BUS_DATA SYSDBG_BUS_DATA
+#endif
+
+#ifndef PCI_SEGMENT_BUS_NUMBER
+typedef struct _PCI_SEGMENT_BUS_NUMBER {
+ union {
+ struct {
+ ULONG BusNumber:8;
+ ULONG SegmentNumber:16;
+ ULONG Reserved:8;
+ } bits;
+ ULONG AsULONG;
+ } u;
+} PCI_SEGMENT_BUS_NUMBER, *PPCI_SEGMENT_BUS_NUMBER;
+#define PCI_SEGMENT_BUS_NUMBER PCI_SEGMENT_BUS_NUMBER
+#endif
+
+#ifndef PCI_SLOT_NUMBER
+typedef struct _PCI_SLOT_NUMBER {
+ union {
+ struct {
+ ULONG DeviceNumber:5;
+ ULONG FunctionNumber:3;
+ ULONG Reserved:24;
+ } bits;
+ ULONG AsULONG;
+ } u;
+} PCI_SLOT_NUMBER, *PPCI_SLOT_NUMBER;
+#define PCI_SLOT_NUMBER PCI_SLOT_NUMBER
+#endif
+
+#ifndef KLDBG
+typedef struct _KLDBG {
+ SYSDBG_COMMAND Command;
+ PVOID Buffer;
+ DWORD BufferLength;
+} KLDBG, *PKLDBG;
+#define KLDBG KLDBG
+#endif
+
+static BOOL debug_privilege_enabled;
+static LUID luid_debug_privilege;
+static BOOL revert_only_privilege;
+static HANDLE revert_token;
+
+static HANDLE kldbg_dev = INVALID_HANDLE_VALUE;
+
+static BOOL
+win32_kldbg_pci_bus_data(BOOL WriteBusData, USHORT SegmentNumber, BYTE BusNumber, BYTE DeviceNumber, BYTE FunctionNumber, USHORT Address, PVOID Buffer, ULONG BufferSize, LPDWORD Length);
+
+static const char *
+win32_strerror(DWORD win32_error_id)
+{
+ /*
+ * Use static buffer which is large enough.
+ * Hopefully no Win32 API error message string is longer than 4 kB.
+ */
+ static char buffer[4096];
+ DWORD len;
+
+ len = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, win32_error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), NULL);
+
+ /* FormatMessage() automatically appends ".\r\n" to the error message. */
+ if (len && buffer[len-1] == '\n')
+ buffer[--len] = '\0';
+ if (len && buffer[len-1] == '\r')
+ buffer[--len] = '\0';
+ if (len && buffer[len-1] == '.')
+ buffer[--len] = '\0';
+
+ if (!len)
+ sprintf(buffer, "Unknown Win32 error %lu", win32_error_id);
+
+ return buffer;
+}
+
+static BOOL
+win32_is_32bit_on_64bit_system(void)
+{
+ BOOL (WINAPI *MyIsWow64Process)(HANDLE, PBOOL);
+ HMODULE kernel32;
+ BOOL is_wow64;
+
+ kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
+ if (!kernel32)
+ return FALSE;
+
+ MyIsWow64Process = (void *)GetProcAddress(kernel32, "IsWow64Process");
+ if (!MyIsWow64Process)
+ return FALSE;
+
+ if (!MyIsWow64Process(GetCurrentProcess(), &is_wow64))
+ return FALSE;
+
+ return is_wow64;
+}
+
+static WORD
+win32_get_current_process_machine(void)
+{
+ IMAGE_DOS_HEADER *dos_header;
+ IMAGE_NT_HEADERS *nt_header;
+
+ dos_header = (IMAGE_DOS_HEADER *)GetModuleHandle(NULL);
+ if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
+ return IMAGE_FILE_MACHINE_UNKNOWN;
+
+ nt_header = (IMAGE_NT_HEADERS *)((BYTE *)dos_header + dos_header->e_lfanew);
+ if (nt_header->Signature != IMAGE_NT_SIGNATURE)
+ return IMAGE_FILE_MACHINE_UNKNOWN;
+
+ return nt_header->FileHeader.Machine;
+}
+
+static BOOL
+win32_check_driver(BYTE *driver_data)
+{
+ IMAGE_DOS_HEADER *dos_header;
+ IMAGE_NT_HEADERS *nt_headers;
+ WORD current_machine;
+
+ current_machine = win32_get_current_process_machine();
+ if (current_machine == IMAGE_FILE_MACHINE_UNKNOWN)
+ return FALSE;
+
+ dos_header = (IMAGE_DOS_HEADER *)driver_data;
+ if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
+ return FALSE;
+
+ nt_headers = (IMAGE_NT_HEADERS *)((BYTE *)dos_header + dos_header->e_lfanew);
+ if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
+ return FALSE;
+
+ if (nt_headers->FileHeader.Machine != current_machine)
+ return FALSE;
+
+ if (!(nt_headers->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE))
+ return FALSE;
+
+#ifndef _WIN64
+ if (!(nt_headers->FileHeader.Characteristics & IMAGE_FILE_32BIT_MACHINE))
+ return FALSE;
+#endif
+
+ /* IMAGE_NT_OPTIONAL_HDR_MAGIC is alias for the header magic used on the target compiler architecture. */
+ if (nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
+ return FALSE;
+
+ if (nt_headers->OptionalHeader.Subsystem != IMAGE_SUBSYSTEM_NATIVE)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int
+win32_kldbg_unpack_driver(struct pci_access *a, void *driver_path)
+{
+ BOOL use_kd_exe = FALSE;
+ HMODULE exe_with_driver = NULL;
+ HRSRC driver_resource_info = NULL;
+ HGLOBAL driver_resource = NULL;
+ BYTE *driver_data = NULL;
+ DWORD driver_size = 0;
+ HANDLE driver_handle = INVALID_HANDLE_VALUE;
+ DWORD written = 0;
+ DWORD error = 0;
+ int ret = 0;
+
+ /* Try to find and open windbg.exe or kd.exe file in PATH. */
+ exe_with_driver = LoadLibraryEx(TEXT("windbg.exe"), NULL, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
+ if (!exe_with_driver)
+ {
+ use_kd_exe = TRUE;
+ exe_with_driver = LoadLibraryEx(TEXT("kd.exe"), NULL, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
+ }
+ if (!exe_with_driver)
+ {
+ error = GetLastError();
+ if (error == ERROR_FILE_NOT_FOUND ||
+ error == ERROR_MOD_NOT_FOUND)
+ a->debug("Cannot find windbg.exe or kd.exe file in PATH");
+ else
+ a->debug("Cannot load %s file: %s.", use_kd_exe ? "kd.exe" : "windbg.exe", win32_strerror(error));
+ goto out;
+ }
+
+ /* kldbgdrv.sys is embedded in windbg.exe/kd.exe as a resource with name id 0x7777 and type id 0x4444. */
+ driver_resource_info = FindResource(exe_with_driver, MAKEINTRESOURCE(0x7777), MAKEINTRESOURCE(0x4444));
+ if (!driver_resource_info)
+ {
+ a->debug("Cannot find kldbgdrv.sys resource in %s file: %s.", use_kd_exe ? "kd.exe" : "windbg.exe", win32_strerror(GetLastError()));
+ goto out;
+ }
+
+ driver_resource = LoadResource(exe_with_driver, driver_resource_info);
+ if (!driver_resource)
+ {
+ a->debug("Cannot load kldbgdrv.sys resource from %s file: %s.", use_kd_exe ? "kd.exe" : "windbg.exe", win32_strerror(GetLastError()));
+ goto out;
+ }
+
+ driver_size = SizeofResource(exe_with_driver, driver_resource_info);
+ if (!driver_size)
+ {
+ a->debug("Cannot determinate size of kldbgdrv.sys resource from %s file: %s.", use_kd_exe ? "kd.exe" : "windbg.exe", win32_strerror(GetLastError()));
+ goto out;
+ }
+
+ driver_data = LockResource(driver_resource);
+ if (!driver_data)
+ {
+ a->debug("Cannot load kldbgdrv.sys resouce data from %s file: %s.", use_kd_exe ? "kd.exe" : "windbg.exe", win32_strerror(GetLastError()));
+ goto out;
+ }
+
+ if (!win32_check_driver(driver_data))
+ {
+ a->debug("Cannot use kldbgdrv.sys driver from %s file: Driver is from different architecture.", use_kd_exe ? "kd.exe" : "windbg.exe");
+ goto out;
+ }
+
+ driver_handle = CreateFile(driver_path, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (driver_handle == INVALID_HANDLE_VALUE)
+ {
+ error = GetLastError();
+ if (error != ERROR_FILE_EXISTS)
+ {
+ a->debug("Cannot create kldbgdrv.sys driver file in system32 directory: %s.", win32_strerror(error));
+ goto out;
+ }
+ /* If driver file in system32 directory already exists then treat it as successfull unpack. */
+ ret = 1;
+ goto out;
+ }
+
+ if (!WriteFile(driver_handle, driver_data, driver_size, &written, NULL) ||
+ written != driver_size)
+ {
+ a->debug("Cannot store kldbgdrv.sys driver file to system32 directory: %s.", win32_strerror(GetLastError()));
+ /* On error, delete file from system32 directory to allow another unpack attempt. */
+ CloseHandle(driver_handle);
+ driver_handle = INVALID_HANDLE_VALUE;
+ DeleteFile(driver_path);
+ goto out;
+ }
+
+ a->debug("Driver kldbgdrv.sys was successfully unpacked from %s and stored in system32 directory...", use_kd_exe ? "kd.exe" : "windbg.exe");
+ ret = 1;
+
+out:
+ if (driver_handle != INVALID_HANDLE_VALUE)
+ CloseHandle(driver_handle);
+
+ if (driver_resource)
+ FreeResource(driver_resource);
+
+ if (exe_with_driver)
+ FreeLibrary(exe_with_driver);
+
+ return ret;
+}
+
+static int
+win32_kldbg_register_driver(struct pci_access *a, SC_HANDLE manager, SC_HANDLE *service)
+{
+ UINT (WINAPI *get_system_root_path)(void *buffer, UINT size) = NULL;
+ UINT systemroot_len;
+ void *driver_path;
+ HANDLE driver_handle;
+ HMODULE kernel32;
+
+ /*
+ * COM library dbgeng.dll unpacks kldbg driver to file \\system32\\kldbgdrv.sys
+ * and register this driver with service name kldbgdrv. Implement same behavior.
+ */
+
+ /*
+ * Old Windows versions return path to NT SystemRoot namespace via
+ * GetWindowsDirectory() function. New Windows versions via
+ * GetSystemWindowsDirectory(). GetSystemWindowsDirectory() is not
+ * provided in old Windows versions, so use GetProcAddress() for
+ * compatibility with all Windows versions.
+ */
+
+ kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
+ if (kernel32)
+ get_system_root_path = (void *)GetProcAddress(kernel32, "GetSystemWindowsDirectory"
+#ifdef UNICODE
+ "W"
+#else
+ "A"
+#endif
+ );
+
+ if (!get_system_root_path)
+ get_system_root_path = (void *)&GetWindowsDirectory;
+
+ systemroot_len = get_system_root_path(NULL, 0);
+ if (!systemroot_len)
+ systemroot_len = sizeof(TEXT("C:\\Windows\\"));
+
+ driver_path = pci_malloc(a, systemroot_len + sizeof(TEXT("\\system32\\kldbgdrv.sys")));
+
+ systemroot_len = get_system_root_path(driver_path, systemroot_len + sizeof(TEXT("")));
+ if (!systemroot_len)
+ {
+ systemroot_len = sizeof(TEXT("C:\\Windows\\"));
+ memcpy(driver_path, TEXT("C:\\Windows\\"), systemroot_len);
+ }
+
+ if (((char *)driver_path)[systemroot_len-sizeof(TEXT(""))+1] != '\\')
+ {
+ ((char *)driver_path)[systemroot_len-sizeof(TEXT(""))+1] = '\\';
+ systemroot_len += sizeof(TEXT(""));
+ }
+
+ memcpy((char *)driver_path + systemroot_len, TEXT("system32\\kldbgdrv.sys"), sizeof(TEXT("system32\\kldbgdrv.sys")));
+
+ driver_handle = CreateFile(driver_path, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (driver_handle != INVALID_HANDLE_VALUE)
+ CloseHandle(driver_handle);
+ else if (GetLastError() == ERROR_FILE_NOT_FOUND)
+ {
+ a->debug("Driver kldbgdrv.sys is missing, trying to unpack it from windbg.exe or kd.exe...");
+ if (!win32_kldbg_unpack_driver(a, driver_path))
+ {
+ pci_mfree(driver_path);
+ return 0;
+ }
+ }
+
+ *service = CreateService(manager, TEXT("kldbgdrv"), TEXT("kldbgdrv"), SERVICE_START, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, driver_path, NULL, NULL, NULL, NULL, NULL);
+ if (!*service)
+ {
+ if (GetLastError() != ERROR_SERVICE_EXISTS)
+ {
+ a->debug("Cannot create kldbgdrv service: %s.", win32_strerror(GetLastError()));
+ pci_mfree(driver_path);
+ return 0;
+ }
+
+ *service = OpenService(manager, TEXT("kldbgdrv"), SERVICE_START);
+ if (!*service)
+ {
+ a->debug("Cannot open kldbgdrv service: %s.", win32_strerror(GetLastError()));
+ pci_mfree(driver_path);
+ return 0;
+ }
+ }
+
+ a->debug("Service kldbgdrv was successfully registered...");
+ pci_mfree(driver_path);
+ return 1;
+}
+
+static int
+win32_kldbg_start_driver(struct pci_access *a)
+{
+ SC_HANDLE manager = NULL;
+ SC_HANDLE service = NULL;
+ DWORD error = 0;
+ int ret = 0;
+
+ manager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);
+ if (!manager)
+ manager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
+ if (!manager)
+ {
+ a->debug("Cannot open Service Manager: %s.", win32_strerror(GetLastError()));
+ return 0;
+ }
+
+ service = OpenService(manager, TEXT("kldbgdrv"), SERVICE_START);
+ if (!service)
+ {
+ error = GetLastError();
+ if (error != ERROR_SERVICE_DOES_NOT_EXIST)
+ {
+ a->debug("Cannot open kldbgdrv service: %s.", win32_strerror(error));
+ goto out;
+ }
+
+ a->debug("Kernel Local Debugging Driver (kldbgdrv.sys) is not registered, trying to register it...");
+
+ if (win32_is_32bit_on_64bit_system())
+ {
+ /* TODO */
+ a->debug("Registering driver from 32-bit process on 64-bit system is not implemented yet.");
+ goto out;
+ }
+
+ if (!win32_kldbg_register_driver(a, manager, &service))
+ goto out;
+ }
+
+ if (!StartService(service, 0, NULL))
+ {
+ error = GetLastError();
+ if (error != ERROR_SERVICE_ALREADY_RUNNING)
+ {
+ a->debug("Cannot start kldbgdrv service: %s.", win32_strerror(error));
+ goto out;
+ }
+ }
+
+ a->debug("Service kldbgdrv successfully started...");
+ ret = 1;
+
+out:
+ if (service)
+ CloseServiceHandle(service);
+
+ if (manager)
+ CloseServiceHandle(manager);
+
+ return ret;
+}
+
+static int
+win32_kldbg_setup(struct pci_access *a)
+{
+ OSVERSIONINFO version;
+ DWORD ret_len;
+ DWORD error;
+ DWORD id;
+
+ if (kldbg_dev != INVALID_HANDLE_VALUE)
+ return 1;
+
+ /* Check for Windows Vista (NT 6.0). */
+ version.dwOSVersionInfoSize = sizeof(version);
+ if (!GetVersionEx(&version) ||
+ version.dwPlatformId != VER_PLATFORM_WIN32_NT ||
+ version.dwMajorVersion < 6)
+ {
+ a->debug("Accessing PCI config space via Kernel Local Debugging Driver requires Windows Vista or higher version.");
+ return 0;
+ }
+
+ kldbg_dev = CreateFile(TEXT("\\\\.\\kldbgdrv"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (kldbg_dev == INVALID_HANDLE_VALUE)
+ {
+ error = GetLastError();
+ if (error != ERROR_FILE_NOT_FOUND)
+ {
+ a->debug("Cannot open \"\\\\.\\kldbgdrv\" device: %s.", win32_strerror(error));
+ return 0;
+ }
+
+ a->debug("Kernel Local Debugging Driver (kldbgdrv.sys) is not running, trying to start it...");
+
+ if (!win32_kldbg_start_driver(a))
+ return 0;
+
+ kldbg_dev = CreateFile(TEXT("\\\\.\\kldbgdrv"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (kldbg_dev == INVALID_HANDLE_VALUE)
+ {
+ error = GetLastError();
+ a->debug("Cannot open \"\\\\.\\kldbgdrv\" device: %s.", win32_strerror(error));
+ return 0;
+ }
+ }
+
+ /*
+ * Try to read PCI id register from PCI device 0000:00:00.0.
+ * If this device does not exist and kldbg API is working then
+ * kldbg returns success with read value 0xffffffff.
+ */
+ if (win32_kldbg_pci_bus_data(FALSE, 0, 0, 0, 0, 0, &id, sizeof(id), &ret_len) && ret_len == sizeof(id))
+ return 1;
+
+ error = GetLastError();
+
+ a->debug("Cannot read PCI config space via Kernel Local Debugging Driver: %s.", win32_strerror(error));
+
+ if (error != ERROR_ACCESS_DENIED)
+ {
+ CloseHandle(kldbg_dev);
+ kldbg_dev = INVALID_HANDLE_VALUE;
+ return 0;
+ }
+
+ a->debug("..Trying again with Debug privilege...");
+
+ if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid_debug_privilege))
+ {
+ a->debug("Debug privilege is not supported.");
+ CloseHandle(kldbg_dev);
+ kldbg_dev = INVALID_HANDLE_VALUE;
+ return 0;
+ }
+
+ if (!enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege))
+ {
+ a->debug("Process does not have right to enable Debug privilege.");
+ CloseHandle(kldbg_dev);
+ kldbg_dev = INVALID_HANDLE_VALUE;
+ return 0;
+ }
+
+ if (win32_kldbg_pci_bus_data(FALSE, 0, 0, 0, 0, 0, &id, sizeof(id), &ret_len) && ret_len == sizeof(id))
+ {
+ a->debug("Succeeded.");
+ debug_privilege_enabled = TRUE;
+ return 1;
+ }
+
+ error = GetLastError();
+
+ a->debug("Cannot read PCI config space via Kernel Local Debugging Driver: %s.", win32_strerror(error));
+
+ CloseHandle(kldbg_dev);
+ kldbg_dev = INVALID_HANDLE_VALUE;
+
+ revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege);
+ revert_token = NULL;
+ revert_only_privilege = FALSE;
+ return 0;
+}
+
+static int
+win32_kldbg_detect(struct pci_access *a)
+{
+ if (!win32_kldbg_setup(a))
+ return 0;
+
+ return 1;
+}
+
+static void
+win32_kldbg_init(struct pci_access *a)
+{
+ if (!win32_kldbg_setup(a))
+ {
+ a->debug("\n");
+ a->error("PCI config space via Kernel Local Debugging Driver cannot be accessed.");
+ }
+}
+
+static void
+win32_kldbg_cleanup(struct pci_access *a UNUSED)
+{
+ if (kldbg_dev == INVALID_HANDLE_VALUE)
+ return;
+
+ CloseHandle(kldbg_dev);
+ kldbg_dev = INVALID_HANDLE_VALUE;
+
+ if (debug_privilege_enabled)
+ {
+ revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege);
+ revert_token = NULL;
+ revert_only_privilege = FALSE;
+ debug_privilege_enabled = FALSE;
+ }
+}
+
+struct acpi_mcfg {
+ char signature[4];
+ u32 length;
+ u8 revision;
+ u8 checksum;
+ char oem_id[6];
+ char oem_table_id[8];
+ u32 oem_revision;
+ char asl_compiler_id[4];
+ u32 asl_compiler_revision;
+ u64 reserved;
+ struct {
+ u64 address;
+ u16 pci_segment;
+ u8 start_bus_number;
+ u8 end_bus_number;
+ u32 reserved;
+ } allocations[0];
+} PCI_PACKED;
+
+static void
+win32_kldbg_scan(struct pci_access *a)
+{
+ /*
+ * There is no kldbg API to retrieve list of PCI segments. WinDBG pci plugin
+ * kext.dll loads debug symbols from pci.pdb file for kernel module pci.sys.
+ * Then it reads kernel memory which belongs to PciSegmentList local variable
+ * which is the first entry of struct _PCI_SEGMENT linked list. And then it
+ * iterates all entries in linked list and reads SegmentNumber for each entry.
+ *
+ * This is extremly ugly hack and does not work on systems without installed
+ * kernel debug symbol files.
+ *
+ * Do something less ugly. Retrieve ACPI MCFG table via GetSystemFirmwareTable
+ * and parse all PCI segment numbers from it. ACPI MCFG table contains PCIe
+ * ECAM definitions, so all PCI segment numbers.
+ */
+
+ UINT (*WINAPI MyGetSystemFirmwareTable)(DWORD FirmwareTableProviderSignature, DWORD FirmwareTableID, PVOID pFirmwareTableBuffer, DWORD BufferSize);
+ int i, allocations_count;
+ struct acpi_mcfg *mcfg;
+ HMODULE kernel32;
+ byte *segments;
+ DWORD error;
+ DWORD size;
+
+ /* Always scan PCI segment 0. */
+ pci_generic_scan_domain(a, 0);
+
+ kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
+ if (!kernel32)
+ return;
+
+ /* Function GetSystemFirmwareTable() is available since Windows Vista. */
+ MyGetSystemFirmwareTable = (void *)GetProcAddress(kernel32, "GetSystemFirmwareTable");
+ if (!MyGetSystemFirmwareTable)
+ return;
+
+ /* 0x41435049 = 'ACPI', 0x4746434D = 'MCFG' */
+ size = MyGetSystemFirmwareTable(0x41435049, 0x4746434D, NULL, 0);
+ if (size == 0)
+ {
+ error = GetLastError();
+ if (error == ERROR_INVALID_FUNCTION) /* ACPI is not present, so only PCI segment 0 is available. */
+ return;
+ else if (error == ERROR_NOT_FOUND) /* MCFG table is not present, so only PCI segment 0 is available. */
+ return;
+ a->debug("Cannot retrieve ACPI MCFG table: %s.\n", win32_strerror(error));
+ return;
+ }
+
+ mcfg = pci_malloc(a, size);
+
+ if (MyGetSystemFirmwareTable(0x41435049, 0x4746434D, mcfg, size) != size)
+ {
+ error = GetLastError();
+ a->debug("Cannot retrieve ACPI MCFG table: %s.\n", win32_strerror(error));
+ pci_mfree(mcfg);
+ return;
+ }
+
+ if (size < sizeof(*mcfg) || size < mcfg->length)
+ {
+ a->debug("ACPI MCFG table is broken.\n");
+ pci_mfree(mcfg);
+ return;
+ }
+
+ segments = pci_malloc(a, 0xFFFF/8);
+ memset(segments, 0, 0xFFFF/8);
+
+ /* Scan all MCFG allocations and set available PCI segments into bit field. */
+ allocations_count = (mcfg->length - ((unsigned char *)&mcfg->allocations - (unsigned char *)mcfg)) / sizeof(mcfg->allocations[0]);
+ for (i = 0; i < allocations_count; i++)
+ segments[mcfg->allocations[i].pci_segment / 8] |= 1 << (mcfg->allocations[i].pci_segment % 8);
+
+ /* Skip PCI segment 0 which was already scanned. */
+ for (i = 1; i < 0xFFFF; i++)
+ if (segments[i / 8] & (1 << (i % 8)))
+ pci_generic_scan_domain(a, i);
+
+ pci_mfree(segments);
+ pci_mfree(mcfg);
+}
+
+static BOOL
+win32_kldbg_pci_bus_data(BOOL WriteBusData, USHORT SegmentNumber, BYTE BusNumber, BYTE DeviceNumber, BYTE FunctionNumber, USHORT Address, PVOID Buffer, ULONG BufferSize, LPDWORD Length)
+{
+ KLDBG kldbg_cmd;
+ SYSDBG_BUS_DATA sysdbg_cmd;
+ PCI_SLOT_NUMBER pci_slot;
+ PCI_SEGMENT_BUS_NUMBER pci_seg_bus;
+
+ memset(&pci_slot, 0, sizeof(pci_slot));
+ memset(&sysdbg_cmd, 0, sizeof(sysdbg_cmd));
+ memset(&pci_seg_bus, 0, sizeof(pci_seg_bus));
+
+ sysdbg_cmd.Address = Address;
+ sysdbg_cmd.Buffer = Buffer;
+ sysdbg_cmd.Request = BufferSize;
+ sysdbg_cmd.BusDataType = PCIConfiguration;
+ pci_seg_bus.u.bits.BusNumber = BusNumber;
+ pci_seg_bus.u.bits.SegmentNumber = SegmentNumber;
+ sysdbg_cmd.BusNumber = pci_seg_bus.u.AsULONG;
+ pci_slot.u.bits.DeviceNumber = DeviceNumber;
+ pci_slot.u.bits.FunctionNumber = FunctionNumber;
+ sysdbg_cmd.SlotNumber = pci_slot.u.AsULONG;
+
+ kldbg_cmd.Command = WriteBusData ? SysDbgWriteBusData : SysDbgReadBusData;
+ kldbg_cmd.Buffer = &sysdbg_cmd;
+ kldbg_cmd.BufferLength = sizeof(sysdbg_cmd);
+
+ *Length = 0;
+ return DeviceIoControl(kldbg_dev, IOCTL_KLDBG, &kldbg_cmd, sizeof(kldbg_cmd), &sysdbg_cmd, sizeof(sysdbg_cmd), Length, NULL);
+}
+
+static int
+win32_kldbg_read(struct pci_dev *d, int pos, byte *buf, int len)
+{
+ DWORD ret_len;
+
+ if ((unsigned int)d->domain > 0xffff)
+ return 0;
+
+ if (!win32_kldbg_pci_bus_data(FALSE, d->domain, d->bus, d->dev, d->func, pos, buf, len, &ret_len))
+ return 0;
+
+ if (ret_len != (unsigned int)len)
+ return 0;
+
+ return 1;
+}
+
+static int
+win32_kldbg_write(struct pci_dev *d, int pos, byte *buf, int len)
+{
+ DWORD ret_len;
+
+ if ((unsigned int)d->domain > 0xffff)
+ return 0;
+
+ if (!win32_kldbg_pci_bus_data(TRUE, d->domain, d->bus, d->dev, d->func, pos, buf, len, &ret_len))
+ return 0;
+
+ if (ret_len != (unsigned int)len)
+ return 0;
+
+ return 1;
+}
+
+struct pci_methods pm_win32_kldbg = {
+ "win32-kldbg",
+ "Win32 PCI config space access using Kernel Local Debugging Driver",
+ NULL, /* config */
+ win32_kldbg_detect,
+ win32_kldbg_init,
+ win32_kldbg_cleanup,
+ win32_kldbg_scan,
+ pci_generic_fill_info,
+ win32_kldbg_read,
+ win32_kldbg_write,
+ NULL, /* read_vpd */
+ NULL, /* init_dev */
+ NULL /* cleanup_dev */
+};