diff options
Diffstat (limited to '')
-rw-r--r-- | lib/win32-cfgmgr32.c | 1765 |
1 files changed, 1765 insertions, 0 deletions
diff --git a/lib/win32-cfgmgr32.c b/lib/win32-cfgmgr32.c new file mode 100644 index 0000000..dbddc54 --- /dev/null +++ b/lib/win32-cfgmgr32.c @@ -0,0 +1,1765 @@ +/* + * The PCI Library -- List PCI devices on Win32 via Configuration Manager + * + * Copyright (c) 2021 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <windows.h> +#include <cfgmgr32.h> + +#include <ctype.h> /* for isxdigit() */ +#include <stdio.h> /* for sprintf() */ +#include <string.h> /* for strlen(), strchr(), strncmp() */ +#include <wchar.h> /* for wcslen(), wcscpy() */ + +#include "internal.h" +#include "win32-helpers.h" + +/* Unfortunately MinGW32 toolchain does not provide these cfgmgr32 constants. */ + +#ifndef RegDisposition_OpenExisting +#define RegDisposition_OpenExisting 0x00000001 +#endif + +#ifndef CM_REGISTRY_SOFTWARE +#define CM_REGISTRY_SOFTWARE 0x00000001 +#endif + +#ifndef CM_DRP_HARDWAREID +#define CM_DRP_HARDWAREID 0x00000002 +#endif +#ifndef CM_DRP_SERVICE +#define CM_DRP_SERVICE 0x00000005 +#endif +#ifndef CM_DRP_BUSNUMBER +#define CM_DRP_BUSNUMBER 0x00000016 +#endif +#ifndef CM_DRP_ADDRESS +#define CM_DRP_ADDRESS 0x0000001D +#endif + +#ifndef CR_INVALID_CONFLICT_LIST +#define CR_INVALID_CONFLICT_LIST 0x00000039 +#endif +#ifndef CR_INVALID_INDEX +#define CR_INVALID_INDEX 0x0000003A +#endif +#ifndef CR_INVALID_STRUCTURE_SIZE +#define CR_INVALID_STRUCTURE_SIZE 0x0000003B +#endif + +#ifndef fIOD_10_BIT_DECODE +#define fIOD_10_BIT_DECODE 0x0004 +#endif +#ifndef fIOD_12_BIT_DECODE +#define fIOD_12_BIT_DECODE 0x0008 +#endif +#ifndef fIOD_16_BIT_DECODE +#define fIOD_16_BIT_DECODE 0x0010 +#endif +#ifndef fIOD_POSITIVE_DECODE +#define fIOD_POSITIVE_DECODE 0x0020 +#endif +#ifndef fIOD_PASSIVE_DECODE +#define fIOD_PASSIVE_DECODE 0x0040 +#endif +#ifndef fIOD_WINDOW_DECODE +#define fIOD_WINDOW_DECODE 0x0080 +#endif +#ifndef fIOD_PORT_BAR +#define fIOD_PORT_BAR 0x0100 +#endif + +#ifndef fMD_WINDOW_DECODE +#define fMD_WINDOW_DECODE 0x0040 +#endif +#ifndef fMD_MEMORY_BAR +#define fMD_MEMORY_BAR 0x0080 +#endif + +#ifndef mMD_Prefetchable +#define mMD_Prefetchable fMD_Prefetchable +#endif + +/* + * Unfortunately MinGW32 toolchain does not provide import library for these + * cfgmgr32.dll functions. So resolve pointers to these functions at runtime. + * MinGW-w64 toolchain provides them also in 32-bit mode. + */ + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + +#ifdef CM_Get_DevNode_Registry_PropertyA +#undef CM_Get_DevNode_Registry_PropertyA +#endif +static CONFIGRET (WINAPI *MyCM_Get_DevNode_Registry_PropertyA)(DEVINST dnDevInst, ULONG ulProperty, PULONG pulRegDataType, PVOID Buffer, PULONG pulLength, ULONG ulFlags); +#define CM_Get_DevNode_Registry_PropertyA MyCM_Get_DevNode_Registry_PropertyA + +#ifdef CM_Get_DevNode_Registry_PropertyW +#undef CM_Get_DevNode_Registry_PropertyW +#endif +static CONFIGRET (WINAPI *MyCM_Get_DevNode_Registry_PropertyW)(DEVINST dnDevInst, ULONG ulProperty, PULONG pulRegDataType, PVOID Buffer, PULONG pulLength, ULONG ulFlags); +#define CM_Get_DevNode_Registry_PropertyW MyCM_Get_DevNode_Registry_PropertyW + +#ifndef CM_Open_DevNode_Key +#undef CM_Open_DevNode_Key +#endif +static CONFIGRET (WINAPI *MyCM_Open_DevNode_Key)(DEVINST dnDevNode, REGSAM samDesired, ULONG ulHardwareProfile, REGDISPOSITION Disposition, PHKEY phkDevice, ULONG ulFlags); +#define CM_Open_DevNode_Key MyCM_Open_DevNode_Key + +static BOOL +resolve_cfgmgr32_functions(void) +{ + HMODULE cfgmgr32; + + if (CM_Get_DevNode_Registry_PropertyA && CM_Get_DevNode_Registry_PropertyW && CM_Open_DevNode_Key) + return TRUE; + + cfgmgr32 = GetModuleHandleA("cfgmgr32.dll"); + if (!cfgmgr32) + return FALSE; + + CM_Get_DevNode_Registry_PropertyA = (void *)GetProcAddress(cfgmgr32, "CM_Get_DevNode_Registry_PropertyA"); + CM_Get_DevNode_Registry_PropertyW = (void *)GetProcAddress(cfgmgr32, "CM_Get_DevNode_Registry_PropertyW"); + CM_Open_DevNode_Key = (void *)GetProcAddress(cfgmgr32, "CM_Open_DevNode_Key"); + if (!CM_Get_DevNode_Registry_PropertyA || !CM_Get_DevNode_Registry_PropertyW || !CM_Open_DevNode_Key) + return FALSE; + + return TRUE; +} + +#endif + +/* + * cfgmgr32.dll uses custom non-Win32 error numbers which are unsupported by + * Win32 APIs like GetLastError() and FormatMessage() functions. + * + * Windows 7 introduced new cfgmgr32.dll function CM_MapCrToWin32Err() for + * translating mapping CR_* errors to Win32 errors but most error codes are + * not mapped. So this function is unusable. + * + * Error strings for CR_* errors are defined in cmapi.rc file which is + * statically linked into some system libraries (e.g. filemgmt.dll, + * acledit.dll, netui0.dll or netui2.dll) but due to static linking it is + * not possible to access these error strings easily at runtime. + * + * So define own function for translating CR_* errors directly to strings. + */ +static const char * +cr_strerror(CONFIGRET cr_error_id) +{ + static char unknown_error[sizeof("Unknown CR error XXXXXXXXXX")]; + static const char *cr_errors[] = { + "The operation completed successfully", + "CR_DEFAULT", + "Not enough memory is available to process this command", + "A required pointer parameter is invalid", + "The ulFlags parameter specified is invalid for this operation", + "The device instance handle parameter is not valid", + "The supplied resource descriptor parameter is invalid", + "The supplied logical configuration parameter is invalid", + "CR_INVALID_ARBITRATOR", + "CR_INVALID_NODELIST", + "CR_DEVNODE_HAS_REQS/CR_DEVINST_HAS_REQS", + "The RESOURCEID parameter does not contain a valid RESOURCEID", + "CR_DLVXD_NOT_FOUND", + "The specified device instance handle does not correspond to a present device", + "There are no more logical configurations available", + "There are no more resource descriptions available", + "This device instance already exists", + "The supplied range list parameter is invalid", + "CR_INVALID_RANGE", + "A general internal error occurred", + "CR_NO_SUCH_LOGICAL_DEV", + "The device is disabled for this configuration", + "CR_NOT_SYSTEM_VM", + "A service or application refused to allow removal of this device", + "CR_APM_VETOED", + "CR_INVALID_LOAD_TYPE", + "An output parameter was too small to hold all the data available", + "CR_NO_ARBITRATOR", + "CR_NO_REGISTRY_HANDLE", + "A required entry in the registry is missing or an attempt to write to the registry failed", + "The specified Device ID is not a valid Device ID", + "One or more parameters were invalid", + "CR_INVALID_API", + "CR_DEVLOADER_NOT_READY", + "CR_NEED_RESTART", + "There are no more hardware profiles available", + "CR_DEVICE_NOT_THERE", + "The specified value does not exist in the registry", + "CR_WRONG_TYPE", + "The specified priority is invalid for this operation", + "This device cannot be disabled", + "CR_FREE_RESOURCES", + "CR_QUERY_VETOED", + "CR_CANT_SHARE_IRQ", + "CR_NO_DEPENDENT", + "CR_SAME_RESOURCES", + "The specified key does not exist in the registry", + "The specified machine name does not meet the UNC naming conventions", + "A general remote communication error occurred", + "The machine selected for remote communication is not available at this time", + "The Plug and Play service or another required service is not available", + "Access denied", + "This routine is not implemented in this version of the operating system", + "The specified property type is invalid for this operation", + "Device interface is active", + "No such device interface", + "Invalid reference string", + "Invalid conflict list", + "Invalid index", + "Invalid structure size" + }; + if (cr_error_id <= 0 || cr_error_id >= sizeof(cr_errors)/sizeof(*cr_errors)) + { + sprintf(unknown_error, "Unknown CR error %lu", cr_error_id); + return unknown_error; + } + return cr_errors[cr_error_id]; +} + +static int +fmt_validate(const char *s, int len, const char *fmt) +{ + int i; + + for (i = 0; i < len; i++) + if (!fmt[i] || (fmt[i] == '#' ? !isxdigit(s[i]) : fmt[i] != s[i])) + return 0; + + return 1; +} + +static int +seq_xdigit_validate(const char *s, int mult, int min) +{ + int i, len; + + len = strlen(s); + if (len < min*mult || len % mult) + return 0; + + for (i = 0; i < len; i++) + if (!isxdigit(s[i])) + return 0; + + return 1; +} + +static LPWSTR +get_device_service_name(struct pci_access *a, DEVINST devinst, DEVINSTID_A devinst_id, BOOL *supported) +{ + ULONG reg_type, reg_size, reg_len; + LPWSTR service_name; + CONFIGRET cr; + + /* + * All data are stored as 7-bit ASCII strings in system but service name is + * exception. It can contain UNICODE. Moreover it is passed to other Win32 API + * functions and therefore it cannot be converted to 8-bit ANSI string without + * data loss. So use wide function CM_Get_DevNode_Registry_PropertyW() in this + * case and deal with all wchar_t problems... + */ + + reg_size = 0; + cr = CM_Get_DevNode_Registry_PropertyW(devinst, CM_DRP_SERVICE, ®_type, NULL, ®_size, 0); + if (cr == CR_CALL_NOT_IMPLEMENTED) + { + *supported = FALSE; + return NULL; + } + else if (cr == CR_NO_SUCH_VALUE) + { + *supported = TRUE; + return NULL; + } + else if (cr != CR_SUCCESS && + cr != CR_BUFFER_SMALL) + { + a->warning("Cannot retrieve service name for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + *supported = TRUE; + return NULL; + } + else if (reg_type != REG_SZ) + { + a->warning("Cannot retrieve service name for PCI device %s: Service name is stored as unknown type 0x%lx.", devinst_id, reg_type); + *supported = TRUE; + return NULL; + } + +retry: + /* + * Returned size is on older Windows versions without nul-term char. + * So explicitly increase size and fill nul-term byte. + */ + reg_size += sizeof(service_name[0]); + service_name = pci_malloc(a, reg_size); + reg_len = reg_size; + cr = CM_Get_DevNode_Registry_PropertyW(devinst, CM_DRP_SERVICE, ®_type, service_name, ®_len, 0); + service_name[reg_size/sizeof(service_name[0]) - 1] = 0; + if (reg_len > reg_size) + { + pci_mfree(service_name); + reg_size = reg_len; + goto retry; + } + else if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve service name for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + pci_mfree(service_name); + *supported = TRUE; + return NULL; + } + else if (reg_type != REG_SZ) + { + a->warning("Cannot retrieve service name for PCI device %s: Service name is stored as unknown type 0x%lx.", devinst_id, reg_type); + pci_mfree(service_name); + *supported = TRUE; + return NULL; + } + + + return service_name; +} + +static char* +get_driver_path_for_service(struct pci_access *a, LPCWSTR service_name, SC_HANDLE manager) +{ + UINT (WINAPI *get_system_root_path)(LPWSTR buffer, UINT size) = NULL; + DWORD service_config_size, service_config_len; + LPQUERY_SERVICE_CONFIGW service_config = NULL; + LPWSTR service_image_path = NULL; + SERVICE_STATUS service_status; + SC_HANDLE service = NULL; + char *driver_path = NULL; + int trim_system32 = 0; + UINT systemroot_len; + int driver_path_len; + UINT system32_len; + HMODULE kernel32; + WCHAR *trim_ptr; + DWORD error; + + service = OpenServiceW(manager, service_name, SERVICE_QUERY_CONFIG | SERVICE_QUERY_STATUS); + if (!service) + { + error = GetLastError(); + if (error != ERROR_SERVICE_DOES_NOT_EXIST) + a->warning("Cannot open service %ls with query rights: %s.", service_name, win32_strerror(error)); + goto out; + } + + if (!QueryServiceStatus(service, &service_status)) + { + error = GetLastError(); + a->warning("Cannot query status of service %ls: %s.", service_name, win32_strerror(error)); + goto out; + } + + if (service_status.dwCurrentState == SERVICE_STOPPED) + goto out; + + if (service_status.dwServiceType != SERVICE_KERNEL_DRIVER) + goto out; + + if (!QueryServiceConfigW(service, NULL, 0, &service_config_size)) + { + error = GetLastError(); + if (error != ERROR_INSUFFICIENT_BUFFER) + { + a->warning("Cannot query config of service %ls: %s.", service_name, win32_strerror(error)); + goto out; + } + } + +retry_service_config: + service_config = pci_malloc(a, service_config_size); + if (!QueryServiceConfigW(service, service_config, service_config_size, &service_config_len)) + { + error = GetLastError(); + if (error == ERROR_INSUFFICIENT_BUFFER) + { + pci_mfree(service_config); + service_config_size = service_config_len; + goto retry_service_config; + } + a->warning("Cannot query config of service %ls: %s.", service_name, win32_strerror(error)); + goto out; + } + + if (service_config->dwServiceType != SERVICE_KERNEL_DRIVER) + goto out; + + /* + * Despite QueryServiceConfig() is Win32 API, it returns lpBinaryPathName + * (ImagePath registry) in NT format. Unfortunately there is no Win32 + * function for converting NT paths to Win32 paths. So do it manually and + * convert this NT format to human-readable Win32 path format. + */ + + /* + * GetSystemWindowsDirectoryW() returns path to NT SystemRoot namespace. + * Alternativelly path to NT SystemRoot namespace can be constructed by + * GetSystemDirectoryW() by trimming "\\system32" from the end of path. + * GetSystemWindowsDirectoryW() is not provided in old Windows versions, + * so use GetProcAddress() for compatibility with all Windows versions. + */ + kernel32 = GetModuleHandleW(L"kernel32.dll"); + if (kernel32) + get_system_root_path = (void *)GetProcAddress(kernel32, "GetSystemWindowsDirectoryW"); + else + { + get_system_root_path = &GetSystemDirectoryW; + trim_system32 = 1; + } + + if (!service_config->lpBinaryPathName || !service_config->lpBinaryPathName[0]) + { + /* No ImagePath is specified, NT kernel assumes implicit kernel driver path by service name, which is relative to "\\system32\\drivers". */ + /* GetSystemDirectoryW() returns path to "\\system32" directory on all Windows versions. */ + system32_len = GetSystemDirectoryW(NULL, 0); /* Returns number of WCHARs plus 1 for nul-term. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * (system32_len + sizeof("\\drivers\\")-1 + wcslen(service_name) + sizeof(".sys")-1)); + system32_len = GetSystemDirectoryW(service_image_path, system32_len); /* Now it returns number of WCHARs without nul-term. */ + if (system32_len && service_image_path[system32_len-1] != L'\\') + service_image_path[system32_len++] = L'\\'; + wcscpy(service_image_path + system32_len, L"drivers\\"); + wcscpy(service_image_path + system32_len + sizeof("drivers\\")-1, service_name); + wcscpy(service_image_path + system32_len + sizeof("drivers\\")-1 + wcslen(service_name), L".sys"); + } + else if (wcsncmp(service_config->lpBinaryPathName, L"\\SystemRoot\\", sizeof("\\SystemRoot\\")-1) == 0) + { + /* ImagePath is in NT SystemRoot namespace, convert to Win32 path via GetSystemWindowsDirectoryW()/GetSystemDirectoryW(). */ + systemroot_len = get_system_root_path(NULL, 0); /* Returns number of WCHARs plus 1 for nul-term. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * (systemroot_len + wcslen(service_config->lpBinaryPathName) - (sizeof("\\SystemRoot")-1))); + systemroot_len = get_system_root_path(service_image_path, systemroot_len); /* Now it returns number of WCHARs without nul-term. */ + if (trim_system32 && systemroot_len && (trim_ptr = wcsrchr(service_image_path, L'\\')) != NULL) + systemroot_len = trim_ptr - service_image_path; + if (systemroot_len && service_image_path[systemroot_len-1] != L'\\') + service_image_path[systemroot_len++] = L'\\'; + wcscpy(service_image_path + systemroot_len, service_config->lpBinaryPathName + sizeof("\\SystemRoot\\")-1); + } + else if (wcsncmp(service_config->lpBinaryPathName, L"\\??\\UNC\\", sizeof("\\??\\UNC\\")-1) == 0 || + wcsncmp(service_config->lpBinaryPathName, L"\\??\\\\UNC\\", sizeof("\\??\\\\UNC\\")-1) == 0) + { + /* ImagePath is in NT UNC namespace, convert to Win32 UNC path via "\\\\" prefix. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * (sizeof("\\\\") + wcslen(service_config->lpBinaryPathName) - (sizeof("\\??\\UNC\\")-1))); + /* Namespace separator may be single or double backslash. */ + driver_path_len = sizeof("\\??\\")-1; + if (service_config->lpBinaryPathName[driver_path_len] == L'\\') + driver_path_len++; + driver_path_len += sizeof("UNC\\")-1; + wcscpy(service_image_path, L"\\\\"); + wcscpy(service_image_path + sizeof("\\\\")-1, service_config->lpBinaryPathName + driver_path_len); + } + else if (wcsncmp(service_config->lpBinaryPathName, L"\\??\\", sizeof("\\??\\")-1) == 0) + { + /* ImagePath is in NT Global?? namespace, root of the Win32 file namespace, so just remove "\\??\\" prefix to get Win32 path. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * (wcslen(service_config->lpBinaryPathName) - (sizeof("\\??\\")-1))); + /* Namespace separator may be single or double backslash. */ + driver_path_len = sizeof("\\??\\")-1; + if (service_config->lpBinaryPathName[driver_path_len] == L'\\') + driver_path_len++; + wcscpy(service_image_path, service_config->lpBinaryPathName + driver_path_len); + } + else if (service_config->lpBinaryPathName[0] != L'\\') + { + /* ImagePath is relative to the NT SystemRoot namespace, convert to Win32 path via GetSystemWindowsDirectoryW()/GetSystemDirectoryW(). */ + systemroot_len = get_system_root_path(NULL, 0); /* Returns number of WCHARs plus 1 for nul-term. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * (systemroot_len + sizeof("\\")-1 + wcslen(service_config->lpBinaryPathName))); + systemroot_len = get_system_root_path(service_image_path, systemroot_len); /* Now it returns number of WCHARs without nul-term. */ + if (trim_system32 && systemroot_len && (trim_ptr = wcsrchr(service_image_path, L'\\')) != NULL) + systemroot_len = trim_ptr - service_image_path; + if (systemroot_len && service_image_path[systemroot_len-1] != L'\\') + service_image_path[systemroot_len++] = L'\\'; + wcscpy(service_image_path + systemroot_len, service_config->lpBinaryPathName); + } + else + { + /* ImagePath is in some unhandled NT namespace, copy it as is. It cannot be used in Win32 API but libpci user can be still interested in it. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * wcslen(service_config->lpBinaryPathName)); + wcscpy(service_image_path, service_config->lpBinaryPathName); + } + + /* Calculate len of buffer needed for conversion from LPWSTR to char*. */ + driver_path_len = WideCharToMultiByte(CP_ACP, 0, service_image_path, -1, NULL, 0, NULL, NULL); + if (driver_path_len <= 0) + { + error = GetLastError(); + a->warning("Cannot convert kernel driver path from wide string to 8-bit string: %s.", win32_strerror(error)); + goto out; + } + + driver_path = pci_malloc(a, driver_path_len); + driver_path_len = WideCharToMultiByte(CP_ACP, 0, service_image_path, -1, driver_path, driver_path_len, NULL, NULL); + if (driver_path_len <= 0) + { + error = GetLastError(); + a->warning("Cannot convert kernel driver path from wide string to 8-bit string: %s.", win32_strerror(error)); + pci_mfree(driver_path); + driver_path = NULL; + goto out; + } + +out: + if (service_image_path) + pci_mfree(service_image_path); + if (service_config) + pci_mfree(service_config); + if (service) + CloseServiceHandle(service); + return driver_path; +} + +static HKEY +get_device_driver_devreg(struct pci_access *a, DEVINST devinst, DEVINSTID_A devinst_id) +{ + CONFIGRET cr; + HKEY key; + + cr = CM_Open_DevNode_Key(devinst, KEY_READ, 0, RegDisposition_OpenExisting, &key, CM_REGISTRY_SOFTWARE); + if (cr != CR_SUCCESS) + { + if (cr != CR_NO_SUCH_VALUE) + a->warning("Cannot retrieve driver key for device %s: %s.", devinst_id, cr_strerror(cr)); + return NULL; + } + + return key; +} + +static char* +read_reg_key_string_value(struct pci_access *a, HKEY key, const char *name, DWORD *unkn_reg_type) +{ + DWORD reg_type, reg_size, reg_len; + char *value; + LONG error; + + reg_size = 0; + error = RegQueryValueExA(key, name, NULL, ®_type, NULL, ®_size); + if (error != ERROR_SUCCESS && + error != ERROR_MORE_DATA) + { + SetLastError(error); + return NULL; + } + else if (reg_type != REG_SZ) + { + SetLastError(0); + *unkn_reg_type = reg_type; + return NULL; + } + +retry: + value = pci_malloc(a, reg_size + 1); + reg_len = reg_size; + error = RegQueryValueExA(key, name, NULL, ®_type, (PBYTE)value, ®_len); + if (error != ERROR_SUCCESS) + { + pci_mfree(value); + if (error == ERROR_MORE_DATA) + { + reg_size = reg_len; + goto retry; + } + SetLastError(error); + return NULL; + } + else if (reg_type != REG_SZ) + { + pci_mfree(value); + SetLastError(0); + *unkn_reg_type = reg_type; + return NULL; + } + value[reg_len] = '\0'; + + return value; +} + +static int +driver_cmp(const char *driver, const char *match) +{ + int len = strlen(driver); + if (driver[0] == '*') + driver++; + if (len >= 4 && strcasecmp(driver + len - 4, ".vxd") == 0) + len -= 4; + return strncasecmp(driver, match, len); +} + +static char* +get_driver_path_for_regkey(struct pci_access *a, DEVINSTID_A devinst_id, HKEY key) +{ + char *driver_list, *driver, *driver_next; + char *subdriver, *subname; + char *driver_ptr; + char *driver_path; + DWORD unkn_reg_type; + UINT systemdir_len; + HKEY subkey; + LONG error; + BOOL vmm32; + BOOL noext; + int len; + + driver_list = read_reg_key_string_value(a, key, "DevLoader", &unkn_reg_type); + if (!driver_list) + { + error = GetLastError(); + if (error == 0) + a->warning("Cannot read driver DevLoader key for PCI device %s: DevLoader key is stored as unknown type 0x%lx.", devinst_id, unkn_reg_type); + else if (error != ERROR_FILE_NOT_FOUND) + a->warning("Cannot read driver DevLoader key for PCI device %s: %s.", devinst_id, win32_strerror(error)); + return NULL; + } + + subdriver = NULL; + driver = driver_list; + while (*driver) + { + driver_next = strchr(driver, ','); + if (driver_next) + *(driver_next++) = '\0'; + + if (driver_cmp(driver, "ios") == 0 || + driver_cmp(driver, "vcomm") == 0) + subname = "PortDriver"; + else if (driver_cmp(driver, "ntkern") == 0) + subname = "NTMPDriver"; + else if (driver_cmp(driver, "ndis") == 0) + subname = "DeviceVxDs"; + else if (driver_cmp(driver, "vdd") == 0) + subname = "minivdd"; + else + subname = NULL; + + subkey = key; + if (subname && strcmp(subname, "minivdd") == 0) + { + error = RegOpenKeyA(key, "Default", &subkey); + if (error != ERROR_SUCCESS) + { + a->warning("Cannot open driver subkey Default for PCI device %s: %s.", devinst_id, win32_strerror(error)); + subkey = NULL; + } + } + + if (!subname) + break; + + if (subkey) + { +retry_subname: + subdriver = read_reg_key_string_value(a, subkey, subname, &unkn_reg_type); + if (!subdriver) + { + error = GetLastError(); + if (error == 0) + a->warning("Cannot read driver %s key for PCI device %s: %s key is stored as unknown type 0x%lx.", subname, devinst_id, subname, unkn_reg_type); + else if (error != ERROR_FILE_NOT_FOUND) + a->warning("Cannot read driver %s key for PCI device %s: %s.", subname, devinst_id, win32_strerror(error)); + else if (strcmp(subname, "minivdd") == 0) + { + subname = "drv"; + goto retry_subname; + } + else if (strcmp(subname, "drv") == 0) + { + subname = "vdd"; + goto retry_subname; + } + } + + if (subkey != key) + RegCloseKey(subkey); + + if (subdriver) + { + char *endptr = strchr(subdriver, ','); + if (endptr) + *endptr = '\0'; + break; + } + } + + driver = driver_next; + } + + if (subdriver && subdriver[0]) + driver_ptr = subdriver; + else if (driver[0]) + driver_ptr = driver; + else + driver_ptr = NULL; + + if (driver_ptr && driver_ptr[0] == '*') + { + vmm32 = TRUE; + driver_ptr++; + } + else + vmm32 = FALSE; + + if (!driver_ptr[0]) + driver_ptr = NULL; + + len = driver_ptr ? strlen(driver_ptr) : 0; + noext = driver_ptr && (len < 4 || driver_ptr[len-4] != '.'); + + if (!driver_ptr) + driver_path = NULL; + else + { + if (tolower(driver_ptr[0]) >= 'a' && tolower(driver_ptr[0]) <= 'z' && driver_ptr[1] == ':') + { + /* Driver is already with absolute path. */ + driver_path = pci_strdup(a, driver_ptr); + } + else if (driver_cmp(driver, "ntkern") == 0 && subdriver) + { + /* Driver is relative to system32\drivers\ directory which is relative to windows directory. */ + systemdir_len = GetWindowsDirectoryA(NULL, 0); + driver_path = pci_malloc(a, systemdir_len + 1 + sizeof("system32\\drivers\\")-1 + strlen(driver_ptr) + 4 + 1); + systemdir_len = GetWindowsDirectoryA(driver_path, systemdir_len + 1); + if (systemdir_len && driver_path[systemdir_len - 1] != '\\') + driver_path[systemdir_len++] = '\\'; + sprintf(driver_path + systemdir_len, "system32\\drivers\\%s%s", driver_ptr, noext ? ".sys" : ""); + } + else if (vmm32) + { + /* Driver is packed in vmm32.vxd which is stored in system directory. */ + systemdir_len = GetSystemDirectoryA(NULL, 0); + driver_path = pci_malloc(a, systemdir_len + 1 + sizeof("vmm32.vxd ()")-1 + strlen(driver_ptr) + 4 + 1); + systemdir_len = GetSystemDirectoryA(driver_path, systemdir_len + 1); + if (systemdir_len && driver_path[systemdir_len - 1] != '\\') + driver_path[systemdir_len++] = '\\'; + sprintf(driver_path + systemdir_len, "vmm32.vxd (%s%s)", driver_ptr, noext ? ".vxd" : ""); + } + else + { + /* Otherwise driver is relative to system directory. */ + systemdir_len = GetSystemDirectoryA(NULL, 0); + driver_path = pci_malloc(a, systemdir_len + 1 + strlen(driver_ptr) + 4 + 1); + systemdir_len = GetSystemDirectoryA(driver_path, systemdir_len + 1); + if (systemdir_len && driver_path[systemdir_len - 1] != '\\') + driver_path[systemdir_len++] = '\\'; + sprintf(driver_path + systemdir_len, "%s%s", driver_ptr, noext ? ".vxd" : ""); + } + } + + if (subdriver) + pci_mfree(subdriver); + pci_mfree(driver_list); + return driver_path; +} + +static char * +get_device_driver_path(struct pci_dev *d, SC_HANDLE manager, BOOL manager_supported) +{ + struct pci_access *a = d->access; + BOOL service_supported = TRUE; + DEVINSTID_A devinst_id = NULL; + LPWSTR service_name = NULL; + ULONG devinst_id_len = 0; + char *driver_path = NULL; + DEVINST devinst = (DEVINST)d->backend_data; + ULONG problem = 0; + ULONG status = 0; + HKEY key = NULL; + + if (CM_Get_DevNode_Status(&status, &problem, devinst, 0) != CR_SUCCESS || !(status & DN_DRIVER_LOADED)) + return NULL; + + if (CM_Get_Device_ID_Size(&devinst_id_len, devinst, 0) == CR_SUCCESS) + { + devinst_id = pci_malloc(a, devinst_id_len + 1); + if (CM_Get_Device_IDA(devinst, devinst_id, devinst_id_len + 1, 0) != CR_SUCCESS) + { + pci_mfree(devinst_id); + devinst_id = pci_strdup(a, "UNKNOWN"); + } + } + else + devinst_id = pci_strdup(a, "UNKNOWN"); + + service_name = get_device_service_name(d->access, devinst, devinst_id, &service_supported); + if ((!service_name || !manager) && service_supported && manager_supported) + goto out; + else if (service_name && manager) + { + driver_path = get_driver_path_for_service(d->access, service_name, manager); + goto out; + } + + key = get_device_driver_devreg(d->access, devinst, devinst_id); + if (key) + { + driver_path = get_driver_path_for_regkey(d->access, devinst_id, key); + goto out; + } + +out: + if (key) + RegCloseKey(key); + if (service_name) + pci_mfree(service_name); + pci_mfree(devinst_id); + return driver_path; +} + +static void +fill_drivers(struct pci_access *a) +{ + BOOL manager_supported; + SC_HANDLE manager; + struct pci_dev *d; + char *driver; + DWORD error; + + /* ERROR_CALL_NOT_IMPLEMENTED is returned on systems without Service Manager support. */ + manager_supported = TRUE; + manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT); + if (!manager) + { + error = GetLastError(); + if (error != ERROR_CALL_NOT_IMPLEMENTED) + a->warning("Cannot open Service Manager with connect right: %s.", win32_strerror(error)); + else + manager_supported = FALSE; + } + + for (d = a->devices; d; d = d->next) + { + driver = get_device_driver_path(d, manager, manager_supported); + if (driver) + { + pci_set_property(d, PCI_FILL_DRIVER, driver); + pci_mfree(driver); + } + d->known_fields |= PCI_FILL_DRIVER; + } + + if (manager) + CloseServiceHandle(manager); +} + +static const char * +res_id_to_str(RESOURCEID res_id) +{ + static char hex_res_id[sizeof("0xffffffff")]; + + if (res_id == ResType_IO) + return "IO"; + else if (res_id == ResType_Mem) + return "MEM"; + else if (res_id == ResType_IRQ) + return "IRQ"; + + sprintf(hex_res_id, "0x%lx", res_id); + return hex_res_id; +} + +static void +fill_resources(struct pci_dev *d, DEVINST devinst, DEVINSTID_A devinst_id) +{ + struct pci_access *a = d->access; + + CONFIGRET cr; + + LOG_CONF config; + ULONG problem; + ULONG status; + + RES_DES prev_res_des; + RES_DES res_des; + RESOURCEID res_id; + DWORD bar_res_count; + + BOOL is_bar_res; + BOOL non_nt_system; + + int last_irq = -1; + int last_shared_irq = -1; + + cr = CM_Get_DevNode_Status(&status, &problem, devinst, 0); + if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve status of PCI device %s: %s.", devinst_id, cr_strerror(cr)); + return; + } + + cr = CR_NO_MORE_LOG_CONF; + + /* + * If the device is running then retrieve allocated configuration by PnP + * manager which is currently in use by a device. + */ + if (!(status & DN_HAS_PROBLEM)) + cr = CM_Get_First_Log_Conf(&config, devinst, ALLOC_LOG_CONF); + + /* + * If the device is not running or it does not have allocated configuration by + * PnP manager then retrieve forced configuration which prevents PnP manager + * from assigning resources. + */ + if (cr == CR_NO_MORE_LOG_CONF) + cr = CM_Get_First_Log_Conf(&config, devinst, FORCED_LOG_CONF); + + /* + * If the device does not have neither allocated configuration by PnP manager + * nor forced configuration and it is not disabled in the BIOS then retrieve + * boot configuration supplied by the BIOS. + */ + if (cr == CR_NO_MORE_LOG_CONF && + (!(status & DN_HAS_PROBLEM) || problem != CM_PROB_HARDWARE_DISABLED)) + cr = CM_Get_First_Log_Conf(&config, devinst, BOOT_LOG_CONF); + + if (cr != CR_SUCCESS) + { + /* + * Note: Starting with Windows 8, CM_Get_First_Log_Conf returns + * CR_CALL_NOT_IMPLEMENTED when used in a Wow64 scenario. + * To request information about the hardware resources on a local machine + * it is necessary implement an architecture-native version of the + * application using the hardware resource APIs. For example: An AMD64 + * application for AMD64 systems. + */ + if (cr == CR_CALL_NOT_IMPLEMENTED && win32_is_32bit_on_win8_64bit_system()) + { + static BOOL warn_once = FALSE; + if (!warn_once) + { + warn_once = TRUE; + a->warning("Cannot retrieve resources of PCI devices from 32-bit application on 64-bit system."); + } + } + else if (cr != CR_NO_MORE_LOG_CONF) + a->warning("Cannot retrieve resources of PCI device %s: %s.", devinst_id, cr_strerror(cr)); + return; + } + + bar_res_count = 0; + non_nt_system = win32_is_non_nt_system(); + + is_bar_res = TRUE; + if (non_nt_system) + { + BOOL has_child; + DEVINST child; + ULONG child_name_len; + PSTR child_name; + BOOL is_bridge; + + if (CM_Get_Child(&child, devinst, 0) != CR_SUCCESS) + has_child = FALSE; + else if (CM_Get_Device_ID_Size(&child_name_len, child, 0) != CR_SUCCESS) + has_child = FALSE; + else + { + child_name_len++; + child_name = pci_malloc(a, child_name_len); + if (CM_Get_Device_IDA(child, child_name, child_name_len, 0) != CR_SUCCESS) + has_child = FALSE; + else if (strncmp(child_name, "PCI\\", 4) != 0) + has_child = FALSE; + else + has_child = TRUE; + pci_mfree(child_name); + } + + if (has_child || d->device_class == PCI_CLASS_BRIDGE_PCI || d->device_class == PCI_CLASS_BRIDGE_CARDBUS) + is_bridge = TRUE; + else + is_bridge = FALSE; + + if (is_bridge) + is_bar_res = FALSE; + } + + prev_res_des = (RES_DES)config; + while ((cr = CM_Get_Next_Res_Des(&res_des, prev_res_des, ResType_All, &res_id, 0)) == CR_SUCCESS) + { + pciaddr_t start, end, size, flags; + ULONG res_des_data_size; + PBYTE res_des_data; + + if (prev_res_des != config) + CM_Free_Res_Des_Handle(prev_res_des); + + prev_res_des = res_des; + + /* Skip other resources early */ + if (res_id != ResType_IO && res_id != ResType_Mem && res_id != ResType_IRQ) + continue; + + cr = CM_Get_Res_Des_Data_Size(&res_des_data_size, res_des, 0); + if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve %s resource data of PCI device %s: %s.", res_id_to_str(res_id), devinst_id, cr_strerror(cr)); + continue; + } + + if (!res_des_data_size) + { + a->warning("Cannot retrieve %s resource data of PCI device %s: %s.", res_id_to_str(res_id), devinst_id, "Empty data"); + continue; + } + + res_des_data = pci_malloc(a, res_des_data_size); + cr = CM_Get_Res_Des_Data(res_des, res_des_data, res_des_data_size, 0); + if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve %s resource data of PCI device %s: %s.", res_id_to_str(res_id), devinst_id, cr_strerror(cr)); + pci_mfree(res_des_data); + continue; + } + + /* + * There can be more resources with the same id. In this case we are + * interested in the last one in the list as at the beginning of the list + * can be some virtual resources (which are not set in PCI config space). + */ + + if (res_id == ResType_IO) + { + PIO_RESOURCE io_data = (PIO_RESOURCE)res_des_data; + + start = io_data->IO_Header.IOD_Alloc_Base; + end = io_data->IO_Header.IOD_Alloc_End; + size = (end > start) ? (end - start + 1) : 0; + flags = PCI_IORESOURCE_IO; + + /* + * If neither 10-bit, 12-bit, nor 16-bit support is presented then + * expects that this is 32-bit I/O resource. If resource does not fit + * into 16-bit space then it must be 32-bit. If PCI I/O resource is + * not 32-bit then it is 16-bit. + */ + if (end <= 0xffff && (io_data->IO_Header.IOD_DesFlags & (fIOD_10_BIT_DECODE|fIOD_12_BIT_DECODE|fIOD_16_BIT_DECODE))) + flags |= PCI_IORESOURCE_IO_16BIT_ADDR; + + /* + * 16/32-bit non-NT systems do not support these two flags. + * Most NT-based Windows versions support only the fIOD_WINDOW_DECODE + * flag and put all BAR resources before window resources in this + * resource list. So use this fIOD_WINDOW_DECODE flag as separator + * between IO/MEM windows and IO/MEM BARs of PCI Bridges. + */ + if (io_data->IO_Header.IOD_DesFlags & fIOD_WINDOW_DECODE) + is_bar_res = FALSE; + else if (io_data->IO_Header.IOD_DesFlags & fIOD_PORT_BAR) + is_bar_res = TRUE; + + if (is_bar_res && bar_res_count < 6) + { + d->flags[bar_res_count] = flags; + d->base_addr[bar_res_count] = start; + d->size[bar_res_count] = size; + bar_res_count++; + } + else if (!is_bar_res) + { + d->bridge_flags[0] = flags; + d->bridge_base_addr[0] = start; + d->bridge_size[0] = size; + d->known_fields |= PCI_FILL_BRIDGE_BASES; + } + } + else if (res_id == ResType_Mem) + { + PMEM_RESOURCE mem_data = (PMEM_RESOURCE)res_des_data; + + start = mem_data->MEM_Header.MD_Alloc_Base; + end = mem_data->MEM_Header.MD_Alloc_End; + size = (end > start) ? (end - start + 1) : 0; + flags = PCI_IORESOURCE_MEM; + + /* + * If fMD_PrefetchAllowed flag is set then this is + * PCI Prefetchable Memory resource. + */ + if ((mem_data->MEM_Header.MD_Flags & mMD_Prefetchable) == fMD_PrefetchAllowed) + flags |= PCI_IORESOURCE_PREFETCH; + + /* If resource does not fit into 32-bit space then it must be 64-bit. */ + if (is_bar_res && end > 0xffffffff) + flags |= PCI_IORESOURCE_MEM_64; + + /* + * These two flags (fMD_WINDOW_DECODE and fMD_MEMORY_BAR) are + * unsupported on most Windows versions, so distinguish between + * window and BAR based on previous resource type. + */ + if (mem_data->MEM_Header.MD_Flags & fMD_WINDOW_DECODE) + is_bar_res = FALSE; + else if (mem_data->MEM_Header.MD_Flags & fMD_MEMORY_BAR) + is_bar_res = TRUE; + + /* 64-bit BAR resource must be at even position. */ + if (is_bar_res && (flags & PCI_IORESOURCE_MEM_64) && bar_res_count % 2) + bar_res_count++; + + if (is_bar_res && bar_res_count < 6) + { + d->flags[bar_res_count] = flags; + d->base_addr[bar_res_count] = start; + d->size[bar_res_count] = size; + bar_res_count++; + /* 64-bit BAR resource occupies two slots. */ + if (flags & PCI_IORESOURCE_MEM_64) + bar_res_count++; + } + else if (!is_bar_res && !(flags & PCI_IORESOURCE_PREFETCH)) + { + d->bridge_flags[1] = flags; + d->bridge_base_addr[1] = start; + d->bridge_size[1] = size; + d->known_fields |= PCI_FILL_BRIDGE_BASES; + } + else if (!is_bar_res && (flags & PCI_IORESOURCE_PREFETCH)) + { + d->bridge_flags[2] = flags; + d->bridge_base_addr[2] = start; + d->bridge_size[2] = size; + d->known_fields |= PCI_FILL_BRIDGE_BASES; + } + } + else if (res_id == ResType_IRQ) + { + PIRQ_RESOURCE irq_data = (PIRQ_RESOURCE)res_des_data; + + /* + * libpci's d->irq should be set to the non-MSI PCI IRQ and therefore + * it should be level IRQ which may be shared with other PCI devices + * and drivers in the system. As always we want to retrieve the last + * IRQ number from the resource list. + * + * On 16/32-bit non-NT systems is fIRQD_Level set to 2 but on NT + * systems to 0. Moreover it looks like that different PCI drivers + * on both NT and non-NT systems set bits 0 and 1 to wrong values + * and so reported value in this list may be incorrect. + * + * Therefore take the last level-shared IRQ number from the resource + * list and if there is none of this type then take the last IRQ + * number from the list. + */ + last_irq = irq_data->IRQ_Header.IRQD_Alloc_Num; + if ((irq_data->IRQ_Header.IRQD_Flags & (mIRQD_Share|mIRQD_Edge_Level)) == (fIRQD_Share|fIRQD_Level)) + last_shared_irq = irq_data->IRQ_Header.IRQD_Alloc_Num; + + /* + * IRQ resource on 16/32-bit non-NT systems is separator between + * IO/MEM windows and IO/MEM BARs of PCI Bridges. After the IRQ + * resource are IO/MEM BAR resources. + */ + if (!is_bar_res && non_nt_system) + is_bar_res = TRUE; + } + + pci_mfree(res_des_data); + } + if (cr != CR_NO_MORE_RES_DES) + a->warning("Cannot retrieve resources of PCI device %s: %s.", devinst_id, cr_strerror(cr)); + + if (prev_res_des != config) + CM_Free_Res_Des_Handle(prev_res_des); + + CM_Free_Log_Conf_Handle(config); + + /* Set the last IRQ from the resource list to pci_dev. */ + if (last_shared_irq >= 0) + d->irq = last_shared_irq; + else if (last_irq >= 0) + d->irq = last_irq; + if (last_shared_irq >= 0 || last_irq >= 0) + d->known_fields |= PCI_FILL_IRQ; + + if (bar_res_count > 0) + d->known_fields |= PCI_FILL_BASES | PCI_FILL_SIZES | PCI_FILL_IO_FLAGS; +} + +static BOOL +get_device_location(struct pci_access *a, DEVINST devinst, DEVINSTID_A devinst_id, unsigned int *domain, unsigned int *bus, unsigned int *dev, unsigned int *func) +{ + ULONG reg_type, reg_len; + CONFIGRET cr; + BOOL have_bus, have_devfunc; + DWORD drp_bus_num, drp_address; + + *domain = 0; + have_bus = FALSE; + have_devfunc = FALSE; + + /* + * DRP_BUSNUMBER consists of PCI domain number in high 24 bits + * and PCI bus number in low 8 bits. + */ + reg_len = sizeof(drp_bus_num); + cr = CM_Get_DevNode_Registry_PropertyA(devinst, CM_DRP_BUSNUMBER, ®_type, &drp_bus_num, ®_len, 0); + if (cr == CR_SUCCESS && reg_type == REG_DWORD && reg_len == sizeof(drp_bus_num)) + { + *domain = drp_bus_num >> 8; + *bus = drp_bus_num & 0xff; + have_bus = TRUE; + } + + /* + * DRP_ADDRESS consists of PCI device number in high 16 bits + * and PCI function number in low 16 bits. + */ + reg_len = sizeof(drp_address); + cr = CM_Get_DevNode_Registry_PropertyA(devinst, CM_DRP_ADDRESS, ®_type, &drp_address, ®_len, 0); + if (cr == CR_SUCCESS && reg_type == REG_DWORD && reg_len == sizeof(drp_address)) + { + *dev = drp_address >> 16; + *func = drp_address & 0xffff; + have_devfunc = TRUE; + } + + /* + * Device Instance Id for PCI devices is of format: + * "<enumerator>\\<device_id>\\<instance_id>" + * where: + * "<enumerator>" is "PCI" + * "<device_id>" is "VEN_####&DEV_####&SUBSYS_########&REV_##" + * and "<instance_id>" for PCI devices is at least in one of following format: + * "BUS_##&DEV_##&FUNC_##" + * "##.." (sequence of devfn hex bytes, where bytes represents tree path to the root) + * "#..&#..&#..&#.." (four hex numbers separated by "&"; meaning is unknown yet) + * + * First two formats are used only on systems without support for multiple + * domains. The second format uses intel-conf encoding of device and function + * number: Low 3 bits is function number and high 5 bits is device number. + * Bus number is not really encoded in second format! + * + * The third format is used on systems with support for multiple domains but + * format is variable length and currently its meaning is unknown. Apparently + * it looks like that DRP_BUSNUMBER and DRP_ADDRESS registry properties are + * supported on these systems. + * + * If DRP_BUSNUMBER or DRP_ADDRESS failed then try to parse PCI bus, device + * and function numbers from Instance Id part. + */ + if (!have_bus || !have_devfunc) + { + const char *device_id0 = strchr(devinst_id, '\\'); + const char *instance_id0 = device_id0 ? strchr(device_id0 + 1, '\\') : NULL; + const char *instance_id = instance_id0 ? instance_id0 + 1 : NULL; + unsigned int devfn; + + if (instance_id) + { + if (fmt_validate(instance_id, strlen(instance_id), "BUS_##&DEV_##&FUNC_##") && + sscanf(instance_id, "BUS_%x&DEV_%x&FUNC_%x", bus, dev, func) == 3) + { + have_bus = TRUE; + have_devfunc = TRUE; + } + else if (seq_xdigit_validate(instance_id, 2, 2) && + sscanf(instance_id, "%2x", &devfn) == 1) + { + *dev = devfn >> 3; + *func = devfn & 0x7; + have_devfunc = TRUE; + } + } + } + + /* + * Virtual IRQ holder devices do not have assigned any bus/dev/func number and + * have "IRQHOLDER" in their Device Id part. So skip them. + */ + if (!have_bus && !have_devfunc && strncmp(devinst_id, "PCI\\IRQHOLDER\\", 14) == 0) + return FALSE; + + /* + * When some numbers cannot be retrieved via cfgmgr32 then set them to zeros + * to have structure initialized. It makes sense to report via libpci also + * such "incomplete" device as cfgmgr32 can provide additional information + * like device/vendor ids or assigned resources. + */ + if (!have_bus && !have_devfunc) + { + *bus = *dev = *func = 0; + a->warning("Cannot retrieve bus, device and function numbers for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + } + else if (!have_bus) + { + *bus = 0; + a->warning("Cannot retrieve bus number for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + } + else if (!have_devfunc) + { + *dev = *func = 0; + a->warning("Cannot retrieve device and function numbers for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + } + + return TRUE; +} + +static void +fill_data_from_string(struct pci_dev *d, const char *str) +{ + BOOL have_device_id; + BOOL have_vendor_id; + BOOL have_prog_if; + BOOL have_rev_id; + const char *endptr, *endptr2; + unsigned int hex; + int len; + + have_device_id = have_vendor_id = (d->known_fields & PCI_FILL_IDENT); + have_prog_if = have_rev_id = (d->known_fields & PCI_FILL_CLASS_EXT); + + while (1) + { + endptr = strchr(str, '&'); + endptr2 = strchr(str, '\\'); + if (endptr2 && (!endptr || endptr > endptr2)) + endptr = endptr2; + len = endptr ? endptr-str : (int)strlen(str); + + if (!have_vendor_id && + fmt_validate(str, len, "VEN_####") && + sscanf(str, "VEN_%x", &hex) == 1) + { + d->vendor_id = hex; + have_vendor_id = TRUE; + } + else if (!have_device_id && + fmt_validate(str, len, "DEV_####") && + sscanf(str, "DEV_%x", &hex) == 1) + { + d->device_id = hex; + have_device_id = TRUE; + } + else if (!(d->known_fields & PCI_FILL_SUBSYS) && + fmt_validate(str, len, "SUBSYS_########") && + sscanf(str, "SUBSYS_%x", &hex) == 1) + { + d->subsys_vendor_id = hex & 0xffff; + d->subsys_id = hex >> 16; + d->known_fields |= PCI_FILL_SUBSYS; + } + else if (!have_rev_id && + fmt_validate(str, len, "REV_##") && + sscanf(str, "REV_%x", &hex) == 1) + { + d->rev_id = hex; + have_rev_id = TRUE; + } + else if (!((d->known_fields & PCI_FILL_CLASS) && have_prog_if) && + (fmt_validate(str, len, "CC_####") || fmt_validate(str, len, "CC_######")) && + sscanf(str, "CC_%x", &hex) == 1) + { + if (len == 9) + { + if (!have_prog_if) + { + d->prog_if = hex & 0xff; + have_prog_if = TRUE; + } + hex >>= 8; + } + if (!(d->known_fields & PCI_FILL_CLASS)) + { + d->device_class = hex; + d->known_fields |= PCI_FILL_CLASS; + } + } + + if (!endptr || endptr == endptr2) + break; + + str = endptr + 1; + } + + if ((have_device_id || d->device_id) && (have_vendor_id || d->vendor_id)) + d->known_fields |= PCI_FILL_IDENT; + + if ((have_prog_if || d->prog_if) && (have_rev_id || d->rev_id)) + d->known_fields |= PCI_FILL_CLASS_EXT; +} + +static void +fill_data_from_devinst_id(struct pci_dev *d, DEVINSTID_A devinst_id) +{ + const char *device_id; + + device_id = strchr(devinst_id, '\\'); + if (!device_id) + return; + device_id++; + + /* + * Device Id part of Device Instance Id is in format: + * "VEN_####&DEV_####&SUBSYS_########&REV_##" + */ + fill_data_from_string(d, device_id); +} + +static void +fill_data_from_hardware_ids(struct pci_dev *d, DEVINST devinst, DEVINSTID_A devinst_id) +{ + ULONG reg_type, reg_size, reg_len; + struct pci_access *a = d->access; + char *hardware_ids = NULL; + const char *str; + CONFIGRET cr; + + reg_size = 0; + cr = CM_Get_DevNode_Registry_PropertyA(devinst, CM_DRP_HARDWAREID, ®_type, NULL, ®_size, 0); + if (cr != CR_SUCCESS && cr != CR_BUFFER_SMALL) + { + a->warning("Cannot retrieve hardware ids for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + return; + } + else if (reg_type != REG_MULTI_SZ && reg_type != REG_SZ) /* Older Windows versions return REG_SZ and new versions REG_MULTI_SZ. */ + { + a->warning("Cannot retrieve hardware ids for PCI device %s: Hardware ids are stored as unknown type 0x%lx.", devinst_id, reg_type); + return; + } + +retry: + /* + * Returned size is on older Windows versions without nul-term char. + * So explicitly increase size and fill nul-term byte. + */ + reg_size++; + hardware_ids = pci_malloc(a, reg_size); + reg_len = reg_size; + cr = CM_Get_DevNode_Registry_PropertyA(devinst, CM_DRP_HARDWAREID, ®_type, hardware_ids, ®_len, 0); + hardware_ids[reg_size - 1] = 0; + if (reg_len > reg_size) + { + pci_mfree(hardware_ids); + reg_size = reg_len; + goto retry; + } + else if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve hardware ids for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + pci_mfree(hardware_ids); + return; + } + else if (reg_type != REG_MULTI_SZ && reg_type != REG_SZ) /* Older Windows versions return REG_SZ and new versions REG_MULTI_SZ. */ + { + a->warning("Cannot retrieve hardware ids for PCI device %s: Hardware ids are stored as unknown type 0x%lx.", devinst_id, reg_type); + pci_mfree(hardware_ids); + return; + } + + /* + * Hardware ids is nul-separated nul-term string list where each string has + * one of the following format: + * "PCI\\VEN_####&DEV_####&SUBSYS_########&REV_##" + * "PCI\\VEN_####&DEV_####&SUBSYS_########" + * "PCI\\VEN_####&DEV_####&REV_##&CC_####" + * "PCI\\VEN_####&DEV_####&CC_######" + * "PCI\\VEN_####&DEV_####&CC_####" + * "PCI\\VEN_####&DEV_####&REV_##" + * "PCI\\VEN_####&DEV_####" + */ + for (str = hardware_ids; *str != '\0'; str += strlen(str) + 1) + { + if (strncmp(str, "PCI\\", 4) != 0) + continue; + str += 4; + fill_data_from_string(d, str); + } + + pci_mfree(hardware_ids); +} + +static void +scan_devinst_id(struct pci_access *a, DEVINSTID_A devinst_id) +{ + unsigned int domain, bus, dev, func; + struct pci_dev *d; + DEVINST devinst; + CONFIGRET cr; + + cr = CM_Locate_DevNodeA(&devinst, devinst_id, CM_LOCATE_DEVNODE_NORMAL); + if (cr != CR_SUCCESS) + { + /* Do not show warning when device is not present (= does not match NORMAL flag). */ + if (cr != CR_NO_SUCH_DEVNODE) + a->warning("Cannot retrieve handle for device %s: %s.", devinst_id, cr_strerror(cr)); + return; + } + + /* get_device_location() returns FALSE if devinst is not real PCI device. */ + if (!get_device_location(a, devinst, devinst_id, &domain, &bus, &dev, &func)) + return; + + d = pci_get_dev(a, domain, bus, dev, func); + pci_link_dev(a, d); + if (!d->access->backend_data) + d->no_config_access = 1; + d->backend_data = (void *)devinst; + + /* Parse device id part of devinst id and fill details into pci_dev. */ + if (!a->buscentric) + fill_data_from_devinst_id(d, devinst_id); + + /* Retrieve hardware ids of devinst, parse them and fill details into pci_dev. */ + if (!a->buscentric) + fill_data_from_hardware_ids(d, devinst, devinst_id); + + if (!a->buscentric) + fill_resources(d, devinst, devinst_id); + + /* + * Set parent field to cfgmgr32 parent devinst handle and backend_data field to current + * devinst handle. At later stage in win32_cfgmgr32_scan() when all pci_dev + * devices are linked, change every devinst handle by pci_dev. + */ + if (!a->buscentric) + { + DEVINST parent_devinst; + if (CM_Get_Parent(&parent_devinst, devinst, 0) != CR_SUCCESS) + { + parent_devinst = 0; + a->warning("Cannot retrieve parent handle for device %s: %s.", devinst_id, cr_strerror(cr)); + } + d->parent = (void *)parent_devinst; + } +} + +static void +win32_cfgmgr32_scan(struct pci_access *a) +{ + ULONG devinst_id_list_size; + PCHAR devinst_id_list; + DEVINSTID_A devinst_id; + struct pci_dev *d; + CONFIGRET cr; + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + if (!resolve_cfgmgr32_functions()) + { + a->warning("Required cfgmgr32.dll functions are unavailable."); + return; + } +#endif + + /* + * Explicitly initialize size to zero as wine cfgmgr32 implementation does not + * support this API but returns CR_SUCCESS without touching size argument. + */ + devinst_id_list_size = 0; + cr = CM_Get_Device_ID_List_SizeA(&devinst_id_list_size, "PCI", CM_GETIDLIST_FILTER_ENUMERATOR); + if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve list of PCI devices: %s.", cr_strerror(cr)); + return; + } + else if (devinst_id_list_size <= 1) + { + a->warning("Cannot retrieve list of PCI devices: No device was found."); + return; + } + + devinst_id_list = pci_malloc(a, devinst_id_list_size); + cr = CM_Get_Device_ID_ListA("PCI", devinst_id_list, devinst_id_list_size, CM_GETIDLIST_FILTER_ENUMERATOR); + if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve list of PCI devices: %s.", cr_strerror(cr)); + pci_mfree(devinst_id_list); + return; + } + + /* Register pci_dev for each cfgmgr32 devinst handle. */ + for (devinst_id = devinst_id_list; *devinst_id; devinst_id += strlen(devinst_id) + 1) + scan_devinst_id(a, devinst_id); + + /* Fill all drivers. */ + if (!a->buscentric) + fill_drivers(a); + + /* Switch parent fields from cfgmgr32 devinst handle to pci_dev. */ + if (!a->buscentric) + { + struct pci_dev *d1, *d2; + for (d1 = a->devices; d1; d1 = d1->next) + { + for (d2 = a->devices; d2; d2 = d2->next) + if ((DEVINST)d1->parent == (DEVINST)d2->backend_data) + break; + d1->parent = d2; + if (d1->parent) + d1->known_fields |= PCI_FILL_PARENT; + } + } + + /* devinst stored in ->backend_data is not needed anymore, clear it. */ + for (d = a->devices; d; d = d->next) + d->backend_data = NULL; + + pci_mfree(devinst_id_list); +} + +static void +win32_cfgmgr32_config(struct pci_access *a) +{ + pci_define_param(a, "win32.cfgmethod", "auto", "PCI config space access method"); +} + +static int +win32_cfgmgr32_detect(struct pci_access *a) +{ + ULONG devinst_id_list_size; + CONFIGRET cr; + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + if (!resolve_cfgmgr32_functions()) + { + a->debug("Required cfgmgr32.dll functions are unavailable."); + return 0; + } +#endif + + /* + * Explicitly initialize size to zero as wine cfgmgr32 implementation does not + * support this API but returns CR_SUCCESS without touching size argument. + */ + devinst_id_list_size = 0; + cr = CM_Get_Device_ID_List_SizeA(&devinst_id_list_size, "PCI", CM_GETIDLIST_FILTER_ENUMERATOR); + if (cr != CR_SUCCESS) + { + a->debug("CM_Get_Device_ID_List_SizeA(\"PCI\"): %s.", cr_strerror(cr)); + return 0; + } + else if (devinst_id_list_size <= 1) + { + a->debug("CM_Get_Device_ID_List_SizeA(\"PCI\"): No device was found."); + return 0; + } + + return 1; +} + +static void +win32_cfgmgr32_fill_info(struct pci_dev *d, unsigned int flags) +{ + /* + * All available flags were filled by win32_cfgmgr32_scan(). + * Filling more flags is possible only from config space. + */ + if (!d->access->backend_data) + return; + + pci_generic_fill_info(d, flags); +} + +static int +win32_cfgmgr32_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + struct pci_access *a = d->access; + struct pci_access *acfg = a->backend_data; + struct pci_dev *dcfg = d->backend_data; + + if (!acfg) + return pci_emulated_read(d, pos, buf, len); + + if (!dcfg) + d->backend_data = dcfg = pci_get_dev(acfg, d->domain, d->bus, d->dev, d->func); + + return pci_read_block(dcfg, pos, buf, len); +} + +static int +win32_cfgmgr32_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + struct pci_access *a = d->access; + struct pci_access *acfg = a->backend_data; + struct pci_dev *dcfg = d->backend_data; + + if (!acfg) + return 0; + + if (!dcfg) + d->backend_data = dcfg = pci_get_dev(acfg, d->domain, d->bus, d->dev, d->func); + + return pci_write_block(dcfg, pos, buf, len); +} + +static void +win32_cfgmgr32_cleanup_dev(struct pci_dev *d) +{ + struct pci_dev *dcfg = d->backend_data; + + if (dcfg) + pci_free_dev(dcfg); +} + +static void +win32_cfgmgr32_init(struct pci_access *a) +{ + char *cfgmethod = pci_get_param(a, "win32.cfgmethod"); + struct pci_access *acfg; + + if (strcmp(cfgmethod, "") == 0 || + strcmp(cfgmethod, "auto") == 0) + { + acfg = pci_clone_access(a); + acfg->method = PCI_ACCESS_AUTO; + } + else if (strcmp(cfgmethod, "none") == 0 || + strcmp(cfgmethod, "win32-cfgmgr32") == 0) + { + if (a->writeable) + a->error("Write access requested but option win32.cfgmethod was not set."); + return; + } + else + { + int m = pci_lookup_method(cfgmethod); + if (m < 0) + a->error("Option win32.cfgmethod is set to an unknown access method \"%s\".", cfgmethod); + acfg = pci_clone_access(a); + acfg->method = m; + } + + a->debug("Loading config space access method...\n"); + if (!pci_init_internal(acfg, PCI_ACCESS_WIN32_CFGMGR32)) + { + pci_cleanup(acfg); + a->debug("Cannot find any working config space access method.\n"); + if (a->writeable) + a->error("Write access requested but no usable access method found."); + return; + } + + a->backend_data = acfg; +} + +static void +win32_cfgmgr32_cleanup(struct pci_access *a) +{ + struct pci_access *acfg = a->backend_data; + + if (acfg) + pci_cleanup(acfg); +} + +struct pci_methods pm_win32_cfgmgr32 = { + "win32-cfgmgr32", + "Win32 device listing via Configuration Manager", + win32_cfgmgr32_config, + win32_cfgmgr32_detect, + win32_cfgmgr32_init, + win32_cfgmgr32_cleanup, + win32_cfgmgr32_scan, + win32_cfgmgr32_fill_info, + win32_cfgmgr32_read, + win32_cfgmgr32_write, + NULL, /* read_vpd */ + NULL, /* init_dev */ + win32_cfgmgr32_cleanup_dev, +}; |