hyperv: Add support for mouse device
This change adds driver support for the Hyper-V HID mouse (fixes #16665 and #18882). Change-Id: I45ee1f3c2fad10f07080d7966184856bd0a22f61 Reviewed-on: https://review.haiku-os.org/c/haiku/+/10334 Tested-by: Commit checker robot <[email protected]> Reviewed-by: waddlesplash <[email protected]> Reviewed-by: Adrien Destugues <[email protected]>
This commit is contained in:
committed by
Adrien Destugues
parent
767d7cf9fa
commit
a3eda7cbda
@@ -144,6 +144,7 @@ AddNewDriversToPackage network :
|
||||
virtio_net
|
||||
;
|
||||
AddNewDriversToPackage input :
|
||||
hyperv_hid@x86,x86_64
|
||||
i2c_elan
|
||||
virtio_input
|
||||
;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
SubDir HAIKU_TOP src add-ons kernel drivers input ;
|
||||
|
||||
SubInclude HAIKU_TOP src add-ons kernel drivers input hyperv_hid ;
|
||||
SubInclude HAIKU_TOP src add-ons kernel drivers input i2c_elan ;
|
||||
SubInclude HAIKU_TOP src add-ons kernel drivers input i2c_hid ;
|
||||
SubInclude HAIKU_TOP src add-ons kernel drivers input ps2_hid ;
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright 2026 John Davis. All rights reserved.
|
||||
* Copyright 2020, Jérôme Duval, [email protected].
|
||||
* Copyright 2008-2011 Michael Lotz <[email protected]>
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
|
||||
#include "DeviceList.h"
|
||||
#include "Driver.h"
|
||||
#include "HIDDevice.h"
|
||||
#include "ProtocolHandler.h"
|
||||
|
||||
#include <lock.h>
|
||||
#include <util/AutoLock.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
device_node* node;
|
||||
hyperv_device_interface* hyperv;
|
||||
hyperv_device hyperv_cookie;
|
||||
HIDDevice* hid_device;
|
||||
} hid_driver_cookie;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ProtocolHandler* handler;
|
||||
uint32 cookie;
|
||||
hid_driver_cookie* driver_cookie;
|
||||
} device_cookie;
|
||||
|
||||
|
||||
static device_manager_info* sDeviceManager;
|
||||
DeviceList* gDeviceList = NULL;
|
||||
static mutex sDriverLock;
|
||||
|
||||
|
||||
static status_t
|
||||
hyperv_hid_init_device(void* driverCookie, void** _deviceCookie)
|
||||
{
|
||||
CALLED();
|
||||
*_deviceCookie = driverCookie;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
hyperv_hid_uninit_device(void* deviceCookie)
|
||||
{
|
||||
CALLED();
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
hyperv_hid_open(void* deviceCookie, const char* path, int openMode, void** _cookie)
|
||||
{
|
||||
CALLED();
|
||||
|
||||
device_cookie* cookie = new(std::nothrow) device_cookie();
|
||||
if (cookie == NULL)
|
||||
return B_NO_MEMORY;
|
||||
cookie->driver_cookie = reinterpret_cast<hid_driver_cookie*>(deviceCookie);
|
||||
|
||||
MutexLocker locker(sDriverLock);
|
||||
ProtocolHandler* handler = reinterpret_cast<ProtocolHandler*>(gDeviceList->FindDevice(path));
|
||||
if (handler == NULL) {
|
||||
delete cookie;
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
}
|
||||
|
||||
cookie->handler = handler;
|
||||
cookie->cookie = 0;
|
||||
|
||||
status_t status = handler->Open(openMode, &cookie->cookie);
|
||||
if (status != B_OK) {
|
||||
delete cookie;
|
||||
return status;
|
||||
}
|
||||
|
||||
*_cookie = cookie;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
hyperv_hid_close(void* cookie)
|
||||
{
|
||||
CALLED();
|
||||
device_cookie* deviceCookie = reinterpret_cast<device_cookie*>(cookie);
|
||||
return deviceCookie->handler->Close(&deviceCookie->cookie);
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
hyperv_hid_free(void* cookie)
|
||||
{
|
||||
CALLED();
|
||||
|
||||
device_cookie* deviceCookie = reinterpret_cast<device_cookie*>(cookie);
|
||||
|
||||
mutex_lock(&sDriverLock);
|
||||
|
||||
HIDDevice* device = deviceCookie->handler->Device();
|
||||
if (device->IsOpen()) {
|
||||
// Another handler of this device is still open so we can't free it
|
||||
} else if (device->IsRemoved()) {
|
||||
// The parent device is removed already and none of its handlers are
|
||||
// open anymore so we can free it here
|
||||
delete device;
|
||||
}
|
||||
|
||||
mutex_unlock(&sDriverLock);
|
||||
|
||||
delete deviceCookie;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
hyperv_hid_read(void* cookie, off_t pos, void* buffer, size_t* _length)
|
||||
{
|
||||
CALLED();
|
||||
device_cookie* deviceCookie = reinterpret_cast<device_cookie*>(cookie);
|
||||
return deviceCookie->handler->Read(&deviceCookie->cookie, pos, buffer, _length);
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
hyperv_hid_write(void* cookie, off_t pos, const void* buffer, size_t* _length)
|
||||
{
|
||||
CALLED();
|
||||
device_cookie* deviceCookie = reinterpret_cast<device_cookie*>(cookie);
|
||||
return deviceCookie->handler->Write(&deviceCookie->cookie, pos, buffer, _length);
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
hyperv_hid_control(void* cookie, uint32 op, void* buffer, size_t length)
|
||||
{
|
||||
CALLED();
|
||||
device_cookie* deviceCookie = reinterpret_cast<device_cookie*>(cookie);
|
||||
return deviceCookie->handler->Control(&deviceCookie->cookie, op, buffer, length);
|
||||
}
|
||||
|
||||
|
||||
static float
|
||||
hyperv_hid_supports_device(device_node* parent)
|
||||
{
|
||||
CALLED();
|
||||
|
||||
// Check if parent is the Hyper-V bus manager
|
||||
const char* bus;
|
||||
if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) != B_OK)
|
||||
return -1;
|
||||
|
||||
if (strcmp(bus, HYPERV_BUS_NAME) != 0)
|
||||
return 0.0f;
|
||||
|
||||
// Check if parent is a Hyper-V Input device
|
||||
const char* type;
|
||||
if (sDeviceManager->get_attr_string(parent, HYPERV_DEVICE_TYPE_ITEM, &type, false) != B_OK)
|
||||
return 0.0f;
|
||||
|
||||
if (strcmp(type, VMBUS_TYPE_INPUT) != 0)
|
||||
return 0.0f;
|
||||
|
||||
TRACE("Hyper-V HID device found!\n");
|
||||
return 0.8f;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
hyperv_hid_register_device(device_node* parent)
|
||||
{
|
||||
CALLED();
|
||||
|
||||
device_attr attributes[] = {
|
||||
{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE,
|
||||
{ .string = HYPERV_PRETTYNAME_INPUT }},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
return sDeviceManager->register_node(parent, HYPERV_HID_DRIVER_MODULE_NAME, attributes, NULL,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
hyperv_hid_init_driver(device_node* node, void** _driverCookie)
|
||||
{
|
||||
CALLED();
|
||||
|
||||
hid_driver_cookie* hidCookie = new(std::nothrow) hid_driver_cookie;
|
||||
if (hidCookie == NULL)
|
||||
return B_NO_MEMORY;
|
||||
memset(hidCookie, 0, sizeof(*hidCookie));
|
||||
|
||||
hidCookie->node = node;
|
||||
|
||||
device_node* parent = sDeviceManager->get_parent_node(node);
|
||||
sDeviceManager->get_driver(parent, (driver_module_info**)&hidCookie->hyperv,
|
||||
(void**)&hidCookie->hyperv_cookie);
|
||||
sDeviceManager->put_node(parent);
|
||||
|
||||
mutex_lock(&sDriverLock);
|
||||
HIDDevice* hidDevice = new(std::nothrow) HIDDevice(hidCookie->hyperv,
|
||||
hidCookie->hyperv_cookie);
|
||||
if (hidDevice == NULL) {
|
||||
mutex_unlock(&sDriverLock);
|
||||
delete hidCookie;
|
||||
return B_NO_MEMORY;
|
||||
}
|
||||
|
||||
status_t status = hidDevice->InitCheck();
|
||||
if (status != B_OK) {
|
||||
mutex_unlock(&sDriverLock);
|
||||
delete hidDevice;
|
||||
delete hidCookie;
|
||||
return status;
|
||||
}
|
||||
|
||||
hidCookie->hid_device = hidDevice;
|
||||
|
||||
mutex_unlock(&sDriverLock);
|
||||
|
||||
*_driverCookie = hidCookie;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
hyperv_hid_uninit_driver(void* driverCookie)
|
||||
{
|
||||
CALLED();
|
||||
hid_driver_cookie* hidCookie = reinterpret_cast<hid_driver_cookie*>(driverCookie);
|
||||
delete hidCookie;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
hyperv_hid_register_child_devices(void* driverCookie)
|
||||
{
|
||||
CALLED();
|
||||
hid_driver_cookie* hidCookie = reinterpret_cast<hid_driver_cookie*>(driverCookie);
|
||||
HIDDevice* hidDevice = hidCookie->hid_device;
|
||||
|
||||
for (uint32 i = 0;; i++) {
|
||||
ProtocolHandler* handler = hidDevice->ProtocolHandlerAt(i);
|
||||
if (handler == NULL)
|
||||
break;
|
||||
|
||||
// As devices can be un- and replugged at will, we cannot
|
||||
// simply rely on a device count. If there is just one
|
||||
// keyboard, this does not mean that it uses the 0 name.
|
||||
// There might have been two keyboards and the one using 0
|
||||
// might have been unplugged. So we just generate names
|
||||
// until we find one that is not currently in use.
|
||||
int32 index = 0;
|
||||
char pathBuffer[B_DEV_NAME_LENGTH];
|
||||
const char* basePath = handler->BasePath();
|
||||
while (true) {
|
||||
sprintf(pathBuffer, "%s%" B_PRId32, basePath, index++);
|
||||
if (gDeviceList->FindDevice(pathBuffer) == NULL) {
|
||||
// Name is still free
|
||||
handler->SetPublishPath(strdup(pathBuffer));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gDeviceList->AddDevice(handler->PublishPath(), handler);
|
||||
sDeviceManager->publish_device(hidCookie->node, pathBuffer,
|
||||
HYPERV_HID_DEVICE_MODULE_NAME);
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
std_ops(int32 op, ...)
|
||||
{
|
||||
switch (op) {
|
||||
case B_MODULE_INIT:
|
||||
gDeviceList = new(std::nothrow) DeviceList();
|
||||
if (gDeviceList == NULL)
|
||||
return B_NO_MEMORY;
|
||||
mutex_init(&sDriverLock, "hyper-v hid driver lock");
|
||||
|
||||
return B_OK;
|
||||
case B_MODULE_UNINIT:
|
||||
delete gDeviceList;
|
||||
gDeviceList = NULL;
|
||||
mutex_destroy(&sDriverLock);
|
||||
return B_OK;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static device_module_info sHyperVHIDDeviceModule = {
|
||||
{
|
||||
HYPERV_HID_DEVICE_MODULE_NAME,
|
||||
0,
|
||||
NULL
|
||||
},
|
||||
|
||||
hyperv_hid_init_device,
|
||||
hyperv_hid_uninit_device,
|
||||
NULL, // remove
|
||||
|
||||
hyperv_hid_open,
|
||||
hyperv_hid_close,
|
||||
hyperv_hid_free,
|
||||
hyperv_hid_read,
|
||||
hyperv_hid_write,
|
||||
NULL, // io
|
||||
hyperv_hid_control,
|
||||
|
||||
NULL, // select
|
||||
NULL // deselect
|
||||
};
|
||||
|
||||
|
||||
static driver_module_info sHyperVHIDDriverModule = {
|
||||
{
|
||||
HYPERV_HID_DRIVER_MODULE_NAME,
|
||||
0,
|
||||
&std_ops
|
||||
},
|
||||
|
||||
hyperv_hid_supports_device,
|
||||
hyperv_hid_register_device,
|
||||
hyperv_hid_init_driver,
|
||||
hyperv_hid_uninit_driver,
|
||||
hyperv_hid_register_child_devices,
|
||||
NULL, // rescan
|
||||
NULL // removed
|
||||
};
|
||||
|
||||
|
||||
module_dependency module_dependencies[] = {
|
||||
{ B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
|
||||
module_info* modules[] = {
|
||||
(module_info*)&sHyperVHIDDriverModule,
|
||||
(module_info*)&sHyperVHIDDeviceModule,
|
||||
NULL
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2026 John Davis. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef _HYPERV_HID_DRIVER_H_
|
||||
#define _HYPERV_HID_DRIVER_H_
|
||||
|
||||
#include <Drivers.h>
|
||||
#include <KernelExport.h>
|
||||
#include <OS.h>
|
||||
#include <util/kernel_cpp.h>
|
||||
|
||||
#define DRIVER_NAME "hyperv_hid"
|
||||
#define DEVICE_PATH_SUFFIX "hyperv"
|
||||
#define DEVICE_NAME "Hyper-V HID"
|
||||
|
||||
//#define TRACE_HYPERV_HID
|
||||
#ifdef TRACE_HYPERV_HID
|
||||
# define TRACE(x...) dprintf("\33[94mhyperv_hid:\33[0m " x)
|
||||
#else
|
||||
# define TRACE(x...) ;
|
||||
#endif
|
||||
#define TRACE_ALWAYS(x...) dprintf("\33[94mhyperv_hid:\33[0m " x)
|
||||
#define ERROR(x...) dprintf("\33[94mhyperv_hid:\33[0m " x)
|
||||
#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
|
||||
|
||||
#define HYPERV_HID_DRIVER_MODULE_NAME "drivers/input/hyperv_hid/driver_v1"
|
||||
#define HYPERV_HID_DEVICE_MODULE_NAME "drivers/input/hyperv_hid/device_v1"
|
||||
|
||||
|
||||
#endif // _HYPERV_HID_DRIVER_H_
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Copyright 2026 John Davis. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
|
||||
#include "Driver.h"
|
||||
#include "HIDCollection.h"
|
||||
#include "HIDDevice.h"
|
||||
#include "HIDReport.h"
|
||||
#include "HIDReportItem.h"
|
||||
#include "HIDWriter.h"
|
||||
#include "ProtocolHandler.h"
|
||||
#include <usb/USB_hid.h>
|
||||
|
||||
|
||||
HIDDevice::HIDDevice(hyperv_device_interface* hyperv,
|
||||
hyperv_device hyperv_cookie)
|
||||
:
|
||||
fStatus(B_NO_INIT),
|
||||
fOpenCount(0),
|
||||
fRemoved(false),
|
||||
fParser(this),
|
||||
fProtocolHandlerCount(0),
|
||||
fProtocolHandlerList(NULL),
|
||||
fHyperV(hyperv),
|
||||
fHyperVCookie(hyperv_cookie),
|
||||
fDeviceInfo(NULL),
|
||||
fDeviceInfoLength(0),
|
||||
fPacket(NULL),
|
||||
fLastX(0),
|
||||
fLastY(0)
|
||||
{
|
||||
CALLED();
|
||||
|
||||
fProtocolRespEvent.Init(this, "hyper-v hid protoresp");
|
||||
fDeviceInfoEvent.Init(this, "hyper-v hid devinfo");
|
||||
|
||||
fPacket = malloc(HV_HID_RX_PKT_BUFFER_SIZE);
|
||||
if (fPacket == NULL) {
|
||||
fStatus = B_NO_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
fStatus = _Connect();
|
||||
if (fStatus != B_OK) {
|
||||
ERROR("Failed to connect to Hyper-V HID\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ProtocolHandler::AddHandlers(*this, fProtocolHandlerList, fProtocolHandlerCount);
|
||||
}
|
||||
|
||||
|
||||
HIDDevice::~HIDDevice()
|
||||
{
|
||||
CALLED();
|
||||
|
||||
ProtocolHandler* handler = fProtocolHandlerList;
|
||||
while (handler != NULL) {
|
||||
ProtocolHandler* next = handler->NextHandler();
|
||||
delete handler;
|
||||
handler = next;
|
||||
}
|
||||
|
||||
free(fPacket);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::Open(ProtocolHandler* handler, uint32 flags)
|
||||
{
|
||||
atomic_add(&fOpenCount, 1);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::Close(ProtocolHandler* handler)
|
||||
{
|
||||
atomic_add(&fOpenCount, -1);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
HIDDevice::Removed()
|
||||
{
|
||||
fRemoved = true;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::MaybeScheduleTransfer(HIDReport* report)
|
||||
{
|
||||
if (fRemoved)
|
||||
return ENODEV;
|
||||
|
||||
// Reports get sent as they become available, cannot query on-demand
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::SendReport(HIDReport* report)
|
||||
{
|
||||
// Hyper-V does not support reports to itself
|
||||
return B_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
ProtocolHandler*
|
||||
HIDDevice::ProtocolHandlerAt(uint32 index) const
|
||||
{
|
||||
ProtocolHandler* handler = fProtocolHandlerList;
|
||||
while (handler != NULL) {
|
||||
if (index == 0)
|
||||
return handler;
|
||||
|
||||
handler = handler->NextHandler();
|
||||
index--;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*static*/ void
|
||||
HIDDevice::_CallbackHandler(void* data)
|
||||
{
|
||||
HIDDevice* hidDevice = reinterpret_cast<HIDDevice*>(data);
|
||||
hidDevice->_Callback();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
HIDDevice::_Callback()
|
||||
{
|
||||
while (true) {
|
||||
vmbus_pkt_header header;
|
||||
uint32 headerLength = sizeof(header);
|
||||
uint32 packetLength = HV_HID_RX_PKT_BUFFER_SIZE;
|
||||
|
||||
status_t status = fHyperV->read_packet(fHyperVCookie, &header, &headerLength,
|
||||
fPacket, &packetLength);
|
||||
if (status == B_DEV_NOT_READY) {
|
||||
break;
|
||||
} else if (status != B_OK) {
|
||||
ERROR("Failed to read packet (%s)\n", strerror(status));
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if this is an HID pipe data message
|
||||
hv_hid_pipe_in_msg* message = reinterpret_cast<hv_hid_pipe_in_msg*>(fPacket);
|
||||
if (message->pipe_header.type != HV_HID_PIPE_MSGTYPE_DATA) {
|
||||
ERROR("Non-data HID pipe message type %u received\n", message->pipe_header.type);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (message->header.type) {
|
||||
case HV_HID_MSGTYPE_PROTOCOL_RESPONSE:
|
||||
memcpy(&fProtocolResponse, &message->protocol_resp, sizeof(fProtocolResponse));
|
||||
fProtocolRespEvent.NotifyAll();
|
||||
break;
|
||||
|
||||
case HV_HID_MSGTYPE_INITIAL_DEV_INFO:
|
||||
if (fDeviceInfo != NULL)
|
||||
free(fDeviceInfo);
|
||||
fDeviceInfo = reinterpret_cast<hv_hid_msg_initial_dev_info*>(
|
||||
malloc(message->header.length));
|
||||
if (fDeviceInfo != NULL) {
|
||||
fDeviceInfoLength = message->header.length;
|
||||
memcpy(fDeviceInfo, &message->dev_info, fDeviceInfoLength);
|
||||
fDeviceInfoEvent.NotifyAll();
|
||||
} else {
|
||||
fDeviceInfoLength = 0;
|
||||
ERROR("Failed to allocate device info\n");
|
||||
fDeviceInfoEvent.NotifyAll(B_NO_MEMORY);
|
||||
}
|
||||
break;
|
||||
|
||||
case HV_HID_MSGTYPE_INPUT_REPORT:
|
||||
_HandleInputReport(&message->input_report);
|
||||
break;
|
||||
|
||||
default:
|
||||
TRACE("Unexpected HID message type %u received\n", message->header.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
HIDDevice::_HandleInputReport(hv_hid_msg_input_report* reportMessage)
|
||||
{
|
||||
if (reportMessage->header.length < sizeof(hyperv_hid_input_report))
|
||||
return;
|
||||
|
||||
hyperv_hid_input_report* report
|
||||
= reinterpret_cast<hyperv_hid_input_report*>(reportMessage->data);
|
||||
TRACE("New input report (btn %u x %u y %u wheel %d)\n", report->buttons, report->x, report->y,
|
||||
report->wheel);
|
||||
|
||||
// Some versions of Hyper-V report an X/Y of zero when the wheel is scrolling,
|
||||
// use the last reported X/Y
|
||||
if (report->wheel != 0 && report->x == 0 && report->y == 0) {
|
||||
report->x = fLastX;
|
||||
report->y = fLastY;
|
||||
TRACE("Fixed input report x %u y %u\n", report->x, report->y);
|
||||
} else {
|
||||
fLastX = report->x;
|
||||
fLastY = report->y;
|
||||
}
|
||||
|
||||
fParser.SetReport(B_OK, reportMessage->data, reportMessage->header.length);
|
||||
}
|
||||
|
||||
status_t
|
||||
HIDDevice::_Connect()
|
||||
{
|
||||
status_t status = fHyperV->open(fHyperVCookie, HV_HID_RING_SIZE, HV_HID_RING_SIZE,
|
||||
_CallbackHandler, this);
|
||||
if (status != B_OK) {
|
||||
ERROR("Failed to open channel\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
hv_hid_pipe_out_msg message;
|
||||
message.pipe_header.type = HV_HID_PIPE_MSGTYPE_DATA;
|
||||
message.pipe_header.length = sizeof(message.protocol_req);
|
||||
message.header.type = HV_HID_MSGTYPE_PROTOCOL_REQUEST;
|
||||
message.header.length = message.pipe_header.length - sizeof(message.header);
|
||||
message.protocol_req.version = HV_HID_VERSION_V2_0;
|
||||
|
||||
ConditionVariableEntry protocolRespEntry;
|
||||
ConditionVariableEntry deviceInfoEntry;
|
||||
fProtocolRespEvent.Add(&protocolRespEntry);
|
||||
fDeviceInfoEvent.Add(&deviceInfoEntry);
|
||||
|
||||
// Send the protocol request message and wait
|
||||
// Initial device info is received immediately after protocol response
|
||||
status = fHyperV->write_packet(fHyperVCookie, VMBUS_PKTTYPE_DATA_INBAND, &message,
|
||||
sizeof(message.pipe_header) + message.pipe_header.length, TRUE, HV_HID_REQUEST_TRANS_ID);
|
||||
if (status != B_OK) {
|
||||
ERROR("Failed to send HID protocol request\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
status = protocolRespEntry.Wait(B_RELATIVE_TIMEOUT | B_CAN_INTERRUPT, HV_HID_TIMEOUT_US);
|
||||
if (status != B_OK)
|
||||
return status;
|
||||
|
||||
TRACE("HID protocol version %u.%u status %u\n",
|
||||
GET_HID_VERSION_MAJOR(fProtocolResponse.version),
|
||||
GET_HID_VERSION_MINOR(fProtocolResponse.version), fProtocolResponse.result);
|
||||
|
||||
status = deviceInfoEntry.Wait(B_RELATIVE_TIMEOUT | B_CAN_INTERRUPT, HV_HID_TIMEOUT_US);
|
||||
if (status != B_OK)
|
||||
return status;
|
||||
|
||||
message.pipe_header.type = HV_HID_PIPE_MSGTYPE_DATA;
|
||||
message.pipe_header.length = sizeof(message.dev_info_ack);
|
||||
message.header.type = HV_HID_MSGTYPE_INITIAL_DEV_INFO_ACK;
|
||||
message.header.length = message.pipe_header.length - sizeof(message.header);
|
||||
message.dev_info_ack.reserved = 0;
|
||||
|
||||
// Send device info acknowledgement to Hyper-V
|
||||
status = fHyperV->write_packet(fHyperVCookie, VMBUS_PKTTYPE_DATA_INBAND, &message,
|
||||
sizeof(message.pipe_header) + message.pipe_header.length, FALSE, HV_HID_REQUEST_TRANS_ID);
|
||||
if (status != B_OK) {
|
||||
ERROR("Failed to send HID device info ack\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
TRACE("Hyper-V HID vid 0x%04X pid 0x%04X version 0x%X\n", fDeviceInfo->info.vendor_id,
|
||||
fDeviceInfo->info.product_id, fDeviceInfo->info.version);
|
||||
|
||||
status = fParser.ParseReportDescriptor(fDeviceInfo->descriptor_data,
|
||||
fDeviceInfo->descriptor.hid_descriptor_length);
|
||||
free(fDeviceInfo);
|
||||
fDeviceInfo = NULL;
|
||||
|
||||
HIDReport* report = fParser.ReportAt(HID_REPORT_TYPE_INPUT, 0);
|
||||
if (report == NULL)
|
||||
return B_ERROR;
|
||||
|
||||
#ifdef TRACE_HYPERV_HID
|
||||
report->PrintToStream();
|
||||
#endif
|
||||
|
||||
// Check report for wheel item, if not present add a wheel item
|
||||
HIDReportItem* wheelItem = report->FindItem(B_HID_USAGE_PAGE_GENERIC_DESKTOP,
|
||||
B_HID_UID_GD_WHEEL);
|
||||
if (wheelItem == NULL) {
|
||||
TRACE("No wheel found in HID report descriptor, adding\n");
|
||||
status = _AddWheelHIDItem(report);
|
||||
if (status != B_OK)
|
||||
ERROR("Failed to add wheel device to HID descriptor (%s)\n", strerror(status));
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::_AddWheelHIDItem(HIDReport* report)
|
||||
{
|
||||
HIDCollection* rootCollection = fParser.RootCollection();
|
||||
if (rootCollection == NULL)
|
||||
return B_ERROR;
|
||||
|
||||
HIDCollection* mouseCollection = NULL;
|
||||
for (uint32 i = 0; i < rootCollection->CountChildrenFlat(COLLECTION_APPLICATION); i++) {
|
||||
HIDCollection* collection = rootCollection->ChildAtFlat(COLLECTION_APPLICATION, i);
|
||||
if (collection != NULL && (collection->UsageID() == B_HID_UID_GD_MOUSE
|
||||
|| collection->UsageID() == B_HID_UID_GD_POINTER)) {
|
||||
mouseCollection = collection;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mouseCollection == NULL)
|
||||
return B_ERROR;
|
||||
|
||||
global_item_state globalState;
|
||||
memset(&globalState, 0, sizeof(global_item_state));
|
||||
|
||||
local_item_state localState;
|
||||
memset(&localState, 0, sizeof(local_item_state));
|
||||
|
||||
main_item_data_converter converter;
|
||||
converter.flat_data = 0;
|
||||
converter.main_data.array_variable = 1;
|
||||
|
||||
globalState.logical_minimum = -127;
|
||||
globalState.logical_maximum = 127;
|
||||
globalState.report_count = 1;
|
||||
globalState.report_size = 8;
|
||||
globalState.usage_page = B_HID_USAGE_PAGE_GENERIC_DESKTOP;
|
||||
|
||||
usage_value usageValue;
|
||||
usageValue.u.s.usage_page = B_HID_USAGE_PAGE_GENERIC_DESKTOP;
|
||||
usageValue.u.s.usage_id = B_HID_UID_GD_WHEEL;
|
||||
|
||||
localState.usage_stack_used = 1;
|
||||
localState.usage_stack = &usageValue;
|
||||
|
||||
report->AddMainItem(globalState, localState, converter.main_data, mouseCollection);
|
||||
|
||||
#ifdef TRACE_HYPERV_HID
|
||||
report->PrintToStream();
|
||||
#endif
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2026 John Davis. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef _HYPERV_HID_DEVICE_H_
|
||||
#define _HYPERV_HID_DEVICE_H_
|
||||
|
||||
#include <new>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <condition_variable.h>
|
||||
#include <device_manager.h>
|
||||
#include <kernel.h>
|
||||
#include <lock.h>
|
||||
#include <fs/devfs.h>
|
||||
|
||||
#include <hyperv.h>
|
||||
|
||||
#include "HIDParser.h"
|
||||
#include "HyperVHIDProtocol.h"
|
||||
|
||||
class ProtocolHandler;
|
||||
|
||||
class HIDDevice {
|
||||
public:
|
||||
HIDDevice(hyperv_device_interface* hyperv,
|
||||
hyperv_device hyperv_cookie);
|
||||
~HIDDevice();
|
||||
|
||||
status_t InitCheck() const { return fStatus; }
|
||||
bool IsOpen() const { return fOpenCount > 0; }
|
||||
status_t Open(ProtocolHandler* handler, uint32 flags);
|
||||
status_t Close(ProtocolHandler* handler);
|
||||
int32 OpenCount() const { return fOpenCount; }
|
||||
|
||||
void Removed();
|
||||
bool IsRemoved() const { return fRemoved; }
|
||||
|
||||
status_t MaybeScheduleTransfer(HIDReport* report);
|
||||
status_t SendReport(HIDReport* report);
|
||||
HIDParser& Parser() { return fParser; }
|
||||
ProtocolHandler* ProtocolHandlerAt(uint32 index) const;
|
||||
|
||||
private:
|
||||
static void _CallbackHandler(void* data);
|
||||
void _Callback();
|
||||
void _HandleInputReport(hv_hid_msg_input_report* reportMessage);
|
||||
status_t _Connect();
|
||||
status_t _AddWheelHIDItem(HIDReport* report);
|
||||
|
||||
private:
|
||||
status_t fStatus;
|
||||
|
||||
int32 fOpenCount;
|
||||
bool fRemoved;
|
||||
|
||||
HIDParser fParser;
|
||||
uint32 fProtocolHandlerCount;
|
||||
ProtocolHandler* fProtocolHandlerList;
|
||||
|
||||
hyperv_device_interface* fHyperV;
|
||||
hyperv_device fHyperVCookie;
|
||||
hv_hid_msg_protocol_response fProtocolResponse;
|
||||
hv_hid_msg_initial_dev_info* fDeviceInfo;
|
||||
uint32 fDeviceInfoLength;
|
||||
|
||||
ConditionVariable fProtocolRespEvent;
|
||||
ConditionVariable fDeviceInfoEvent;
|
||||
void* fPacket;
|
||||
uint16 fLastX;
|
||||
uint16 fLastY;
|
||||
};
|
||||
|
||||
|
||||
#endif // _HYPERV_HID_DEVICE_H_
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2026 John Davis. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef _HYPERV_HID_PROTOCOL_H_
|
||||
#define _HYPERV_HID_PROTOCOL_H_
|
||||
|
||||
|
||||
#include <hyperv_spec.h>
|
||||
|
||||
|
||||
#define HV_HID_RING_SIZE 0x8000
|
||||
#define HV_HID_RX_PKT_BUFFER_SIZE 256
|
||||
#define HV_HID_REQUEST_TRANS_ID 0xCAFECAFE
|
||||
#define HV_HID_TIMEOUT_US HV_MS_TO_US(5000)
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8 length;
|
||||
uint8 type;
|
||||
uint16 version;
|
||||
uint8 country_code;
|
||||
uint8 num_descriptors;
|
||||
uint8 hid_descriptor_type;
|
||||
uint16 hid_descriptor_length;
|
||||
} _PACKED hv_hid_descriptor;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32 length;
|
||||
uint16 vendor_id;
|
||||
uint16 product_id;
|
||||
uint16 version;
|
||||
uint16 reserved[11];
|
||||
} _PACKED hv_hid_dev_info;
|
||||
|
||||
|
||||
// Hyper-V HID version
|
||||
#define MAKE_HID_VERSION(major, minor) ((((major) << 16) & 0xFFFF0000) | ((minor) & 0x0000FFFF))
|
||||
#define GET_HID_VERSION_MAJOR(version) (((version) >> 16) & 0xFFFF)
|
||||
#define GET_HID_VERSION_MINOR(version) ((version) & 0xFFFF)
|
||||
|
||||
// HID protocol version 2.0 used in Server 2008 and newer.
|
||||
#define HV_HID_VERSION_V2_0 MAKE_HID_VERSION(2, 0)
|
||||
|
||||
|
||||
// HID pipe message types
|
||||
enum {
|
||||
HV_HID_PIPE_MSGTYPE_INVALID = 0,
|
||||
HV_HID_PIPE_MSGTYPE_DATA = 1
|
||||
};
|
||||
|
||||
|
||||
// HID message types
|
||||
enum {
|
||||
HV_HID_MSGTYPE_PROTOCOL_REQUEST = 0,
|
||||
HV_HID_MSGTYPE_PROTOCOL_RESPONSE = 1,
|
||||
HV_HID_MSGTYPE_INITIAL_DEV_INFO = 2,
|
||||
HV_HID_MSGTYPE_INITIAL_DEV_INFO_ACK = 3,
|
||||
HV_HID_MSGTYPE_INPUT_REPORT = 4,
|
||||
HV_HID_MSGTYPE_INPUT_MAX
|
||||
};
|
||||
|
||||
|
||||
// HID message header
|
||||
typedef struct {
|
||||
uint32 type;
|
||||
uint32 length;
|
||||
} _PACKED hv_hid_msg_header;
|
||||
|
||||
|
||||
// HID protocol request message sent to Hyper-V
|
||||
typedef struct {
|
||||
hv_hid_msg_header header;
|
||||
|
||||
uint32 version;
|
||||
} _PACKED hv_hid_msg_protocol_request;
|
||||
|
||||
|
||||
// HID protocol response message received from Hyper-V
|
||||
typedef struct {
|
||||
hv_hid_msg_header header;
|
||||
|
||||
uint32 version;
|
||||
uint8 result;
|
||||
uint8 reserved[3];
|
||||
} _PACKED hv_hid_msg_protocol_response;
|
||||
|
||||
|
||||
// HID initial device info message received from Hyper-V
|
||||
typedef struct {
|
||||
hv_hid_msg_header header;
|
||||
|
||||
hv_hid_dev_info info;
|
||||
hv_hid_descriptor descriptor;
|
||||
uint8 descriptor_data[];
|
||||
} _PACKED hv_hid_msg_initial_dev_info;
|
||||
|
||||
|
||||
// HID initial device info acknowledgement message sent to Hyper-V
|
||||
typedef struct {
|
||||
hv_hid_msg_header header;
|
||||
|
||||
uint8 reserved;
|
||||
} _PACKED hv_hid_msg_initial_dev_info_ack;
|
||||
|
||||
|
||||
// HID input report message received from Hyper-V
|
||||
typedef struct {
|
||||
hv_hid_msg_header header;
|
||||
|
||||
uint8 data[];
|
||||
} _PACKED hv_hid_msg_input_report;
|
||||
|
||||
|
||||
// HID pipe message header
|
||||
typedef struct {
|
||||
uint32 type;
|
||||
uint32 length;
|
||||
} _PACKED hv_hid_pipe_msg_header;
|
||||
|
||||
|
||||
// HID pipe message sent to Hyper-V
|
||||
typedef struct {
|
||||
hv_hid_pipe_msg_header pipe_header;
|
||||
|
||||
union {
|
||||
hv_hid_msg_header header;
|
||||
hv_hid_msg_protocol_request protocol_req;
|
||||
hv_hid_msg_protocol_response protocol_resp;
|
||||
hv_hid_msg_initial_dev_info_ack dev_info_ack;
|
||||
};
|
||||
} _PACKED hv_hid_pipe_out_msg;
|
||||
|
||||
|
||||
// HID pipe message received from Hyper-V
|
||||
typedef struct {
|
||||
hv_hid_pipe_msg_header pipe_header;
|
||||
|
||||
union {
|
||||
hv_hid_msg_header header;
|
||||
hv_hid_msg_protocol_request protocol_req;
|
||||
hv_hid_msg_protocol_response protocol_resp;
|
||||
hv_hid_msg_initial_dev_info dev_info;
|
||||
hv_hid_msg_initial_dev_info_ack dev_info_ack;
|
||||
hv_hid_msg_input_report input_report;
|
||||
};
|
||||
} _PACKED hv_hid_pipe_in_msg;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8 buttons;
|
||||
uint16 x;
|
||||
uint16 y;
|
||||
int8 wheel;
|
||||
} _PACKED hyperv_hid_input_report;
|
||||
|
||||
|
||||
#endif // _HYPERV_HID_PROTOCOL_H_
|
||||
@@ -0,0 +1,27 @@
|
||||
SubDir HAIKU_TOP src add-ons kernel drivers input hyperv_hid ;
|
||||
|
||||
SubDirSysHdrs $(HAIKU_TOP) headers os drivers ;
|
||||
SubDirSysHdrs $(HAIKU_TOP) src add-ons kernel drivers input hid_shared ;
|
||||
UsePrivateHeaders [ FDirName kernel util ] input drivers device hyperv ;
|
||||
UsePrivateKernelHeaders ;
|
||||
|
||||
SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src add-ons kernel drivers input hid_shared ] ;
|
||||
|
||||
KernelAddon hyperv_hid :
|
||||
DeviceList.cpp
|
||||
Driver.cpp
|
||||
HIDDevice.cpp
|
||||
|
||||
HIDCollection.cpp
|
||||
HIDParser.cpp
|
||||
HIDReport.cpp
|
||||
HIDReportItem.cpp
|
||||
HIDWriter.cpp
|
||||
|
||||
ProtocolHandler.cpp
|
||||
|
||||
JoystickProtocolHandler.cpp
|
||||
KeyboardProtocolHandler.cpp
|
||||
MouseProtocolHandler.cpp
|
||||
TabletProtocolHandler.cpp
|
||||
;
|
||||
Reference in New Issue
Block a user