From 6c5673dd240a61a87c11d3444014d048a0008be1 Mon Sep 17 00:00:00 2001 From: Tau Date: Fri, 30 Aug 2019 18:49:11 -0400 Subject: [PATCH] board/io4.c: Add initial emulator for USB-mode IO-4 --- board/io4.c | 317 ++++++++++++++++++++++++++++++++++++++++++++++ board/io4.h | 25 ++++ board/meson.build | 2 + 3 files changed, 344 insertions(+) create mode 100644 board/io4.c create mode 100644 board/io4.h diff --git a/board/io4.c b/board/io4.c new file mode 100644 index 0000000..79d7085 --- /dev/null +++ b/board/io4.c @@ -0,0 +1,317 @@ +#include +#include + +#include +#include +#include +#include + +#include "board/guid.h" +#include "board/io4.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "util/async.h" +#include "util/dprintf.h" + +#pragma pack(push, 1) + +enum { + IO4_CMD_SET_COMM_TIMEOUT = 0x01, + IO4_CMD_SET_SAMPLING_COUNT = 0x02, + IO4_CMD_CLEAR_BOARD_STATUS = 0x03, + IO4_CMD_SET_GENERAL_OUTPUT = 0x04, + IO4_CMD_SET_PWM_OUTPUT = 0x05, + IO4_CMD_UPDATE_FIRMWARE = 0x85, +}; + +struct io4_report_in { + uint8_t report_id; + uint16_t adcs[8]; + uint16_t spinners[4]; + uint16_t chutes[2]; + uint16_t buttons[2]; + uint8_t system_status; + uint8_t usb_status; + uint8_t unknown[29]; +}; + +static_assert(sizeof(struct io4_report_in) == 0x40, "IO4 IN report size"); + +struct io4_report_out { + uint8_t report_id; + uint8_t cmd; + uint8_t payload[62]; +}; + +static_assert(sizeof(struct io4_report_out) == 0x40, "IO4 OUT report size"); + +#pragma pack(pop) + + +static HRESULT io4_handle_irp(struct irp *irp); +static HRESULT io4_handle_open(struct irp *irp); +static HRESULT io4_handle_close(struct irp *irp); +static HRESULT io4_handle_read(struct irp *irp); +static HRESULT io4_handle_write(struct irp *irp); +static HRESULT io4_handle_ioctl(struct irp *irp); + +static HRESULT io4_ioctl_get_manufacturer_string(struct irp *irp); +static HRESULT io4_ioctl_get_product_string(struct irp *irp); + +static HRESULT io4_async_poll(void *ctx, struct irp *irp); + +/* Device node path must contain substring "vid_0ca3" (case-insensitive). */ +static const wchar_t io4_path[] = L"$io4\\vid_0ca3"; + +static const wchar_t io4_manf[] = L"SEGA"; +static const wchar_t io4_prod[] = + /* "Product" (N.B. numbers are in hex) */ + + L"I/O CONTROL BD;" /* Board type */ + L"15257;" /* Board number */ + L"01;" /* "Mode" (prob. USB vs JVS?) */ + L"90;" /* Firmware revision */ + L"1831;" /* Firmware checksum */ + L"6679A;" /* "Custom chip no" */ + L"00;" /* "Config" */ + + /* "Function" (N.B. all values are in hex) */ + + L"GOUT=14_" /* General-purpose output */ + L"ADIN=8,E_" /* ADC inputs */ + L"ROTIN=4_" /* Rotary inputs */ + L"COININ=2_" /* Coin inputs */ + L"SWIN=2,E_" /* Switch inputs */ + L"UQ1=41,6" /* "Unique function 1" */ + ; + +static HANDLE io4_fd; +static struct async io4_async; +static uint8_t io4_system_status; +static const struct io4_ops *io4_ops; +static void *io4_ops_ctx; + +HRESULT io4_hook_init(const struct io4_ops *ops, void *ctx) +{ + assert(ops != NULL); + + async_init(&io4_async, NULL); + + io4_fd = iohook_open_dummy_fd(); + io4_ops = ops; + io4_ops_ctx = ctx; + io4_system_status = 0x02; /* idk */ + iohook_push_handler(io4_handle_irp); + + return setupapi_add_phantom_dev(&hid_guid, io4_path); +} + +static HRESULT io4_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != io4_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return io4_handle_open(irp); + case IRP_OP_CLOSE: return io4_handle_close(irp); + case IRP_OP_READ: return io4_handle_read(irp); + case IRP_OP_WRITE: return io4_handle_write(irp); + case IRP_OP_IOCTL: return io4_handle_ioctl(irp); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT io4_handle_open(struct irp *irp) +{ + if (wcscmp(irp->open_filename, io4_path) != 0) { + return iohook_invoke_next(irp); + } + + dprintf("USB I/O: Device opened\n"); + irp->fd = io4_fd; + + return S_OK; +} + +static HRESULT io4_handle_close(struct irp *irp) +{ + dprintf("USB I/O: Device closed\n"); + + return S_OK; +} + +static HRESULT io4_handle_read(struct irp *irp) +{ + /* The amdaemon USBIO driver will continuously poll the IO until the IO + call returns an async operation in progress. We have to return and then + signal the OVERLAPPED event object "a little bit later" in order to avoid + an infinite loop. */ + + return async_submit(&io4_async, irp, io4_async_poll); +} + +static HRESULT io4_handle_write(struct irp *irp) +{ + struct io4_report_out out; + HRESULT hr; + + hr = iobuf_read(&irp->write, &out, sizeof(out)); + + if (FAILED(hr)) { + return hr; + } + + if (out.report_id != 0x10) { + dprintf("USB I/O: OUT Report ID is incorrect"); + + return E_FAIL; + } + + switch (out.cmd) { + case IO4_CMD_SET_COMM_TIMEOUT: + dprintf("USB I/O: Set comm timeout\n"); + + return S_OK; + + case IO4_CMD_SET_SAMPLING_COUNT: + dprintf("USB I/O: Set sampling count\n"); + + return S_OK; + + case IO4_CMD_CLEAR_BOARD_STATUS: + dprintf("USB I/O: Clear board status\n"); + io4_system_status = 0x00; + + return S_OK; + + case IO4_CMD_SET_GENERAL_OUTPUT: + dprintf("USB I/O: GPIO Out\n"); + + return S_OK; + + case IO4_CMD_SET_PWM_OUTPUT: + dprintf("USB I/O: PWM Out\n"); + + return S_OK; + + case IO4_CMD_UPDATE_FIRMWARE: + dprintf("USB I/O: Update firmware..?\n"); + + return E_FAIL; + + default: + dprintf("USB I/O: Unknown command %02x\n", out.cmd); + + return E_FAIL; + } +} + +static HRESULT io4_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case IOCTL_HID_GET_MANUFACTURER_STRING: + return io4_ioctl_get_manufacturer_string(irp); + + case IOCTL_HID_GET_PRODUCT_STRING: + return io4_ioctl_get_product_string(irp); + + case IOCTL_HID_GET_INPUT_REPORT: + dprintf("USB I/O: Control IN (untested!!)\n"); + + return io4_handle_read(irp); + + case IOCTL_HID_SET_OUTPUT_REPORT: + dprintf("USB I/O: Control OUT (untested!!)\n"); + + return io4_handle_write(irp); + + default: + dprintf("USB I/O: Unknown ioctl %#08x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT io4_ioctl_get_manufacturer_string(struct irp *irp) +{ + dprintf("USB I/O: Get manufacturer string\n"); + + if (irp->read.nbytes < sizeof(io4_manf)) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + memcpy(irp->read.bytes, io4_manf, sizeof(io4_manf)); + irp->read.pos = sizeof(io4_manf); + + return S_OK; +} + +static HRESULT io4_ioctl_get_product_string(struct irp *irp) +{ + dprintf("USB I/O: Get product string\n"); + + if (irp->read.nbytes < sizeof(io4_prod)) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + memcpy(irp->read.bytes, io4_prod, sizeof(io4_prod)); + irp->read.pos = sizeof(io4_prod); + + return S_OK; +} + +static HRESULT io4_async_poll(void *ctx, struct irp *irp) +{ + struct io4_report_in in; + struct io4_state state; + HRESULT hr; + size_t i; + + /* Delay long enough for the instigating thread in amdaemon to be satisfied + that all queued-up reports have been drained. */ + + Sleep(1); + + /* Call into ops to poll the underlying inputs */ + + memset(&state, 0, sizeof(state)); + hr = io4_ops->poll(io4_ops_ctx, &state); + + if (FAILED(hr)) { + return hr; + } + + /* Construct IN report. Values are all little-endian, unlike JVS. */ + + memset(&in, 0, sizeof(in)); + in.report_id = 0x01; + in.system_status = io4_system_status; + + for (i = 0 ; i < 8 ; i++) { + in.adcs[i] = state.adcs[i]; + } + + for (i = 0 ; i < 4 ; i++) { + in.spinners[i] = state.spinners[i]; + } + + for (i = 0 ; i < 2 ; i++) { + in.chutes[i] = state.chutes[i]; + } + + for (i = 0 ; i < 2 ; i++) { + in.buttons[i] = state.buttons[i]; + } + + return iobuf_write(&irp->read, &in, sizeof(in)); +} diff --git a/board/io4.h b/board/io4.h new file mode 100644 index 0000000..1ce7bcf --- /dev/null +++ b/board/io4.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +enum { + /* System buttons in button[0] */ + + IO4_BUTTON_TEST = 1 << 9, + IO4_BUTTON_SERVICE = 1 << 6, +}; + +struct io4_state { + uint16_t adcs[8]; + uint16_t spinners[4]; + uint16_t chutes[2]; + uint16_t buttons[2]; +}; + +struct io4_ops { + HRESULT (*poll)(void *ctx, struct io4_state *state); +}; + +HRESULT io4_hook_init(const struct io4_ops *ops, void *ctx); diff --git a/board/meson.build b/board/meson.build index 3dcdf9f..fe079c6 100644 --- a/board/meson.build +++ b/board/meson.build @@ -13,6 +13,8 @@ board_lib = static_library( 'guid.h', 'io3.c', 'io3.h', + 'io4.c', + 'io4.h', 'sg-cmd.c', 'sg-cmd.h', 'sg-frame.c',