mirror of
https://github.com/nefarius/ViGEmBus.git
synced 2025-08-10 00:52:17 +00:00
818 lines
24 KiB
C
818 lines
24 KiB
C
/*
|
|
* Virtual Gamepad Emulation Framework - Windows kernel-mode bus driver
|
|
* Copyright (C) 2016-2018 Benjamin Höglinger-Stelzer
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include "busenum.h"
|
|
#include <wdmguid.h>
|
|
#include <usb.h>
|
|
#include "busenum.tmh"
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text (PAGE, Bus_PlugInDevice)
|
|
#pragma alloc_text (PAGE, Bus_UnPlugDevice)
|
|
#endif
|
|
|
|
|
|
|
|
//
|
|
// Simulates a device plug-in event.
|
|
//
|
|
NTSTATUS Bus_PlugInDevice(
|
|
_In_ WDFDEVICE Device,
|
|
_In_ WDFREQUEST Request,
|
|
_In_ BOOLEAN IsInternal,
|
|
_Out_ size_t* Transferred)
|
|
{
|
|
PDO_IDENTIFICATION_DESCRIPTION description;
|
|
NTSTATUS status;
|
|
PVIGEM_PLUGIN_TARGET plugIn;
|
|
WDFFILEOBJECT fileObject;
|
|
PFDO_FILE_DATA pFileData;
|
|
size_t length = 0;
|
|
WDF_OBJECT_ATTRIBUTES requestAttribs;
|
|
PFDO_PLUGIN_REQUEST_DATA pReqData;
|
|
PFDO_DEVICE_DATA pFdoData;
|
|
|
|
PAGED_CODE();
|
|
|
|
|
|
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_BUSENUM, "%!FUNC! Entry");
|
|
|
|
pFdoData = FdoGetData(Device);
|
|
|
|
status = WdfRequestRetrieveInputBuffer(Request, sizeof(VIGEM_PLUGIN_TARGET), (PVOID)&plugIn, &length);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"WdfRequestRetrieveInputBuffer failed with status %!STATUS!", status);
|
|
return status;
|
|
}
|
|
|
|
if ((sizeof(VIGEM_PLUGIN_TARGET) != plugIn->Size) || (length != plugIn->Size))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"sizeof(VIGEM_PLUGIN_TARGET) buffer size mismatch [%d != %d]",
|
|
sizeof(VIGEM_PLUGIN_TARGET), plugIn->Size);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (plugIn->SerialNo == 0)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"Serial no. 0 not allowed");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
*Transferred = length;
|
|
|
|
fileObject = WdfRequestGetFileObject(Request);
|
|
if (fileObject == NULL)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"WdfRequestGetFileObject failed to fetch WDFFILEOBJECT from request 0x%p",
|
|
Request);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
pFileData = FileObjectGetData(fileObject);
|
|
if (pFileData == NULL)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"FileObjectGetData failed to get context data for 0x%p",
|
|
fileObject);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Initialize the description with the information about the newly
|
|
// plugged in device.
|
|
//
|
|
WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER_INIT(&description.Header, sizeof(description));
|
|
|
|
description.SerialNo = plugIn->SerialNo;
|
|
description.TargetType = plugIn->TargetType;
|
|
description.OwnerProcessId = CURRENT_PROCESS_ID();
|
|
description.SessionId = pFileData->SessionId;
|
|
description.OwnerIsDriver = IsInternal;
|
|
|
|
// Set default IDs if supplied values are invalid
|
|
if (plugIn->VendorId == 0 || plugIn->ProductId == 0)
|
|
{
|
|
switch (plugIn->TargetType)
|
|
{
|
|
case Xbox360Wired:
|
|
|
|
description.VendorId = 0x045E;
|
|
description.ProductId = 0x028E;
|
|
|
|
break;
|
|
case DualShock4Wired:
|
|
|
|
description.VendorId = 0x054C;
|
|
description.ProductId = 0x05C4;
|
|
|
|
break;
|
|
case XboxOneWired:
|
|
|
|
description.VendorId = 0x0E6F;
|
|
description.ProductId = 0x0139;
|
|
|
|
#if !DBG
|
|
// TODO: implement and remove!
|
|
return STATUS_NOT_SUPPORTED;
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
description.VendorId = plugIn->VendorId;
|
|
description.ProductId = plugIn->ProductId;
|
|
}
|
|
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_BUSENUM,
|
|
"New PDO properties: serial = %d, type = %d, pid = %d, session = %d, internal = %d, vid = 0x%04X, pid = 0x%04X",
|
|
description.SerialNo,
|
|
description.TargetType,
|
|
description.OwnerProcessId,
|
|
description.SessionId,
|
|
description.OwnerIsDriver,
|
|
description.VendorId,
|
|
description.ProductId
|
|
);
|
|
|
|
WdfSpinLockAcquire(pFdoData->PendingPluginRequestsLock);
|
|
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_BUSENUM,
|
|
"Current pending requests count: %d",
|
|
WdfCollectionGetCount(pFdoData->PendingPluginRequests));
|
|
|
|
status = WdfChildListAddOrUpdateChildDescriptionAsPresent(WdfFdoGetDefaultChildList(Device), &description.Header, NULL);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"WdfChildListAddOrUpdateChildDescriptionAsPresent failed with status %!STATUS!",
|
|
status);
|
|
|
|
goto pluginEnd;
|
|
}
|
|
|
|
//
|
|
// The requested serial number is already in use
|
|
//
|
|
if (status == STATUS_OBJECT_NAME_EXISTS)
|
|
{
|
|
status = STATUS_INVALID_PARAMETER;
|
|
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"The described PDO already exists (%!STATUS!)",
|
|
status);
|
|
|
|
goto pluginEnd;
|
|
}
|
|
|
|
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&requestAttribs, FDO_PLUGIN_REQUEST_DATA);
|
|
|
|
//
|
|
// Allocate context data to request
|
|
//
|
|
status = WdfObjectAllocateContext(Request, &requestAttribs, (PVOID)&pReqData);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"WdfObjectAllocateContext failed with status %!STATUS!",
|
|
status);
|
|
|
|
goto pluginEnd;
|
|
}
|
|
|
|
//
|
|
// Glue current serial to request
|
|
//
|
|
pReqData->Serial = plugIn->SerialNo;
|
|
|
|
//
|
|
// Timestamp the request to track its age
|
|
//
|
|
pReqData->Timestamp = KeQueryPerformanceCounter(&pReqData->Frequency);
|
|
|
|
//
|
|
// Keep track of pending request in collection
|
|
//
|
|
status = WdfCollectionAdd(pFdoData->PendingPluginRequests, Request);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"WdfCollectionAdd failed with status %!STATUS!",
|
|
status);
|
|
|
|
goto pluginEnd;
|
|
}
|
|
|
|
TraceEvents(TRACE_LEVEL_INFORMATION,
|
|
TRACE_BUSENUM,
|
|
"Added item with serial: %d",
|
|
plugIn->SerialNo);
|
|
|
|
//
|
|
// At least one request present in the collection; start clean-up timer
|
|
//
|
|
WdfTimerStart(
|
|
pFdoData->PendingPluginRequestsCleanupTimer,
|
|
WDF_REL_TIMEOUT_IN_MS(ORC_TIMER_PERIODIC_DUE_TIME)
|
|
);
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_DRIVER,
|
|
"Started periodic timer");
|
|
|
|
status = NT_SUCCESS(status) ? STATUS_PENDING : status;
|
|
|
|
pluginEnd:
|
|
|
|
WdfSpinLockRelease(pFdoData->PendingPluginRequestsLock);
|
|
|
|
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_BUSENUM, "%!FUNC! Exit with status %!STATUS!", status);
|
|
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Simulates a device unplug event.
|
|
//
|
|
NTSTATUS Bus_UnPlugDevice(
|
|
_In_ WDFDEVICE Device,
|
|
_In_ WDFREQUEST Request,
|
|
_In_ BOOLEAN IsInternal,
|
|
_Out_ size_t* Transferred)
|
|
{
|
|
NTSTATUS status;
|
|
WDFDEVICE hChild;
|
|
WDFCHILDLIST list;
|
|
WDF_CHILD_LIST_ITERATOR iterator;
|
|
WDF_CHILD_RETRIEVE_INFO childInfo;
|
|
PDO_IDENTIFICATION_DESCRIPTION description;
|
|
BOOLEAN unplugAll;
|
|
PVIGEM_UNPLUG_TARGET unPlug;
|
|
WDFFILEOBJECT fileObject;
|
|
PFDO_FILE_DATA pFileData = NULL;
|
|
size_t length = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_BUSENUM, "%!FUNC! Entry");
|
|
|
|
status = WdfRequestRetrieveInputBuffer(Request, sizeof(VIGEM_UNPLUG_TARGET), (PVOID)&unPlug, &length);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"WdfRequestRetrieveInputBuffer failed with status %!STATUS!",
|
|
status);
|
|
return status;
|
|
}
|
|
|
|
if ((sizeof(VIGEM_UNPLUG_TARGET) != unPlug->Size) || (length != unPlug->Size))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"sizeof(VIGEM_UNPLUG_TARGET) buffer size mismatch [%d != %d]",
|
|
sizeof(VIGEM_UNPLUG_TARGET), unPlug->Size);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
*Transferred = length;
|
|
unplugAll = (unPlug->SerialNo == 0);
|
|
|
|
if (!IsInternal)
|
|
{
|
|
fileObject = WdfRequestGetFileObject(Request);
|
|
if (fileObject == NULL)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"WdfRequestGetFileObject failed to fetch WDFFILEOBJECT from request 0x%p",
|
|
Request);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
pFileData = FileObjectGetData(fileObject);
|
|
if (pFileData == NULL)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"FileObjectGetData failed to get context data for 0x%p",
|
|
fileObject);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_BUSENUM,
|
|
"Starting child list traversal");
|
|
|
|
list = WdfFdoGetDefaultChildList(Device);
|
|
|
|
WDF_CHILD_LIST_ITERATOR_INIT(&iterator, WdfRetrievePresentChildren);
|
|
|
|
WdfChildListBeginIteration(list, &iterator);
|
|
|
|
for (;;)
|
|
{
|
|
WDF_CHILD_RETRIEVE_INFO_INIT(&childInfo, &description.Header);
|
|
WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER_INIT(&description.Header, sizeof(description));
|
|
|
|
status = WdfChildListRetrieveNextDevice(list, &iterator, &hChild, &childInfo);
|
|
|
|
// Error or no more children, end loop
|
|
if (!NT_SUCCESS(status) || status == STATUS_NO_MORE_ENTRIES)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_BUSENUM,
|
|
"WdfChildListRetrieveNextDevice returned with status %!STATUS!",
|
|
status);
|
|
break;
|
|
}
|
|
|
|
// If unable to retrieve device
|
|
if (childInfo.Status != WdfChildListRetrieveDeviceSuccess)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_BUSENUM,
|
|
"childInfo.Status = %d",
|
|
childInfo.Status);
|
|
continue;
|
|
}
|
|
|
|
// Child isn't the one we looked for, skip
|
|
if (!unplugAll && description.SerialNo != unPlug->SerialNo)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_BUSENUM,
|
|
"Seeking serial mismatch: %d != %d",
|
|
description.SerialNo,
|
|
unPlug->SerialNo);
|
|
continue;
|
|
}
|
|
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_BUSENUM,
|
|
"description.SessionId = %d, pFileData->SessionId = %d",
|
|
description.SessionId,
|
|
pFileData->SessionId);
|
|
|
|
// Only unplug owned children
|
|
if (IsInternal || description.SessionId == pFileData->SessionId)
|
|
{
|
|
// Unplug child
|
|
status = WdfChildListUpdateChildDescriptionAsMissing(list, &description.Header);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"WdfChildListUpdateChildDescriptionAsMissing failed with status %!STATUS!",
|
|
status);
|
|
}
|
|
}
|
|
}
|
|
|
|
WdfChildListEndIteration(list, &iterator);
|
|
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_BUSENUM,
|
|
"Finished child list traversal");
|
|
|
|
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_BUSENUM, "%!FUNC! Exit with status %!STATUS!", STATUS_SUCCESS);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Sends a report update to an XUSB PDO.
|
|
//
|
|
NTSTATUS Bus_XusbSubmitReport(WDFDEVICE Device, ULONG SerialNo, PXUSB_SUBMIT_REPORT Report, BOOLEAN FromInterface)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_BUSENUM, "%!FUNC! Entry");
|
|
|
|
return Bus_SubmitReport(Device, SerialNo, Report, FromInterface);
|
|
}
|
|
|
|
//
|
|
// Queues an inverted call to receive XUSB-specific updates.
|
|
//
|
|
NTSTATUS Bus_QueueNotification(WDFDEVICE Device, ULONG SerialNo, WDFREQUEST Request)
|
|
{
|
|
NTSTATUS status = STATUS_INVALID_PARAMETER;
|
|
WDFDEVICE hChild;
|
|
PPDO_DEVICE_DATA pdoData;
|
|
PXUSB_DEVICE_DATA xusbData;
|
|
PDS4_DEVICE_DATA ds4Data;
|
|
|
|
|
|
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_BUSENUM, "%!FUNC! Entry");
|
|
|
|
hChild = Bus_GetPdo(Device, SerialNo);
|
|
|
|
// Validate child
|
|
if (hChild == NULL)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"Bus_GetPdo: PDO with serial %d not found",
|
|
SerialNo);
|
|
return STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
// Check common context
|
|
pdoData = PdoGetData(hChild);
|
|
if (pdoData == NULL)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"PdoGetData failed");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
// Check if caller owns this PDO
|
|
if (!IS_OWNER(pdoData))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"PDO & Request ownership mismatch: %d != %d",
|
|
pdoData->OwnerProcessId,
|
|
CURRENT_PROCESS_ID());
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
// Queue the request for later completion by the PDO and return STATUS_PENDING
|
|
switch (pdoData->TargetType)
|
|
{
|
|
case Xbox360Wired:
|
|
|
|
xusbData = XusbGetData(hChild);
|
|
|
|
if (xusbData == NULL)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"XusbGetData failed");
|
|
break;
|
|
}
|
|
|
|
status = WdfRequestForwardToIoQueue(Request, pdoData->PendingNotificationRequests);
|
|
|
|
break;
|
|
case DualShock4Wired:
|
|
|
|
ds4Data = Ds4GetData(hChild);
|
|
|
|
if (ds4Data == NULL)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"Ds4GetData failed");
|
|
break;
|
|
}
|
|
|
|
status = WdfRequestForwardToIoQueue(Request, pdoData->PendingNotificationRequests);
|
|
|
|
break;
|
|
default:
|
|
status = STATUS_NOT_SUPPORTED;
|
|
TraceEvents(TRACE_LEVEL_WARNING,
|
|
TRACE_BUSENUM,
|
|
"Unknown target type: %d (%!STATUS!)",
|
|
pdoData->TargetType,
|
|
status);
|
|
break;
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"WdfRequestForwardToIoQueue failed with status %!STATUS!",
|
|
status);
|
|
}
|
|
|
|
status = (NT_SUCCESS(status)) ? STATUS_PENDING : status;
|
|
|
|
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_BUSENUM, "%!FUNC! Exit with status %!STATUS!", status);
|
|
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Sends a report update to a DS4 PDO.
|
|
//
|
|
NTSTATUS Bus_Ds4SubmitReport(WDFDEVICE Device, ULONG SerialNo, PDS4_SUBMIT_REPORT Report, BOOLEAN FromInterface)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_BUSENUM, "%!FUNC! Entry");
|
|
|
|
return Bus_SubmitReport(Device, SerialNo, Report, FromInterface);
|
|
}
|
|
|
|
NTSTATUS Bus_XgipSubmitReport(WDFDEVICE Device, ULONG SerialNo, PXGIP_SUBMIT_REPORT Report, BOOLEAN FromInterface)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_BUSENUM, "%!FUNC! Entry");
|
|
|
|
return Bus_SubmitReport(Device, SerialNo, Report, FromInterface);
|
|
}
|
|
|
|
NTSTATUS Bus_XgipSubmitInterrupt(WDFDEVICE Device, ULONG SerialNo, PXGIP_SUBMIT_INTERRUPT Report, BOOLEAN FromInterface)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_BUSENUM, "%!FUNC! Entry");
|
|
|
|
return Bus_SubmitReport(Device, SerialNo, Report, FromInterface);
|
|
}
|
|
|
|
WDFDEVICE Bus_GetPdo(IN WDFDEVICE Device, IN ULONG SerialNo)
|
|
{
|
|
WDFCHILDLIST list;
|
|
WDF_CHILD_RETRIEVE_INFO info;
|
|
|
|
list = WdfFdoGetDefaultChildList(Device);
|
|
|
|
PDO_IDENTIFICATION_DESCRIPTION description;
|
|
|
|
WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER_INIT(&description.Header, sizeof(description));
|
|
|
|
description.SerialNo = SerialNo;
|
|
|
|
WDF_CHILD_RETRIEVE_INFO_INIT(&info, &description.Header);
|
|
|
|
return WdfChildListRetrievePdo(list, &info);
|
|
}
|
|
|
|
NTSTATUS Bus_SubmitReport(WDFDEVICE Device, ULONG SerialNo, PVOID Report, BOOLEAN FromInterface)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
WDFDEVICE hChild;
|
|
PPDO_DEVICE_DATA pdoData;
|
|
WDFREQUEST usbRequest;
|
|
PIRP pendingIrp;
|
|
BOOLEAN changed;
|
|
|
|
|
|
TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_BUSENUM, "%!FUNC! Entry");
|
|
|
|
hChild = Bus_GetPdo(Device, SerialNo);
|
|
|
|
// Validate child
|
|
if (hChild == NULL)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"Bus_GetPdo: PDO with serial %d not found",
|
|
SerialNo);
|
|
return STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
// Check common context
|
|
pdoData = PdoGetData(hChild);
|
|
if (pdoData == NULL)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"PdoGetData failed");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
// Check if caller owns this PDO
|
|
if (!FromInterface && !IS_OWNER(pdoData))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR,
|
|
TRACE_BUSENUM,
|
|
"PDO & Request ownership mismatch: %d != %d",
|
|
pdoData->OwnerProcessId,
|
|
CURRENT_PROCESS_ID());
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
// Check if input is different from previous value
|
|
switch (pdoData->TargetType)
|
|
{
|
|
case Xbox360Wired:
|
|
|
|
changed = (RtlCompareMemory(&XusbGetData(hChild)->Packet.Report,
|
|
&((PXUSB_SUBMIT_REPORT)Report)->Report,
|
|
sizeof(XUSB_REPORT)) != sizeof(XUSB_REPORT));
|
|
|
|
break;
|
|
case DualShock4Wired:
|
|
|
|
changed = TRUE;
|
|
|
|
break;
|
|
case XboxOneWired:
|
|
|
|
// TODO: necessary?
|
|
changed = TRUE;
|
|
|
|
break;
|
|
default:
|
|
|
|
changed = FALSE;
|
|
|
|
break;
|
|
}
|
|
|
|
// Don't waste pending IRP if input hasn't changed
|
|
if (!changed)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_BUSENUM,
|
|
"Input report hasn't changed since last update, aborting with %!STATUS!",
|
|
status);
|
|
return status;
|
|
}
|
|
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_BUSENUM,
|
|
"Received new report, processing");
|
|
|
|
// Get pending USB request
|
|
switch (pdoData->TargetType)
|
|
{
|
|
case Xbox360Wired:
|
|
|
|
status = WdfIoQueueRetrieveNextRequest(pdoData->PendingUsbInRequests, &usbRequest);
|
|
|
|
break;
|
|
case DualShock4Wired:
|
|
|
|
status = WdfIoQueueRetrieveNextRequest(pdoData->PendingUsbInRequests, &usbRequest);
|
|
|
|
break;
|
|
case XboxOneWired:
|
|
|
|
// Request is control data
|
|
if (((PXGIP_SUBMIT_INTERRUPT)Report)->Size == sizeof(XGIP_SUBMIT_INTERRUPT))
|
|
{
|
|
PXGIP_DEVICE_DATA xgip = XgipGetData(hChild);
|
|
PXGIP_SUBMIT_INTERRUPT interrupt = (PXGIP_SUBMIT_INTERRUPT)Report;
|
|
WDFMEMORY memory;
|
|
WDF_OBJECT_ATTRIBUTES memAttribs;
|
|
WDF_OBJECT_ATTRIBUTES_INIT(&memAttribs);
|
|
|
|
memAttribs.ParentObject = hChild;
|
|
|
|
// Allocate kernel memory
|
|
status = WdfMemoryCreate(&memAttribs, NonPagedPool, VIGEM_POOL_TAG,
|
|
interrupt->InterruptLength, &memory, NULL);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
KdPrint((DRIVERNAME "WdfMemoryCreate failed with status 0x%X\n", status));
|
|
goto endSubmitReport;
|
|
}
|
|
|
|
// Copy interrupt buffer to memory object
|
|
status = WdfMemoryCopyFromBuffer(memory, 0, interrupt->Interrupt, interrupt->InterruptLength);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
KdPrint((DRIVERNAME "WdfMemoryCopyFromBuffer failed with status 0x%X\n", status));
|
|
goto endSubmitReport;
|
|
}
|
|
|
|
// Add memory object to collection
|
|
status = WdfCollectionAdd(xgip->XboxgipSysInitCollection, memory);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
KdPrint((DRIVERNAME "WdfCollectionAdd failed with status 0x%X\n", status));
|
|
goto endSubmitReport;
|
|
}
|
|
|
|
// Check if all packets have been received
|
|
xgip->XboxgipSysInitReady =
|
|
WdfCollectionGetCount(xgip->XboxgipSysInitCollection) == XGIP_SYS_INIT_PACKETS;
|
|
|
|
// If all packets are cached, start initialization timer
|
|
if (xgip->XboxgipSysInitReady)
|
|
{
|
|
WdfTimerStart(xgip->XboxgipSysInitTimer, XGIP_SYS_INIT_PERIOD);
|
|
}
|
|
|
|
goto endSubmitReport;
|
|
}
|
|
|
|
status = WdfIoQueueRetrieveNextRequest(XgipGetData(hChild)->PendingUsbInRequests, &usbRequest);
|
|
|
|
break;
|
|
default:
|
|
|
|
status = STATUS_NOT_SUPPORTED;
|
|
|
|
TraceEvents(TRACE_LEVEL_WARNING,
|
|
TRACE_BUSENUM,
|
|
"Unknown target type: %d (%!STATUS!)",
|
|
pdoData->TargetType,
|
|
status);
|
|
|
|
goto endSubmitReport;
|
|
}
|
|
|
|
if (status == STATUS_PENDING)
|
|
goto endSubmitReport;
|
|
else if (!NT_SUCCESS(status))
|
|
goto endSubmitReport;
|
|
|
|
TraceEvents(TRACE_LEVEL_VERBOSE,
|
|
TRACE_BUSENUM,
|
|
"Processing pending IRP");
|
|
|
|
// Get pending IRP
|
|
pendingIrp = WdfRequestWdmGetIrp(usbRequest);
|
|
|
|
// Get USB request block
|
|
PURB urb = (PURB)URB_FROM_IRP(pendingIrp);
|
|
|
|
// Get transfer buffer
|
|
PUCHAR Buffer = (PUCHAR)urb->UrbBulkOrInterruptTransfer.TransferBuffer;
|
|
|
|
switch (pdoData->TargetType)
|
|
{
|
|
case Xbox360Wired:
|
|
|
|
urb->UrbBulkOrInterruptTransfer.TransferBufferLength = sizeof(XUSB_INTERRUPT_IN_PACKET);
|
|
|
|
// Copy submitted report to cache
|
|
RtlCopyBytes(&XusbGetData(hChild)->Packet.Report, &((PXUSB_SUBMIT_REPORT)Report)->Report, sizeof(XUSB_REPORT));
|
|
// Copy cached report to URB transfer buffer
|
|
RtlCopyBytes(Buffer, &XusbGetData(hChild)->Packet, sizeof(XUSB_INTERRUPT_IN_PACKET));
|
|
|
|
break;
|
|
case DualShock4Wired:
|
|
|
|
urb->UrbBulkOrInterruptTransfer.TransferBufferLength = DS4_REPORT_SIZE;
|
|
|
|
/* Copy report to cache and transfer buffer
|
|
* Skip first byte as it contains the never changing report id */
|
|
RtlCopyBytes(Ds4GetData(hChild)->Report + 1, &((PDS4_SUBMIT_REPORT)Report)->Report, sizeof(DS4_REPORT));
|
|
|
|
if (Buffer)
|
|
RtlCopyBytes(Buffer, Ds4GetData(hChild)->Report, DS4_REPORT_SIZE);
|
|
|
|
break;
|
|
case XboxOneWired:
|
|
|
|
// Request is input report
|
|
if (((PXGIP_SUBMIT_REPORT)Report)->Size == sizeof(XGIP_SUBMIT_REPORT))
|
|
{
|
|
urb->UrbBulkOrInterruptTransfer.TransferBufferLength = XGIP_REPORT_SIZE;
|
|
|
|
// Increase event counter on every call (can roll-over)
|
|
XgipGetData(hChild)->Report[2]++;
|
|
|
|
/* Copy report to cache and transfer buffer
|
|
* Skip first four bytes as they are not part of the report */
|
|
RtlCopyBytes(XgipGetData(hChild)->Report + 4, &((PXGIP_SUBMIT_REPORT)Report)->Report, sizeof(XGIP_REPORT));
|
|
RtlCopyBytes(Buffer, XgipGetData(hChild)->Report, XGIP_REPORT_SIZE);
|
|
|
|
break;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
// Complete pending request
|
|
WdfRequestComplete(usbRequest, status);
|
|
|
|
endSubmitReport:
|
|
|
|
TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_BUSENUM, "%!FUNC! Exit with status %!STATUS!", status);
|
|
|
|
return status;
|
|
}
|
|
|