On 8/18/21 2:31 AM, Rémi Bernon wrote:
Currently only acting as a pass-through driver, matching any device with a WINEBUS\WINE_COMP_XINPUT compatible id.
This creates new PDO on the bus, adding the &IG_ device ID suffix to the original device ID (replacing an eventual &MI_ suffix), and removes the need to set the suffix on winebus.sys side.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com
Can we find something else to name this, so it's less ambiguous what "xinput" refers to?
Maybe "winexinput"; this is a wine-specific DLL after all.
configure.ac | 1 + dlls/winebus.sys/main.c | 27 +-- dlls/xinput.sys/Makefile.in | 8 + dlls/xinput.sys/main.c | 393 ++++++++++++++++++++++++++++++++ dlls/xinput.sys/xinput.inf | 22 ++ dlls/xinput.sys/xinput.rc | 20 ++ dlls/xinput.sys/xinput.sys.spec | 1 + loader/wine.inf.in | 1 + 8 files changed, 457 insertions(+), 16 deletions(-) create mode 100644 dlls/xinput.sys/Makefile.in create mode 100644 dlls/xinput.sys/main.c create mode 100644 dlls/xinput.sys/xinput.inf create mode 100644 dlls/xinput.sys/xinput.rc create mode 100644 dlls/xinput.sys/xinput.sys.spec
diff --git a/configure.ac b/configure.ac index 7237289a2ad..7ff7869b37e 100644 --- a/configure.ac +++ b/configure.ac @@ -3895,6 +3895,7 @@ WINE_CONFIG_MAKEFILE(dlls/xaudio2_7) WINE_CONFIG_MAKEFILE(dlls/xaudio2_7/tests) WINE_CONFIG_MAKEFILE(dlls/xaudio2_8) WINE_CONFIG_MAKEFILE(dlls/xaudio2_9) +WINE_CONFIG_MAKEFILE(dlls/xinput.sys) WINE_CONFIG_MAKEFILE(dlls/xinput1_1) WINE_CONFIG_MAKEFILE(dlls/xinput1_2) WINE_CONFIG_MAKEFILE(dlls/xinput1_3) diff --git a/dlls/winebus.sys/main.c b/dlls/winebus.sys/main.c index e360416b5dd..17720bffbc5 100644 --- a/dlls/winebus.sys/main.c +++ b/dlls/winebus.sys/main.c @@ -152,8 +152,6 @@ static CRITICAL_SECTION device_list_cs = { &critsect_debug, -1, 0, 0, 0, 0 }; static struct list pnp_devset = LIST_INIT(pnp_devset);
static const WCHAR zero_serialW[]= {'0','0','0','0',0}; -static const WCHAR miW[] = {'M','I',0}; -static const WCHAR igW[] = {'I','G',0};
static inline WCHAR *strdupW(const WCHAR *src) { @@ -201,25 +199,17 @@ static WCHAR *get_instance_id(DEVICE_OBJECT *device)
static WCHAR *get_device_id(DEVICE_OBJECT *device) {
- static const WCHAR input_formatW[] = {'&','M','I','_','%','0','2','u',0}; static const WCHAR formatW[] = {'%','s','\','v','i','d','_','%','0','4','x', '&','p','i','d','_','%','0','4','x',0};
- static const WCHAR format_inputW[] = {'%','s','\','v','i','d','_','%','0','4','x',
'&','p','i','d','_','%','0','4','x','&','%','s','_','%','0','2','i',0}; struct device_extension *ext = (struct device_extension *)device->DeviceExtension; DWORD len = strlenW(ext->busid) + 34;
- WCHAR *dst;
WCHAR *dst, *tmp;
if ((dst = ExAllocatePool(PagedPool, len * sizeof(WCHAR)))) {
if (ext->input == (WORD)-1)
{
sprintfW(dst, formatW, ext->busid, ext->vid, ext->pid);
}
else
{
sprintfW(dst, format_inputW, ext->busid, ext->vid, ext->pid,
ext->is_gamepad ? igW : miW, ext->input);
}
tmp = dst + sprintfW(dst, formatW, ext->busid, ext->vid, ext->pid);
if (ext->input != (WORD)-1) sprintfW(tmp, input_formatW, ext->input); } return dst;
@@ -245,12 +235,17 @@ static WCHAR *get_compatible_ids(DEVICE_OBJECT *device) { 'W','I','N','E','B','U','S','\','W','I','N','E','_','C','O','M','P','_','H','I','D',0 };
- DWORD len = strlenW(hid_compat);
static const WCHAR xinput_compat[] =
{
'W','I','N','E','B','U','S','\\','W','I','N','E','_','C','O','M','P','_','X','I','N','P','U','T',0
};
struct device_extension *ext = (struct device_extension *)device->DeviceExtension;
DWORD len = strlenW(ext->is_gamepad ? xinput_compat : hid_compat); WCHAR *dst;
if ((dst = ExAllocatePool(PagedPool, (len + 2) * sizeof(WCHAR)))) {
strcpyW(dst, hid_compat);
strcpyW(dst, ext->is_gamepad ? xinput_compat : hid_compat); dst[len + 1] = 0; }
diff --git a/dlls/xinput.sys/Makefile.in b/dlls/xinput.sys/Makefile.in new file mode 100644 index 00000000000..52b00ef0528 --- /dev/null +++ b/dlls/xinput.sys/Makefile.in @@ -0,0 +1,8 @@ +MODULE = xinput.sys +IMPORTS = ntoskrnl +EXTRADLLFLAGS = -mno-cygwin -Wl,--subsystem,native
+C_SRCS = \
- main.c
+RC_SRCS = xinput.rc diff --git a/dlls/xinput.sys/main.c b/dlls/xinput.sys/main.c new file mode 100644 index 00000000000..b97ec49b325 --- /dev/null +++ b/dlls/xinput.sys/main.c @@ -0,0 +1,393 @@ +/*
- WINE XInput device driver
- Copyright 2021 Rémi Bernon for CodeWeavers
- 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
- */
+#include <stdarg.h> +#include <stdlib.h>
+#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "winioctl.h"
+#include "cfgmgr32.h" +#include "ddk/wdm.h" +#include "ddk/hidport.h"
+#include "wine/debug.h"
+WINE_DEFAULT_DEBUG_CHANNEL(xinput);
+#if defined(__i386__) && !defined(_WIN32) +extern void *WINAPI wrap_fastcall_func1(void *func, const void *a); +__ASM_STDCALL_FUNC(wrap_fastcall_func1, 8,
"popl %ecx\n\t"
"popl %eax\n\t"
"xchgl (%esp),%ecx\n\t"
"jmp *%eax");
+#define call_fastcall_func1(func,a) wrap_fastcall_func1(func,a) +#else +#define call_fastcall_func1(func,a) func(a) +#endif
+struct device_state
This is a weird thing to call this, because it's not dynamic at all.
Also, maybe a slightly more idiomatic structure would be to hold a pointer to the parent device as a struct device_extension, and then access its members through that pointer?
+{
- DEVICE_OBJECT *bus_pdo;
- DEVICE_OBJECT *xinput_pdo;
This is ambiguous, how about "child_pdo"?
- WCHAR bus_id[MAX_DEVICE_ID_LEN];
- WCHAR instance_id[MAX_DEVICE_ID_LEN];
+};
+struct device_extension
Can we please not call this "device_extension"? I know we use that in other DLLs, but I think it's rather unclear. "struct device" seems better to me.
+{
- BOOL is_fdo;
- BOOL removed;
- WCHAR device_id[MAX_DEVICE_ID_LEN];
- struct device_state *state;
+};
+static inline struct device_extension *ext_from_DEVICE_OBJECT(DEVICE_OBJECT *device) +{
- return (struct device_extension *)device->DeviceExtension;
+}
+static NTSTATUS WINAPI internal_ioctl(DEVICE_OBJECT *device, IRP *irp) +{
- struct device_extension *ext = ext_from_DEVICE_OBJECT(device);
- IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp);
- ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
- struct device_state *state = ext->state;
- if (InterlockedOr(&ext->removed, FALSE))
- {
irp->IoStatus.Status = STATUS_DELETE_PENDING;
irp->IoStatus.Information = 0;
IoCompleteRequest(irp, IO_NO_INCREMENT);
- }
- TRACE("device %p, irp %p, code %#x, bus_pdo %p.\n", device, irp, code, state->bus_pdo);
- IoSkipCurrentIrpStackLocation(irp);
- return IoCallDriver(state->bus_pdo, irp);
+}
+static WCHAR *query_instance_id(DEVICE_OBJECT *device) +{
- struct device_extension *ext = ext_from_DEVICE_OBJECT(device);
- struct device_state *state = ext->state;
- DWORD len = wcslen(state->instance_id);
- WCHAR *dst;
- if ((dst = ExAllocatePool(PagedPool, len * sizeof(WCHAR))))
wcscpy(dst, state->instance_id);
- return dst;
+}
+static WCHAR *query_device_id(DEVICE_OBJECT *device) +{
- struct device_extension *ext = ext_from_DEVICE_OBJECT(device);
- DWORD len = wcslen(ext->device_id);
- WCHAR *dst;
- if ((dst = ExAllocatePool(PagedPool, len * sizeof(WCHAR))))
wcscpy(dst, ext->device_id);
- return dst;
+}
+static WCHAR *query_hardware_ids(DEVICE_OBJECT *device) +{
- struct device_extension *ext = (struct device_extension *)device->DeviceExtension;
- DWORD len = wcslen(ext->device_id);
- WCHAR *dst;
- if ((dst = ExAllocatePool(PagedPool, (len + 2) * sizeof(WCHAR))))
- {
wcscpy(dst, ext->device_id);
dst[len + 1] = 0;
- }
- return dst;
+}
+static WCHAR *query_compatible_ids(DEVICE_OBJECT *device) +{
- DWORD len = wcslen(L"WINEBUS\WINE_COMP_HID");
Nitpick, but it strikes me as odd to define "len" instead of defining the string constant itself.
- WCHAR *dst;
- if ((dst = ExAllocatePool(PagedPool, (len + 2) * sizeof(WCHAR))))
- {
wcscpy(dst, L"WINEBUS\\WINE_COMP_HID");
Also, you've just used "len", so this should be memcmp. Same for the others.
dst[len + 1] = 0;
- }
- return dst;
+}
+static NTSTATUS WINAPI pdo_pnp(DEVICE_OBJECT *device, IRP *irp) +{
- struct device_extension *ext = ext_from_DEVICE_OBJECT(device);
- IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp);
- struct device_state *state = ext->state;
- ULONG code = stack->MinorFunction;
- NTSTATUS status;
- TRACE("device %p, irp %p, code %#x, bus_pdo %p.\n", device, irp, code, state->bus_pdo);
- switch (code)
- {
- case IRP_MN_START_DEVICE:
status = STATUS_SUCCESS;
break;
- case IRP_MN_SURPRISE_REMOVAL:
status = STATUS_SUCCESS;
if (InterlockedExchange(&ext->removed, TRUE)) break;
break;
- case IRP_MN_REMOVE_DEVICE:
irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(irp, IO_NO_INCREMENT);
IoDeleteDevice(device);
return STATUS_SUCCESS;
- case IRP_MN_QUERY_ID:
switch (stack->Parameters.QueryId.IdType)
{
case BusQueryHardwareIDs:
irp->IoStatus.Information = (ULONG_PTR)query_hardware_ids(device);
break;
case BusQueryCompatibleIDs:
irp->IoStatus.Information = (ULONG_PTR)query_compatible_ids(device);
break;
case BusQueryDeviceID:
irp->IoStatus.Information = (ULONG_PTR)query_device_id(device);
break;
case BusQueryInstanceID:
irp->IoStatus.Information = (ULONG_PTR)query_instance_id(device);
break;
default:
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(state->bus_pdo, irp);
}
if (!irp->IoStatus.Information) status = STATUS_NO_MEMORY;
else status = STATUS_SUCCESS;
break;
- default:
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(state->bus_pdo, irp);
- }
- irp->IoStatus.Status = status;
- IoCompleteRequest(irp, IO_NO_INCREMENT);
- return status;
+}
+static void create_child_pdos(DEVICE_OBJECT *fdo) +{
- struct device_extension *fdo_ext = fdo->DeviceExtension, *pdo_ext;
- struct device_state *state = fdo_ext->state;
- DEVICE_OBJECT *xinput_pdo;
- UNICODE_STRING string;
- WCHAR *tmp, pdo_name[255];
- NTSTATUS status;
- swprintf(pdo_name, ARRAY_SIZE(pdo_name), L"\Device\%s#%p&%p", state->bus_id, fdo->DriverObject, state->bus_pdo);
- RtlInitUnicodeString(&string, pdo_name);
- if ((status = IoCreateDevice(fdo->DriverObject, sizeof(*pdo_ext), &string, 0, 0, FALSE, &xinput_pdo)))
- {
ERR( "failed to create xinput PDO, status %#x.\n", status );
return;
- }
- pdo_ext = xinput_pdo->DeviceExtension;
- pdo_ext->is_fdo = FALSE;
- pdo_ext->state = state;
- wcscpy(pdo_ext->device_id, fdo_ext->device_id);
- if ((tmp = wcsstr(pdo_ext->device_id, L"&MI_"))) memcpy(tmp, L"&IG", 6);
- else wcscat(pdo_ext->device_id, L"&IG_00");
I hate to nitpick, but can we please add some newlines here? This is not easy to read.
- state->xinput_pdo = xinput_pdo;
- TRACE("fdo %p, xinput PDO %p.\n", fdo, xinput_pdo);
- IoInvalidateDeviceRelations(state->bus_pdo, BusRelations);
+}
+static NTSTATUS WINAPI fdo_pnp(DEVICE_OBJECT *device, IRP *irp) +{
- struct device_extension *ext = ext_from_DEVICE_OBJECT(device);
- IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp);
- struct device_state *state = ext->state;
- ULONG code = stack->MinorFunction;
- DEVICE_RELATIONS *devices;
- DEVICE_OBJECT *child;
- NTSTATUS status;
- TRACE("device %p, irp %p, code %#x, bus_pdo %p.\n", device, irp, code, state->bus_pdo);
- switch (stack->MinorFunction)
- {
- case IRP_MN_QUERY_DEVICE_RELATIONS:
if (stack->Parameters.QueryDeviceRelations.Type == BusRelations)
{
if (!(devices = ExAllocatePool(PagedPool, offsetof(DEVICE_RELATIONS, Objects[2]))))
{
irp->IoStatus.Status = STATUS_NO_MEMORY;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_NO_MEMORY;
}
devices->Count = 0;
if ((child = state->xinput_pdo))
{
devices->Objects[devices->Count] = child;
call_fastcall_func1(ObfReferenceObject, child);
devices->Count++;
}
irp->IoStatus.Information = (ULONG_PTR)devices;
irp->IoStatus.Status = STATUS_SUCCESS;
}
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(state->bus_pdo, irp);
- case IRP_MN_START_DEVICE:
IoSkipCurrentIrpStackLocation(irp);
if (!(status = IoCallDriver(state->bus_pdo, irp)))
create_child_pdos(device);
return status;
- case IRP_MN_REMOVE_DEVICE:
IoSkipCurrentIrpStackLocation(irp);
status = IoCallDriver(state->bus_pdo, irp);
IoDetachDevice(state->bus_pdo);
IoDeleteDevice(device);
return status;
- default:
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(state->bus_pdo, irp);
- }
- return STATUS_SUCCESS;
+}
+static NTSTATUS WINAPI driver_pnp(DEVICE_OBJECT *device, IRP *irp) +{
- struct device_extension *ext = ext_from_DEVICE_OBJECT(device);
- if (ext->is_fdo) return fdo_pnp(device, irp);
- return pdo_pnp(device, irp);
+}
+static NTSTATUS get_device_id(DEVICE_OBJECT *device, BUS_QUERY_ID_TYPE type, WCHAR *id) +{
- IO_STACK_LOCATION *stack;
- IO_STATUS_BLOCK io;
- KEVENT event;
- IRP *irp;
- KeInitializeEvent(&event, NotificationEvent, FALSE);
- irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP, device, NULL, 0, NULL, &event, &io);
- if (irp == NULL) return STATUS_NO_MEMORY;
- stack = IoGetNextIrpStackLocation(irp);
- stack->MinorFunction = IRP_MN_QUERY_ID;
- stack->Parameters.QueryId.IdType = type;
- if (IoCallDriver(device, irp) == STATUS_PENDING)
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
- wcscpy(id, (WCHAR *)io.Information);
- ExFreePool((WCHAR *)io.Information);
- return io.Status;
+}
+static NTSTATUS WINAPI add_device(DRIVER_OBJECT *driver, DEVICE_OBJECT *bus_pdo) +{
- WCHAR bus_id[MAX_DEVICE_ID_LEN], *device_id, instance_id[MAX_DEVICE_ID_LEN];
- struct device_extension *ext;
- struct device_state *state;
- DEVICE_OBJECT *fdo;
- NTSTATUS status;
- TRACE("driver %p, bus_pdo %p.\n", driver, bus_pdo);
- if ((status = get_device_id(bus_pdo, BusQueryDeviceID, bus_id)))
- {
ERR("failed to get PDO device id, status %#x.\n", status);
return status;
- }
- if ((device_id = wcsrchr(bus_id, '\'))) *device_id++ = 0;
- else
- {
ERR("unexpected device id %s\n", debugstr_w(bus_id));
return STATUS_UNSUCCESSFUL;
- }
- if ((status = get_device_id(bus_pdo, BusQueryInstanceID, instance_id)))
- {
ERR("failed to get PDO instance id, status %#x.\n", status);
return status;
- }
- if ((status = IoCreateDevice(driver, sizeof(*ext) + sizeof(struct device_state), NULL,
FILE_DEVICE_BUS_EXTENDER, 0, FALSE, &fdo)))
- {
ERR("failed to create bus FDO, status %#x.\n", status);
return status;
- }
- ext = fdo->DeviceExtension;
- ext->is_fdo = TRUE;
- ext->state = (struct device_state *)(ext + 1);
- state = ext->state;
- state->bus_pdo = bus_pdo;
- wcscpy(state->bus_id, bus_id);
- swprintf(ext->device_id, MAX_DEVICE_ID_LEN, L"%s\%s", bus_id, device_id);
- wcscpy(state->instance_id, instance_id);
- TRACE("fdo %p, bus %s, device %s, instance %s.\n", fdo, debugstr_w(state->bus_id), debugstr_w(ext->device_id), debugstr_w(state->instance_id));
- IoAttachDeviceToDeviceStack(fdo, bus_pdo);
- fdo->Flags &= ~DO_DEVICE_INITIALIZING;
- return STATUS_SUCCESS;
+}
+NTSTATUS WINAPI DriverEntry(DRIVER_OBJECT *driver, UNICODE_STRING *path) +{
- TRACE("driver %p, path %s.\n", driver, debugstr_w(path->Buffer));
- driver->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = internal_ioctl;
- driver->MajorFunction[IRP_MJ_PNP] = driver_pnp;
- driver->DriverExtension->AddDevice = add_device;
- return STATUS_SUCCESS;
+}
Missing a DriverUnload; that'll prevent the driver from being unloaded.
diff --git a/dlls/xinput.sys/xinput.inf b/dlls/xinput.sys/xinput.inf new file mode 100644 index 00000000000..dcc5c87166f --- /dev/null +++ b/dlls/xinput.sys/xinput.inf @@ -0,0 +1,22 @@ +[Version] +Signature="$CHICAGO$" +ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318} +Class=System
+[Manufacturer] +Wine=mfg_section
+[mfg_section] +Wine XInput compatible device=device_section,WINEBUS\WINE_COMP_XINPUT
+[device_section.Services] +AddService = xinput,0x2,svc_section
+[svc_section] +Description="Wine XInput device driver" +DisplayName="Wine XInput" +ServiceBinary="%12%\xinput.sys" +LoadOrderGroup="WinePlugPlay" +ServiceType=1 +StartType=3 +ErrorControl=1 diff --git a/dlls/xinput.sys/xinput.rc b/dlls/xinput.sys/xinput.rc new file mode 100644 index 00000000000..791dca8fccb --- /dev/null +++ b/dlls/xinput.sys/xinput.rc @@ -0,0 +1,20 @@ +/*
- Copyright 2021 Rémi Bernon for CodeWeavers
- 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
- */
+/* @makedep: xinput.inf */ +1 WINE_DATA_FILE xinput.inf diff --git a/dlls/xinput.sys/xinput.sys.spec b/dlls/xinput.sys/xinput.sys.spec new file mode 100644 index 00000000000..76421d7e35b --- /dev/null +++ b/dlls/xinput.sys/xinput.sys.spec @@ -0,0 +1 @@ +# nothing to export diff --git a/loader/wine.inf.in b/loader/wine.inf.in index 49e4f45da27..9b8ec938c2f 100644 --- a/loader/wine.inf.in +++ b/loader/wine.inf.in @@ -4060,6 +4060,7 @@ services,"@%11%\ws2_32.dll,-4" winebus.inf,"@%12%\winebus.sys,-1" winehid.inf,"@%12%\winehid.sys,-1" wineusb.inf,"@%12%\wineusb.sys,-1" +xinput.inf,"@%12%\xinput.sys,-1"
[NlsFiles] c_037.nls