277 lines
6.6 KiB
C
277 lines
6.6 KiB
C
#include <initguid.h>
|
|
#include <shlobj.h>
|
|
#include <windows.h>
|
|
|
|
#include <hidsdi.h>
|
|
#include <setupapi.h>
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "mm/mm.h"
|
|
|
|
#include "util/cmdline.h"
|
|
#include "util/defs.h"
|
|
#include "util/log.h"
|
|
#include "util/mem.h"
|
|
|
|
DEFINE_GUID(
|
|
hid_guid,
|
|
0x4D1E55B2L,
|
|
0xF16F,
|
|
0x11CF,
|
|
0x88,
|
|
0xCB,
|
|
0x00,
|
|
0x11,
|
|
0x11,
|
|
0x00,
|
|
0x00,
|
|
0x30);
|
|
|
|
static HANDLE mm_fd;
|
|
|
|
static struct mm_input *mm_in_bufs;
|
|
static int mm_in_buf_count;
|
|
static int mm_in_buf_pos;
|
|
static DWORD mm_in_xferred;
|
|
static OVERLAPPED mm_in_ovl;
|
|
|
|
static struct mm_output mm_out_buf;
|
|
static OVERLAPPED mm_out_ovl;
|
|
static bool mm_out_pending;
|
|
|
|
static HANDLE mm_open_device(void);
|
|
static HANDLE
|
|
mm_try_device(HDEVINFO dev_info, SP_DEVICE_INTERFACE_DATA *iface_data);
|
|
|
|
static HANDLE mm_open_device(void)
|
|
{
|
|
HDEVINFO dev_info;
|
|
SP_DEVICE_INTERFACE_DATA iface_data;
|
|
HANDLE cur_fd;
|
|
DWORD i;
|
|
BOOL ok;
|
|
|
|
dev_info = SetupDiGetClassDevsW(
|
|
&hid_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
|
|
if (dev_info == NULL) {
|
|
log_fatal("SetupDiGetClassDevs failed");
|
|
}
|
|
|
|
for (i = 0, cur_fd = NULL; cur_fd == NULL; i++) {
|
|
iface_data.cbSize = sizeof(iface_data);
|
|
|
|
ok = SetupDiEnumDeviceInterfaces(
|
|
dev_info, NULL, &hid_guid, i, &iface_data);
|
|
|
|
if (!ok) {
|
|
if (GetLastError() == ERROR_NO_MORE_ITEMS) {
|
|
break;
|
|
} else {
|
|
log_fatal(
|
|
"SetupDiEnumDeviceInterfaces failed: %#x",
|
|
(int) GetLastError());
|
|
}
|
|
}
|
|
|
|
cur_fd = mm_try_device(dev_info, &iface_data);
|
|
}
|
|
|
|
SetupDiDestroyDeviceInfoList(dev_info);
|
|
|
|
return cur_fd;
|
|
}
|
|
|
|
static HANDLE
|
|
mm_try_device(HDEVINFO dev_info, SP_DEVICE_INTERFACE_DATA *iface_data)
|
|
{
|
|
SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail;
|
|
HIDD_ATTRIBUTES hid_attrs;
|
|
HANDLE cur_fd;
|
|
DWORD detail_size;
|
|
BOOL ok;
|
|
|
|
ok = SetupDiGetDeviceInterfaceDetailW(
|
|
dev_info, iface_data, NULL, 0, &detail_size, NULL);
|
|
|
|
if (!ok && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
|
log_fatal(
|
|
"SetupDiGetDeviceInterfaceDetail sizing failed: %#x",
|
|
(int) GetLastError());
|
|
}
|
|
|
|
detail = xmalloc(detail_size);
|
|
detail->cbSize = sizeof(*detail);
|
|
|
|
ok = SetupDiGetDeviceInterfaceDetailW(
|
|
dev_info, iface_data, detail, detail_size, NULL, NULL);
|
|
|
|
if (!ok) {
|
|
log_fatal(
|
|
"SetupDiGetDeviceInterfaceDetail failed: %#x",
|
|
(int) GetLastError());
|
|
}
|
|
|
|
cur_fd = CreateFileW(
|
|
detail->DevicePath,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_OVERLAPPED,
|
|
NULL);
|
|
|
|
free(detail);
|
|
|
|
if (cur_fd == INVALID_HANDLE_VALUE) {
|
|
goto open_fail;
|
|
}
|
|
|
|
ok = HidD_GetAttributes(cur_fd, &hid_attrs);
|
|
|
|
if (!ok) {
|
|
log_fatal("HidD_GetAttributes failed");
|
|
}
|
|
|
|
if (hid_attrs.VendorID != MM_VENDOR_ID ||
|
|
hid_attrs.ProductID != MM_PRODUCT_ID) {
|
|
goto id_fail;
|
|
}
|
|
|
|
return cur_fd;
|
|
|
|
id_fail:
|
|
CloseHandle(cur_fd);
|
|
|
|
open_fail:
|
|
return NULL;
|
|
}
|
|
|
|
bool mm_init(uint32_t in_buf_count)
|
|
{
|
|
mm_in_buf_pos = 0;
|
|
mm_in_buf_count = in_buf_count;
|
|
mm_in_bufs = xcalloc(sizeof(*mm_in_bufs) * in_buf_count);
|
|
|
|
log_info("Searching for MiniMaid ...");
|
|
|
|
mm_fd = mm_open_device();
|
|
|
|
if (mm_fd == NULL) {
|
|
log_warning("MiniMaid not found");
|
|
|
|
return false;
|
|
}
|
|
|
|
if (!HidD_SetNumInputBuffers(mm_fd, mm_in_buf_count)) {
|
|
log_fatal("HidD_SetNumInputBuffers failed");
|
|
}
|
|
|
|
log_info("MiniMaid connected");
|
|
|
|
memset(&mm_in_ovl, 0, sizeof(mm_in_ovl));
|
|
mm_in_ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
memset(&mm_out_ovl, 0, sizeof(mm_out_ovl));
|
|
mm_out_ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
/* Start up buffer chain */
|
|
|
|
mm_in_xferred = sizeof(struct mm_input);
|
|
|
|
do {
|
|
mm_in_buf_pos = (mm_in_buf_pos + 1) % mm_in_buf_count;
|
|
} while (ReadFile(
|
|
mm_fd,
|
|
&mm_in_bufs[mm_in_buf_pos],
|
|
sizeof(*mm_in_bufs),
|
|
NULL,
|
|
&mm_in_ovl));
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
log_fatal("USB IN failed: %#x", (int) GetLastError());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void mm_update(const struct mm_output *out, struct mm_input *in)
|
|
{
|
|
DWORD xferred;
|
|
|
|
/* Lights go first; we defer the input read as much as possible in order
|
|
to return the freshest possible pad state */
|
|
|
|
if (mm_out_pending) {
|
|
/* Only try to send lights once. If the OUT transfer is still in
|
|
flight then we'll retire the IO on the next poll cycle. */
|
|
|
|
if (GetOverlappedResult(mm_fd, &mm_out_ovl, &xferred, FALSE)) {
|
|
ResetEvent(mm_out_ovl.hEvent);
|
|
|
|
mm_out_pending = false;
|
|
} else {
|
|
if (GetLastError() != ERROR_IO_INCOMPLETE) {
|
|
log_fatal(
|
|
"GetOverlappedResult[OUT] failed: %#x",
|
|
(int) GetLastError());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Kick off an async write if one isn't already in progress. We try to
|
|
maintain a continuous transfer of OUT reports (even if no lights have
|
|
changed) in order to keep the bus utilisation consistent and hence
|
|
minimise timing jitter. */
|
|
|
|
if (!mm_out_pending) {
|
|
memcpy(&mm_out_buf, out, sizeof(*out));
|
|
|
|
if (!WriteFile(
|
|
mm_fd, &mm_out_buf, sizeof(mm_out_buf), NULL, &mm_out_ovl)) {
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
log_fatal("USB OUT failed: %#x", (int) GetLastError());
|
|
}
|
|
}
|
|
|
|
mm_out_pending = true;
|
|
}
|
|
|
|
/* Last TRUE here means that we block until an IN report is available. */
|
|
|
|
if (!GetOverlappedResult(mm_fd, &mm_in_ovl, &mm_in_xferred, TRUE)) {
|
|
log_fatal("GetOverlappedResult[IN] failed: %#x", (int) GetLastError());
|
|
}
|
|
|
|
/* Pump whatever backlog of reports is available until we block */
|
|
|
|
do {
|
|
mm_in_buf_pos = (mm_in_buf_pos + 1) % mm_in_buf_count;
|
|
} while (ReadFile(
|
|
mm_fd,
|
|
&mm_in_bufs[mm_in_buf_pos],
|
|
sizeof(*mm_in_bufs),
|
|
NULL,
|
|
&mm_in_ovl));
|
|
|
|
/* (did we actually block or was there an IO error?) */
|
|
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
log_fatal("USB IN failed: %#x", (int) GetLastError());
|
|
}
|
|
|
|
memcpy(in, &mm_in_bufs[(mm_in_buf_pos + 1) % mm_in_buf_count], sizeof(*in));
|
|
}
|
|
|
|
void mm_fini(void)
|
|
{
|
|
CloseHandle(mm_fd);
|
|
CloseHandle(mm_out_ovl.hEvent);
|
|
CloseHandle(mm_in_ovl.hEvent);
|
|
|
|
log_info("Closed MiniMaid");
|
|
}
|