forked from Hay1tsme/segatools
board/io4.c: Add initial emulator for USB-mode IO-4
This commit is contained in:
parent
13c59b13b8
commit
6c5673dd24
317
board/io4.c
Normal file
317
board/io4.c
Normal file
@ -0,0 +1,317 @@
|
||||
#include <windows.h>
|
||||
#include <hidclass.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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));
|
||||
}
|
25
board/io4.h
Normal file
25
board/io4.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user