From: Harald Sitter sitter@kde.org
This is a bit convoluted because of the amount of pieces involved. I'll try to untangle things a bit.
The container id is meant to be globally unique and stable. We therefore need a suitable amount of stable entropy that we can feed into a hash function to get a stable and unique output.
This happens in winebus. The corner stone of hash inputs is the linux device topology (sysfs path) obtained via udev. Along with all other possible data we feed this into a hash function to obtain our container id.
A bunch of extra wine-specific keys needed to be added to carry the necessary metadata from the driver into the ntoskrnl (where it gets written to the property registry of the relevant device).
Separate from this we also ingest the linux device topology (sysfs path) in the winepulse driver.
In mmdevapi we then use winepulse's sysfs path to find the matching device in the setupapi and query its container id. We now have the same container id in both the device and the MMDevice, allowing applications (in particular Games) to find the MMDevice for a given device.
For practical reasons we always resolve the sysfs path to the root device as early as possible. Container ids are meant to be equal for sub devices inside a "container" anyway. --- dlls/hidclass.sys/hid.h | 1 + dlls/hidclass.sys/pnp.c | 28 +++++++ dlls/mmdevapi/Makefile.in | 2 +- dlls/mmdevapi/devenum.c | 143 ++++++++++++++++++++++++++++++-- dlls/ntoskrnl.exe/pnp.c | 20 ++++- dlls/setupapi/devinst.c | 2 + dlls/winebus.sys/Makefile.in | 2 +- dlls/winebus.sys/bus_udev.c | 119 ++++++++++++++++++++++++++ dlls/winebus.sys/main.c | 156 +++++++++++++++++++++++++++++++++++ dlls/winebus.sys/unixlib.h | 2 + dlls/winepulse.drv/pulse.c | 32 +++++++ dlls/wineusb.sys/wineusb.c | 8 ++ include/Makefile.in | 1 + include/ddk/wdm.h | 1 + include/setupapi.h | 1 + include/wine/setupapi.h | 25 ++++++ 16 files changed, 528 insertions(+), 15 deletions(-) create mode 100644 include/wine/setupapi.h
diff --git a/dlls/hidclass.sys/hid.h b/dlls/hidclass.sys/hid.h index 3924c72dcc3..de8cda3074a 100644 --- a/dlls/hidclass.sys/hid.h +++ b/dlls/hidclass.sys/hid.h @@ -87,6 +87,7 @@ typedef struct _BASE_DEVICE_EXTENSION WCHAR instance_id[MAX_DEVICE_ID_LEN]; WCHAR container_id[MAX_GUID_STRING_LEN]; const GUID *class_guid; + WCHAR sysfs_path[MAX_PATH];
BOOL is_fdo; } BASE_DEVICE_EXTENSION; diff --git a/dlls/hidclass.sys/pnp.c b/dlls/hidclass.sys/pnp.c index 156b9a65f9f..5ba08248581 100644 --- a/dlls/hidclass.sys/pnp.c +++ b/dlls/hidclass.sys/pnp.c @@ -171,6 +171,13 @@ static NTSTATUS WINAPI driver_add_device(DRIVER_OBJECT *driver, DEVICE_OBJECT *b if (get_device_id(bus_pdo, BusQueryContainerID, ext->container_id)) ext->container_id[0] = 0;
+ if (get_device_id(bus_pdo, WineBusQuerySysfsPath, ext->sysfs_path)) { + TRACE("Failed to get sysfs_path.\n"); + ext->sysfs_path[0] = 0; + } else { + TRACE("sysfs_path %s\n", debugstr_w(ext->sysfs_path)); + } + is_xinput_class = !wcsncmp(device_id, L"WINEXINPUT\", 7) && wcsstr(device_id, L"&XI_") != NULL; if (is_xinput_class) ext->class_guid = &GUID_DEVINTERFACE_WINEXINPUT; else ext->class_guid = &GUID_DEVINTERFACE_HID; @@ -304,6 +311,7 @@ static NTSTATUS create_child_pdos( minidriver *minidriver, DEVICE_OBJECT *device wcscpy( pdo_ext->instance_id, fdo_ext->instance_id ); } wcscpy(pdo_ext->container_id, fdo_ext->container_id); + wcscpy(pdo_ext->sysfs_path, fdo_ext->sysfs_path); pdo_ext->class_guid = fdo_ext->class_guid;
pdo_ext->u.pdo.information.VendorID = fdo_ext->u.fdo.attrs.VendorID; @@ -471,6 +479,18 @@ static WCHAR *query_container_id(DEVICE_OBJECT *device) return dst; }
+static WCHAR *query_sysfs_path(DEVICE_OBJECT *device) +{ + BASE_DEVICE_EXTENSION *ext = device->DeviceExtension; + DWORD size = (wcslen(ext->sysfs_path) + 1) * sizeof(WCHAR); + WCHAR *dst; + + if ((dst = ExAllocatePool(PagedPool, size))) + memcpy(dst, ext->sysfs_path, size); + + return dst; +} + static NTSTATUS pdo_pnp(DEVICE_OBJECT *device, IRP *irp) { IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation(irp); @@ -517,6 +537,14 @@ static NTSTATUS pdo_pnp(DEVICE_OBJECT *device, IRP *irp) else status = STATUS_SUCCESS; } break; + case WineBusQuerySysfsPath: + if (ext->sysfs_path[0]) + { + irp->IoStatus.Information = (ULONG_PTR)query_sysfs_path(device); + if (!irp->IoStatus.Information) status = STATUS_NO_MEMORY; + else status = STATUS_SUCCESS; + } + break; default: WARN("IRP_MN_QUERY_ID type %u, not implemented!\n", type); break; diff --git a/dlls/mmdevapi/Makefile.in b/dlls/mmdevapi/Makefile.in index 90c2302ef50..1c38f3bd0a0 100644 --- a/dlls/mmdevapi/Makefile.in +++ b/dlls/mmdevapi/Makefile.in @@ -1,5 +1,5 @@ MODULE = mmdevapi.dll -IMPORTS = ole32 oleaut32 user32 advapi32 version +IMPORTS = ole32 oleaut32 user32 advapi32 version setupapi shlwapi
SOURCES = \ audiosessionmanager.c \ diff --git a/dlls/mmdevapi/devenum.c b/dlls/mmdevapi/devenum.c index b1993573dd9..1d81de72aa4 100644 --- a/dlls/mmdevapi/devenum.c +++ b/dlls/mmdevapi/devenum.c @@ -18,6 +18,8 @@
#include <stdarg.h> #include <wchar.h> +#include <limits.h> +#include <stdlib.h>
#define COBJMACROS #include "windef.h" @@ -26,6 +28,7 @@ #include "winreg.h" #include "wine/debug.h" #include "wine/list.h" +#include "wine/setupapi.h"
#include "initguid.h" #include "ole2.h" @@ -36,6 +39,9 @@ #include "endpointvolume.h" #include "audiopolicy.h" #include "spatialaudioclient.h" +#include "setupapi.h" +#include "ddk/hidclass.h" +#include "shlwapi.h"
#include "mmdevapi_private.h" #include "devpkey.h" @@ -47,6 +53,13 @@ DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0); static HKEY key_render; static HKEY key_capture;
+struct clsid_blob { + BYTE vt; + BYTE wReserved0[3]; // Unknown metadata, maybe flags? + DWORD wReserved1; // Possibly a version of some sort? + GUID puuid; +}; + typedef struct MMDevPropStoreImpl { IPropertyStore IPropertyStore_iface; @@ -196,15 +209,28 @@ static HRESULT MMDevice_GetPropValue(const GUID *devguid, DWORD flow, REFPROPERT RegGetValueW(regkey, NULL, buffer, RRF_RT_REG_DWORD, NULL, (BYTE*)&pv->ulVal, &size); break; } - case REG_BINARY: - { - pv->vt = VT_BLOB; - pv->blob.cbSize = size; - pv->blob.pBlobData = CoTaskMemAlloc(size); - if (!pv->blob.pBlobData) - hr = E_OUTOFMEMORY; - else - RegGetValueW(regkey, NULL, buffer, RRF_RT_REG_BINARY, NULL, (BYTE*)pv->blob.pBlobData, &size); + case REG_BINARY: { + if (IsEqualPropertyKey(*key, DEVPKEY_Device_ContainerId)) { + struct clsid_blob blob; + DWORD size = sizeof(blob); + + RegGetValueW(regkey, NULL, buffer, RRF_RT_REG_BINARY, NULL, (BYTE *)&blob, &size); + + pv->vt = VT_CLSID; + pv->puuid = CoTaskMemAlloc(sizeof(GUID)); + if (!pv->puuid) + hr = E_OUTOFMEMORY; + else + *pv->puuid = blob.puuid; + } else { + pv->vt = VT_BLOB; + pv->blob.cbSize = size; + pv->blob.pBlobData = CoTaskMemAlloc(size); + if (!pv->blob.pBlobData) + hr = E_OUTOFMEMORY; + else + RegGetValueW(regkey, NULL, buffer, RRF_RT_REG_BINARY, NULL, (BYTE *)pv->blob.pBlobData, &size); + } break; } default: @@ -249,6 +275,11 @@ static HRESULT MMDevice_SetPropValue(const GUID *devguid, DWORD flow, REFPROPERT ret = RegSetValueExW(regkey, buffer, 0, REG_SZ, (const BYTE*)pv->pwszVal, sizeof(WCHAR)*(1+lstrlenW(pv->pwszVal))); break; } + case VT_CLSID: { + struct clsid_blob blob = {.vt = VT_CLSID, .wReserved0 = {0, 0, 0}, .wReserved1 = 0, .puuid = *pv->puuid}; + ret = RegSetValueExW(regkey, buffer, 0, REG_BINARY, (const BYTE *)&blob, sizeof(blob)); + break; + } default: ret = 0; FIXME("Unhandled type %u\n", pv->vt); @@ -331,6 +362,84 @@ static const WCHAR *find_product_name_override(const WCHAR *device_id) return NULL; }
+static void set_container_id_for_devinfo(GUID *id, EDataFlow flow, HDEVINFO device_info_set, SP_DEVINFO_DATA *devinfo) +{ + DWORD property_reg_data_type = 0; + WCHAR property_buffer[MAX_PATH]; + DWORD required_size = 0; + PROPVARIANT pv; + UNICODE_STRING guid_str; + + PropVariantInit(&pv); + + if (!SetupDiGetDeviceRegistryPropertyW(device_info_set, + devinfo, + SPDRP_BASE_CONTAINERID, + &property_reg_data_type, + (PBYTE)property_buffer, + MAX_PATH, + &required_size)) { + return; + } + + pv.vt = VT_CLSID; + pv.puuid = CoTaskMemAlloc(sizeof(GUID)); + if (!pv.puuid) { + WARN("Failed to allocate memory for container id\n"); + goto finish; + } + + RtlInitUnicodeString(&guid_str, property_buffer); + if (!NT_SUCCESS(RtlGUIDFromString(&guid_str, pv.puuid))) { + WARN("Failed to parse container id\n"); + goto finish; + } + + if (SUCCEEDED(MMDevice_SetPropValue(id, flow, (PROPERTYKEY *)&DEVPKEY_Device_ContainerId, &pv))) { + TRACE("Container id set\n"); + } else { + WARN("Failed to set container id\n"); + } + +finish: + PropVariantClear(&pv); +} + +static void set_container_id_for_sysfs_path(GUID *id, EDataFlow flow, LPWSTR audio_sysfs_path) +{ + HDEVINFO device_info_set = INVALID_HANDLE_VALUE; + SP_DEVINFO_DATA devinfo; + + memset(&devinfo, 0, sizeof(SP_DEVINFO_DATA)); + devinfo.cbSize = sizeof(SP_DEVINFO_DATA); + + device_info_set = SetupDiGetClassDevsW(NULL, NULL, NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + for (DWORD i = 0; SetupDiEnumDeviceInfo(device_info_set, i, &devinfo); i++) { + DWORD property_reg_data_type = 0; + WCHAR property_buffer[MAX_PATH]; + DWORD required_size = 0; + + if (SetupDiGetDeviceRegistryPropertyW(device_info_set, + &devinfo, + WINE_SPDRP_SYSFS_PATH, + &property_reg_data_type, + (PBYTE)property_buffer, + MAX_PATH, + &required_size)) { + CHAR device_path[MAX_PATH]; + CHAR audio_device_path[MAX_PATH]; + + wcstombs((CHAR *)&audio_device_path, audio_sysfs_path, wcslen(audio_sysfs_path) + 1); + wcstombs((CHAR *)&device_path, property_buffer, wcslen(property_buffer) + 1); + + if (strlen(device_path) > 0 && strncmp(device_path, audio_device_path, strlen(device_path)) == 0) { + set_container_id_for_devinfo(id, flow, device_info_set, &devinfo); + break; + } + } + } +} + /* Creates or updates the state of a device * If GUID is null, a random guid will be assigned * and the device will be created @@ -349,6 +458,13 @@ static MMDevice *MMDevice_Create(const WCHAR *name, GUID *id, EDataFlow flow, DW {0xb3f8fa53, 0x0004, 0x438e, {0x90, 0x03, 0x51, 0xa4, 0x6e, 0x13, 0x9b, 0xfc}}, 2 };
+ // Randomly generated to represent the drivers sysfs path if available + static const PROPERTYKEY wine_sysfs_path_key = { + {0x7bdb77a0, 0x8531, 0x40d5, {0x9c, 0x4a, 0xbb, 0x7a, 0x8e, 0x7c, 0x97, 0x76}} + }; + + TRACE("MMDevice_Create %s %s %u %u\n", debugstr_w(name), wine_dbgstr_guid(id), flow, (unsigned)state); + LIST_FOR_EACH_ENTRY(device, &device_list, MMDevice, entry) { if (device->flow == flow && IsEqualGUID(&device->devguid, id)){ @@ -437,6 +553,14 @@ static MMDevice *MMDevice_Create(const WCHAR *name, GUID *id, EDataFlow flow, DW PropVariantClear(&pv2); }
+ if (SUCCEEDED(set_driver_prop_value(id, flow, &wine_sysfs_path_key))) { + PROPVARIANT pv2; + PropVariantInit(&pv2); + if (SUCCEEDED(MMDevice_GetPropValue(id, flow, &wine_sysfs_path_key, &pv2)) && pv2.vt == VT_LPWSTR) + set_container_id_for_sysfs_path(id, flow, pv2.pwszVal); + PropVariantClear(&pv2); + } + RegCloseKey(keyprop); } RegCloseKey(key); @@ -1519,6 +1643,7 @@ static HRESULT WINAPI MMDevPropStore_GetValue(IPropertyStore *iface, REFPROPERTY pv->vt == VT_LPWSTR && wcslen(pv->pwszVal) > 62) WARN("Returned name exceeds length limit of some broken apps/libs, might crash: %s\n", debugstr_w(pv->pwszVal)); } + return hres; }
diff --git a/dlls/ntoskrnl.exe/pnp.c b/dlls/ntoskrnl.exe/pnp.c index f887bfc619d..b6b1a96cee6 100644 --- a/dlls/ntoskrnl.exe/pnp.c +++ b/dlls/ntoskrnl.exe/pnp.c @@ -20,6 +20,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include <stdlib.h> + #include "ntoskrnl_private.h" #include "winreg.h" #include "winuser.h" @@ -28,6 +30,7 @@ #include "dbt.h" #include "wine/exception.h" #include "wine/heap.h" +#include "wine/setupapi.h"
#include "plugplay.h"
@@ -355,11 +358,20 @@ static void enumerate_new_device( DEVICE_OBJECT *device, HDEVINFO set ) return; }
- if (!get_device_id(device, BusQueryContainerID, &id) && id) - { - SetupDiSetDeviceRegistryPropertyW( set, &sp_device, SPDRP_BASE_CONTAINERID, (BYTE *)id, - (lstrlenW( id ) + 1) * sizeof(WCHAR) ); + if (!get_device_id(device, BusQueryContainerID, &id) && id) { + TRACE("Setting container ID %s.\n", debugstr_w(id)); + SetupDiSetDeviceRegistryPropertyW(set, &sp_device, SPDRP_BASE_CONTAINERID, (BYTE *)id, (lstrlenW(id) + 1) * sizeof(WCHAR)); ExFreePool( id ); + } else { + TRACE("Failed to get container ID.\n"); + } + + if (!get_device_id(device, WineBusQuerySysfsPath, &id) && id) { + TRACE("Setting sysfs path %s.\n", debugstr_w(id)); + SetupDiSetDeviceRegistryPropertyW(set, &sp_device, WINE_SPDRP_SYSFS_PATH, (BYTE *)id, (lstrlenW(id) + 1) * sizeof(WCHAR)); + ExFreePool(id); + } else { + TRACE("Failed to get syfs path.\n"); }
if (need_driver && !install_device_driver( device, set, &sp_device ) && !caps.RawDeviceOK) diff --git a/dlls/setupapi/devinst.c b/dlls/setupapi/devinst.c index 4e406fe700e..359a1aac4a5 100644 --- a/dlls/setupapi/devinst.c +++ b/dlls/setupapi/devinst.c @@ -38,6 +38,7 @@ #include "shlwapi.h" #include "wine/debug.h" #include "wine/list.h" +#include "wine/setupapi.h" #include "cfgmgr32.h" #include "winioctl.h" #include "rpc.h" @@ -710,6 +711,7 @@ static const struct PropertyMapEntry PropertyMap[] = { { REG_MULTI_SZ, "UpperFilters", UpperFilters }, { REG_MULTI_SZ, "LowerFilters", LowerFilters }, [SPDRP_BASE_CONTAINERID] = { REG_SZ, "ContainerId", ContainerId }, + [WINE_SPDRP_SYSFS_PATH] = {REG_SZ, "WineSysfsPath", L"WineSysfsPath"}, };
static BOOL SETUPDI_SetDeviceRegistryPropertyW(struct device *device, diff --git a/dlls/winebus.sys/Makefile.in b/dlls/winebus.sys/Makefile.in index db32faee5b3..7b4270973b8 100644 --- a/dlls/winebus.sys/Makefile.in +++ b/dlls/winebus.sys/Makefile.in @@ -1,6 +1,6 @@ MODULE = winebus.sys UNIXLIB = winebus.so -IMPORTS = ntoskrnl hidparse +IMPORTS = ntoskrnl hidparse bcrypt UNIX_LIBS = $(IOKIT_LIBS) $(UDEV_LIBS) $(PTHREAD_LIBS) $(INOTIFY_LIBS) UNIX_CFLAGS = $(UDEV_CFLAGS) $(SDL2_CFLAGS)
diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c index 561f0cdc0e4..e5f669a55e1 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -29,6 +29,7 @@ #include <stdlib.h> #include <stdio.h> #include <stdint.h> +#include <sys/stat.h> #include <sys/types.h> #include <dirent.h> #include <unistd.h> @@ -44,6 +45,7 @@ # include <sys/inotify.h> #endif #include <limits.h> +#include <libgen.h>
#ifdef HAVE_LINUX_INPUT_H # include <linux/input.h> @@ -1199,8 +1201,122 @@ static void get_device_subsystem_info(struct udev_device *dev, const char *subsy } }
+// Find the directory with a 'removeble' file. Mutates sysfs_path in place. +static BOOL find_removable_file_dir(char *sysfs_path) +{ + DIR *device_dir = NULL; + struct stat st; + for (;;) { + if (strcmp("/sys/devices", sysfs_path) == 0) { + TRACE("Device is not removable (could not find removable file)\n"); + return FALSE; + } + device_dir = opendir(sysfs_path); + if (fstatat(dirfd(device_dir), "removable", &st, 0) == 0) { + closedir(device_dir); + return TRUE; + } + closedir(device_dir); + dirname(sysfs_path); // mutates in place + } + return FALSE; +} + +// Checks if the device at sysfs_path is removable by checking the contents of the 'removable' file. +static BOOL is_device_removable(char *sysfs_path) +{ + char is_removable_str[MAX_PATH]; + char removable[] = "removable"; + DIR *device_dir = opendir(sysfs_path); + int fd = openat(dirfd(device_dir), "removable", O_RDONLY | O_CLOEXEC); + int err = errno; + + closedir(device_dir); + + if (fd != -1) { + read(fd, is_removable_str, sizeof(is_removable_str)); + close(fd); + if (strncmp(is_removable_str, removable, strlen(removable)) == 0) { + // Perfect, it's removable, so let's expose the sysfs path and by extension generate a container id. + return TRUE; + } else { + return FALSE; + TRACE("Device is not removable, not exposing sysfs path\n"); + } + } + + WARN("Failed to open %s/removable: %s\n", sysfs_path, strerror(err)); + return FALSE; +} + +// Reads the modalias into the modalias buffer. +static void read_modalias(char const *sysfs_path, char *modalias) +{ + DIR *syspath_dir = opendir(sysfs_path); + int fd = openat(dirfd(syspath_dir), "modalias", O_RDONLY | O_CLOEXEC); + off_t done = 0; + + closedir(syspath_dir); + + if (fd == -1) { + return; + } + + for (;;) { + ssize_t len = read(fd, modalias + done, sizeof(modalias) - 1 - done); + if (len == -1) { + if (errno == EINTR) { + continue; + } + break; + } + if (len == 0) { + break; + } + done += len; + } + close(fd); +} + +static void get_modalias(char const *sysfs_path, struct device_desc *desc) +{ + char modalias[MAX_PATH] = {0}; + read_modalias(sysfs_path, modalias); + ntdll_umbstowcs(modalias, strlen(modalias) + 1, desc->modalias, ARRAY_SIZE(desc->modalias)); +} + +static void get_device_sysfs_path_info(char const *sysfs_path, struct device_desc *desc) +{ + char resolved_sysfs_path[MAX_PATH]; + if (realpath(sysfs_path, resolved_sysfs_path) == NULL) { + WARN("realpath failed: %s\n", strerror(errno)); + } else { + if (find_removable_file_dir(resolved_sysfs_path)) { + // resolved_sysfs_path is now pointing at the device directory containing a removable file. + // Next let's figure out if this device is actually removable. + if (is_device_removable(resolved_sysfs_path)) { + ntdll_umbstowcs(resolved_sysfs_path, strlen(resolved_sysfs_path) + 1, desc->sysfs_path, ARRAY_SIZE(desc->sysfs_path)); + } + } + } +} + +static const char *get_device_syspath(struct udev_device *dev) +{ + struct udev_device *parent; + + if ((parent = udev_device_get_parent_with_subsystem_devtype(dev, "hid", NULL))) + return udev_device_get_syspath(parent); + + if ((parent = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device"))) + return udev_device_get_syspath(parent); + + return udev_device_get_syspath(dev); +} + static void udev_add_device(struct udev_device *dev, int fd) { + const char *sysfs_path = get_device_syspath(dev); struct device_desc desc = { .input = -1, @@ -1224,6 +1340,9 @@ static void udev_add_device(struct udev_device *dev, int fd)
TRACE("udev %s syspath %s\n", debugstr_a(devnode), udev_device_get_syspath(dev));
+ get_modalias(sysfs_path, &desc); + get_device_sysfs_path_info(sysfs_path, &desc); + get_device_subsystem_info(dev, "hid", NULL, &desc, &bus); get_device_subsystem_info(dev, "input", NULL, &desc, &bus); get_device_subsystem_info(dev, "usb", "usb_device", &desc, &bus); diff --git a/dlls/winebus.sys/main.c b/dlls/winebus.sys/main.c index 82bc8667bde..7969578dee7 100644 --- a/dlls/winebus.sys/main.c +++ b/dlls/winebus.sys/main.c @@ -19,6 +19,7 @@ */
#include <stdarg.h> +#include <stdlib.h> #include <assert.h>
#include "ntstatus.h" @@ -37,9 +38,13 @@ #include "wine/debug.h" #include "wine/list.h" #include "wine/unixlib.h" +#include "bcrypt.h"
#include "unixlib.h"
+#include "initguid.h" +DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0); + WINE_DEFAULT_DEBUG_CHANNEL(hid);
static DRIVER_OBJECT *driver_obj; @@ -91,6 +96,7 @@ struct device_extension
UINT32 report_fixups; UINT64 unix_device; + GUID container_id; };
static CRITICAL_SECTION device_list_cs; @@ -198,6 +204,140 @@ static WCHAR *get_instance_id(DEVICE_OBJECT *device) return dst; }
+NTSTATUS container_id_from_inputs(LPWSTR *inputs, UINT inputs_count, GUID *container_id) +{ + NTSTATUS status = STATUS_SUCCESS; + BCRYPT_ALG_HANDLE algorithm_handle = NULL; + BCRYPT_HASH_HANDLE hash_handle = NULL; + PUCHAR hash_object = NULL; + PUCHAR hash = NULL; + ULONG property = 0; // must be ULONG so it fits the length property content + ULONG get_result = 0; + + memset(container_id, 0, sizeof(GUID)); + + status = BCryptOpenAlgorithmProvider(&algorithm_handle, BCRYPT_SHA1_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0); + if (!NT_SUCCESS(status)) + goto finish; + + status = BCryptGetProperty(algorithm_handle, BCRYPT_OBJECT_LENGTH, (PUCHAR)&property, sizeof(property), &get_result, 0); + if (!NT_SUCCESS(status)) + goto finish; + + if (sizeof(DWORD) != get_result) { + status = STATUS_INVALID_BUFFER_SIZE; + goto finish; + } + + hash_object = ExAllocatePool(NonPagedPool, property); + if (NULL == hash_object) { + status = STATUS_INSUFFICIENT_RESOURCES; + goto finish; + } + + status = BCryptCreateHash(algorithm_handle, &hash_handle, hash_object, property, NULL, 0, 0); + if (!NT_SUCCESS(status)) + goto finish; + + for (int i = 0; i < inputs_count; i++) { + TRACE("adding input: %s\n", debugstr_w(inputs[i])); + status = BCryptHashData(hash_handle, (PUCHAR)inputs[i], wcslen(inputs[i]) * sizeof(WCHAR), 0); + if (!NT_SUCCESS(status)) + goto finish; + } + + status = BCryptGetProperty(algorithm_handle, BCRYPT_HASH_LENGTH, (PUCHAR)&property, sizeof(property), &get_result, 0); + if (!NT_SUCCESS(status)) { + goto finish; + } + + if (sizeof(DWORD) != get_result) { + status = STATUS_INVALID_PARAMETER; + goto finish; + } + + if (sizeof(GUID) > property) { + status = STATUS_INVALID_PARAMETER; + goto finish; + } + + hash = ExAllocatePool(NonPagedPool, property); + if (NULL == hash) { + status = STATUS_INSUFFICIENT_RESOURCES; + goto finish; + } + + status = BCryptFinishHash(hash_handle, hash, property, 0); + if (!NT_SUCCESS(status)) + goto finish; + + memcpy(container_id, hash, sizeof(GUID)); + TRACE("Container ID: %s\n", debugstr_guid(container_id)); + +finish: + if (hash_handle) + BCryptDestroyHash(hash_handle); + if (hash) + ExFreePool(hash); + if (hash_object) + ExFreePool(hash_object); + if (algorithm_handle) + BCryptCloseAlgorithmProvider(algorithm_handle, 0); + return status; +} + +// ContainerID generation is a bit awkward to pull off. The IDs are meant to be globally unique but to get there +// we need sufficient entropy inputs regardless of our bus implementations. At the same time we should try to keep the IDs +// stable across plug events but able to tell the same type of device apart. +// Another concern is bus inheritance. If a device is a composite device, the ContainerID should be the same for all. +// That last point particularly is only implemented by bus_udev. +// +// Because capabilities vary between bus implementations we'll take a variable amount of inputs. +// For udev we get the sysfs path and the modalias. +// For all others we get the VID, PID, version, and serial number. +NTSTATUS fill_container_id(struct device_extension *ext, GUID *container_id) +{ + const int radix = 10; + WCHAR vid_buffer[MAX_PATH]; + WCHAR pid_buffer[MAX_PATH]; + WCHAR version_buffer[MAX_PATH]; + // Feed as much entropy into the container ID as possible, but only stable values. The serialnumber and sysfs_path serve to disambiguate the same device model. + LPWSTR inputs[] = {ext->desc.sysfs_path, ext->desc.modalias, (LPWSTR)&vid_buffer, (LPWSTR)&pid_buffer, (LPWSTR)&version_buffer, ext->desc.serialnumber}; + + // When sysfs_path is empty it means either something has gone horribly wrong or the device is simply not removable. + // Non-removable devices have no container id. + if (ext->desc.sysfs_path[0] == 0) { + return STATUS_INVALID_PARAMETER; + } + + _itow_s(ext->desc.vid, vid_buffer, sizeof(vid_buffer), radix); + _itow_s(ext->desc.pid, pid_buffer, sizeof(pid_buffer), radix); + _itow_s(ext->desc.version, version_buffer, sizeof(version_buffer), radix); + + return container_id_from_inputs(inputs, ARRAY_SIZE(inputs), container_id); +} + +static WCHAR *get_container_id(DEVICE_OBJECT *device) +{ + struct device_extension *ext = (struct device_extension *)device->DeviceExtension; + UNICODE_STRING dst; + + if (IsEqualGUID(&ext->container_id, &GUID_NULL)) { + return NULL; + } + + RtlZeroMemory(&dst, sizeof(dst)); + RtlStringFromGUID(&ext->container_id, &dst); + + return dst.Buffer; +} + +static WCHAR *get_sysfs_path(DEVICE_OBJECT *device) +{ + struct device_extension *ext = (struct device_extension *)device->DeviceExtension; + return ext->desc.sysfs_path; +} + static WCHAR *get_device_id(DEVICE_OBJECT *device) { static const WCHAR input_format[] = L"&MI_%02u"; @@ -315,6 +455,14 @@ static DEVICE_OBJECT *bus_create_hid_device(struct device_desc *desc, UINT64 uni ext->desc = *desc; ext->index = get_device_index(desc, &before); ext->unix_device = unix_device; + + // after all others in case the hash inputs are dependant on other fields + if (fill_container_id(ext, &ext->container_id) == STATUS_SUCCESS) { + TRACE("Filled container ID %s\n", debugstr_guid(&ext->container_id)); + } else { + WARN("Failed to fill container ID\n"); + } + list_init(&ext->reports);
if (desc->is_hidraw && desc->is_bluetooth && is_dualshock4_gamepad(desc->vid, desc->pid)) @@ -680,6 +828,14 @@ static NTSTATUS handle_IRP_MN_QUERY_ID(DEVICE_OBJECT *device, IRP *irp) TRACE("BusQueryInstanceID\n"); irp->IoStatus.Information = (ULONG_PTR)get_instance_id(device); break; + case BusQueryContainerID: + TRACE("BusQueryContainerID\n"); + irp->IoStatus.Information = (ULONG_PTR)get_container_id(device); + break; + case WineBusQuerySysfsPath: + TRACE("WineBusQuerySysfsPath\n"); + irp->IoStatus.Information = (ULONG_PTR)get_sysfs_path(device); + break; default: WARN("Unhandled type %08x\n", type); return status; diff --git a/dlls/winebus.sys/unixlib.h b/dlls/winebus.sys/unixlib.h index 02e7a1c6953..996dc1699bb 100644 --- a/dlls/winebus.sys/unixlib.h +++ b/dlls/winebus.sys/unixlib.h @@ -45,6 +45,8 @@ struct device_desc WCHAR manufacturer[MAX_PATH]; WCHAR product[MAX_PATH]; WCHAR serialnumber[MAX_PATH]; + WCHAR sysfs_path[MAX_PATH]; + WCHAR modalias[MAX_PATH]; };
struct sdl_bus_options diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 050609d2cda..b85aa562ba8 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -23,6 +23,7 @@ #pragma makedep unix #endif
+#include <libgen.h> #include <stdarg.h> #include <pthread.h> #include <math.h> @@ -105,6 +106,7 @@ typedef struct _PhysDevice { UINT index; REFERENCE_TIME min_period, def_period; WAVEFORMATEXTENSIBLE fmt; + char sysfs_path[MAX_PATH]; char pulse_name[0]; } PhysDevice;
@@ -549,6 +551,7 @@ static void fill_device_info(PhysDevice *dev, pa_proplist *p) dev->bus_type = phys_device_bus_invalid; dev->vendor_id = 0; dev->product_id = 0; + dev->sysfs_path[0] = '\0';
if (!p) return; @@ -565,6 +568,16 @@ static void fill_device_info(PhysDevice *dev, pa_proplist *p)
if ((buffer = pa_proplist_gets(p, PA_PROP_DEVICE_PRODUCT_ID))) dev->product_id = strtol(buffer, NULL, 16); + + if ((buffer = pa_proplist_gets(p, "sysfs.path"))) { + // The syspah is of the audio device. Resolve it up to the device level. + char sysfs_path[MAX_PATH]; + snprintf(sysfs_path, sizeof(sysfs_path), "/sys%s/device", buffer); + realpath(sysfs_path, dev->sysfs_path); + // We are now at /sys/devices/pci0000:00/0000:00:01.3/0000:02:00.0/usb1/1-7/1-7:1.0 + // Get us up to the base device (1-7) container ids are meant to be the same across all sub devices inside a "container". + dirname(dev->sysfs_path); // mutates in place + } }
static void pulse_add_device(struct list *list, pa_proplist *proplist, int index, EndpointFormFactor form, @@ -2596,6 +2609,11 @@ static NTSTATUS pulse_get_prop_value(void *args) static const PROPERTYKEY devicepath_key = { /* undocumented? - {b3f8fa53-0004-438e-9003-51a46e139bfc},2 */ {0xb3f8fa53, 0x0004, 0x438e, {0x90, 0x03, 0x51, 0xa4, 0x6e, 0x13, 0x9b, 0xfc}}, 2 }; + // Randomly generated to represent the drivers sysfs path if available + static const PROPERTYKEY wine_sysfs_path_key = { + {0x7bdb77a0, 0x8531, 0x40d5, {0x9c, 0x4a, 0xbb, 0x7a, 0x8e, 0x7c, 0x97, 0x76}} + }; + struct get_prop_value_params *params = args; struct list *list = (params->flow == eRender) ? &g_phys_speakers : &g_phys_sources; PhysDevice *dev; @@ -2621,6 +2639,20 @@ static NTSTATUS pulse_get_prop_value(void *args) params->result = S_OK; return STATUS_SUCCESS; } + } else if (IsEqualPropertyKey(*params->prop, wine_sysfs_path_key)) { + size_t sysfs_path_len = strlen(dev->sysfs_path) + 1; + if (sysfs_path_len > 1) { + if (*params->buffer_size < sysfs_path_len * sizeof(WCHAR)) { + params->result = E_NOT_SUFFICIENT_BUFFER; + *params->buffer_size = sysfs_path_len * sizeof(WCHAR); + return STATUS_SUCCESS; + } + params->value->vt = VT_LPWSTR; + params->value->pwszVal = params->buffer; + ntdll_umbstowcs(dev->sysfs_path, sysfs_path_len, params->value->pwszVal, sysfs_path_len); + params->result = S_OK; + return STATUS_SUCCESS; + } }
params->result = E_NOTIMPL; diff --git a/dlls/wineusb.sys/wineusb.c b/dlls/wineusb.sys/wineusb.c index 29d28ee002d..a91611b655b 100644 --- a/dlls/wineusb.sys/wineusb.c +++ b/dlls/wineusb.sys/wineusb.c @@ -427,6 +427,14 @@ static NTSTATUS query_id(struct usb_device *device, IRP *irp, BUS_QUERY_ID_TYPE get_compatible_ids(device, &buffer); break;
+ case BusQueryContainerID: + ERR("BusQueryContainerID not implemented in wineusb. Should be handled in winebus!\n"); + return STATUS_NOT_IMPLEMENTED; + + case WineBusQuerySysfsPath: + ERR("WineBusQuerySysfsPath not implemented in wineusb. Should be handled in winebus!\n"); + return STATUS_NOT_IMPLEMENTED; + default: FIXME("Unhandled ID query type %#x.\n", type); return irp->IoStatus.Status; diff --git a/include/Makefile.in b/include/Makefile.in index cb2b83b6d8c..f5adb0a3a35 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -922,6 +922,7 @@ SOURCES = \ wine/condrv.h \ wine/dcetypes.idl \ wine/debug.h \ + wine/setupapi.h \ wine/dplaysp.h \ wine/epm.idl \ wine/exception.h \ diff --git a/include/ddk/wdm.h b/include/ddk/wdm.h index 2ea6a81d432..ac169456c4e 100644 --- a/include/ddk/wdm.h +++ b/include/ddk/wdm.h @@ -876,6 +876,7 @@ typedef enum _BUS_QUERY_ID_TYPE { BusQueryInstanceID, BusQueryDeviceSerialNumber, BusQueryContainerID, + WineBusQuerySysfsPath = 0x3fffffff, } BUS_QUERY_ID_TYPE, *PBUS_QUERY_ID_TYPE;
typedef enum _CREATE_FILE_TYPE { diff --git a/include/setupapi.h b/include/setupapi.h index 74b5d003453..4903b4fa875 100644 --- a/include/setupapi.h +++ b/include/setupapi.h @@ -1323,6 +1323,7 @@ DECL_WINELIB_SETUPAPI_TYPE_AW(PSP_INF_SIGNER_INFO) #define SPDRP_INSTALL_STATE 0x00000022 #define SPDRP_BASE_CONTAINERID 0x00000024 #define SPDRP_MAXIMUM_PROPERTY 0x00000025 +// WINE_SPDRP_SYSFS_PATH is defined internally. Do not use from the outside. Make sure the value is not in conflict with public API.
#define DPROMPT_SUCCESS 0 #define DPROMPT_CANCEL 1 diff --git a/include/wine/setupapi.h b/include/wine/setupapi.h new file mode 100644 index 00000000000..82f296b129e --- /dev/null +++ b/include/wine/setupapi.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2025 Harald Sitter sitter@kde.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __WINE_WINE_SETUPAPI +#define __WINE_WINE_SETUPAPI + +// Wine internal, not part of MAXIMUM_PROPERTY by design. Do not use this! +#define WINE_SPDRP_SYSFS_PATH 0x00000fff + +#endif /* __WINE_WINE_SETUPAPI */