From 372868565e5400ec7253619b5eaa26fbc4df2e5c Mon Sep 17 00:00:00 2001 From: Tau Date: Thu, 8 Nov 2018 14:44:34 -0500 Subject: [PATCH] board/io3.c: Add SEGA "Type 3" JVS I/O emulator --- board/io3.c | 606 ++++++++++++++++++++++++++++++++++++++++++++++ board/io3.h | 35 +++ board/meson.build | 2 + 3 files changed, 643 insertions(+) create mode 100644 board/io3.c create mode 100644 board/io3.h diff --git a/board/io3.c b/board/io3.c new file mode 100644 index 0000000..af6a577 --- /dev/null +++ b/board/io3.c @@ -0,0 +1,606 @@ +/* + Sega "Type 3" JVS I/O emulator + + Credits: + + Protocol docs: + https://github.com/TheOnlyJoey/openjvs/wiki/ (a/o October 2018) + + Capability dumps: + https://wiki.arcadeotaku.com/w/JVS#Sega_837-14572 (a/o October 2018) +*/ + +#include + +#include +#include +#include + +#include "board/io3.h" + +#include "jvs/jvs-bus.h" +#include "jvs/jvs-cmd.h" +#include "jvs/jvs-util.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static void io3_transact( + struct jvs_node *node, + const void *bytes, + size_t nbytes, + struct iobuf *resp); + +static bool io3_sense(struct jvs_node *node); + +static HRESULT io3_cmd( + void *ctx, + struct const_iobuf *req, + struct iobuf *resp); + +static HRESULT io3_cmd_read_id( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_cmd_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_jvs_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_comm_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_features( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_read_switches( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_read_coin( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_read_analogs( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_write_gpio( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_reset(struct io3 *io3, struct const_iobuf *buf); + +static HRESULT io3_cmd_assign_addr( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static const uint8_t io3_ident[] = + "SEGA CORPORATION;I/O BD JVS;837-14572;Ver1.00;2005/10"; + +static uint8_t io3_features[] = { + /* Feature : 0x01 : Players and switches + Param1 : 2 : Number of players + Param2 : 14 : Number of switches per player + Param3 : 0 : N/A */ + + 0x01, 2, 14, 0, + + /* Feature : 0x02 : Coin slots + Param1 : 2 : Number of coin slots + Param2 : 0 : N/A + Param3 : 0 : N/A */ + + 0x02, 2, 0, 0, + + /* Feature : 0x03 : Analog inputs + Param1 : 8 : Number of ADC channels + Param2 : 10 : Effective bits of resolution per ADC + Param3 : 0 : N/A */ + + 0x03, 8, 10, 0, + + /* Feature : 0x12 : GPIO outputs + Param1 : 3 : Number of ports (8 bits per port) + Param2 : 0 : N/A + Param3 : 0 : N/A */ + + 0x12, 3, 0, 0, + + /* Feature : 0x00 : End of capabilities */ + + 0x00, +}; + +void io3_init( + struct io3 *io3, + struct jvs_node *next, + const struct io3_ops *ops, + void *ops_ctx) +{ + assert(io3 != NULL); + assert(ops != NULL); + + io3->jvs.next = next; + io3->jvs.transact = io3_transact; + io3->jvs.sense = io3_sense; + io3->addr = 0xFF; + io3->ops = ops; + io3->ops_ctx = ops_ctx; +} + +static void io3_transact( + struct jvs_node *node, + const void *bytes, + size_t nbytes, + struct iobuf *resp) +{ + struct io3 *io3; + + assert(node != NULL); + assert(bytes != NULL); + assert(resp != NULL); + + io3 = CONTAINING_RECORD(node, struct io3, jvs); + + jvs_crack_request(bytes, nbytes, resp, io3->addr, io3_cmd, io3); +} + +static bool io3_sense(struct jvs_node *node) +{ + struct io3 *io3; + + assert(node != NULL); + + io3 = CONTAINING_RECORD(node, struct io3, jvs); + + return io3->addr == 0xFF; +} + +static HRESULT io3_cmd( + void *ctx, + struct const_iobuf *req, + struct iobuf *resp) +{ + struct io3 *io3; + + io3 = ctx; + + switch (req->bytes[req->pos]) { + case JVS_CMD_READ_ID: + return io3_cmd_read_id(io3, req, resp); + + case JVS_CMD_GET_CMD_VERSION: + return io3_cmd_get_cmd_version(io3, req, resp); + + case JVS_CMD_GET_JVS_VERSION: + return io3_cmd_get_jvs_version(io3, req, resp); + + case JVS_CMD_GET_COMM_VERSION: + return io3_cmd_get_comm_version(io3, req, resp); + + case JVS_CMD_GET_FEATURES: + return io3_cmd_get_features(io3, req, resp); + + case JVS_CMD_READ_SWITCHES: + return io3_cmd_read_switches(io3, req, resp); + + case JVS_CMD_READ_COIN: + return io3_cmd_read_coin(io3, req, resp); + + case JVS_CMD_READ_ANALOGS: + return io3_cmd_read_analogs(io3, req, resp); + + case JVS_CMD_WRITE_GPIO: + return io3_cmd_write_gpio(io3, req, resp); + + case JVS_CMD_RESET: + return io3_cmd_reset(io3, req); + + case JVS_CMD_ASSIGN_ADDR: + return io3_cmd_assign_addr(io3, req, resp); + + default: + dprintf("JVS I/O: Node %02x: Unhandled command byte %02x\n", + io3->addr, + req->bytes[req->pos]); + + return E_NOTIMPL; + } +} + +static HRESULT io3_cmd_read_id( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Read ID\n"); + + /* Write report byte */ + + hr = iobuf_write_8(resp_buf, 0x01); + + if (FAILED(hr)) { + return hr; + } + + /* Write the identification string. The NUL terminator at the end of this C + string is also sent, and it naturally terminates the response chunk. */ + + return iobuf_write(resp_buf, io3_ident, sizeof(io3_ident)); +} + +static HRESULT io3_cmd_get_cmd_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + uint8_t resp[2]; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get command format version\n"); + resp[0] = 0x01; /* Report byte */ + resp[1] = 0x13; /* Command format version BCD */ + + return iobuf_write(resp_buf, resp, sizeof(resp)); +} + +static HRESULT io3_cmd_get_jvs_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + uint8_t resp[2]; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get JVS version\n"); + resp[0] = 0x01; /* Report byte */ + resp[1] = 0x20; /* JVS version BCD */ + + return iobuf_write(resp_buf, resp, sizeof(resp)); +} + +static HRESULT io3_cmd_get_comm_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + uint8_t resp[2]; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get communication version\n"); + resp[0] = 0x01; /* Report byte */ + resp[1] = 0x10; /* "Communication version" BCD */ + + return iobuf_write(resp_buf, resp, sizeof(resp)); +} + +static HRESULT io3_cmd_get_features( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get features\n"); + + hr = iobuf_write_8(resp_buf, 0x01); /* Write report byte */ + + if (FAILED(hr)) { + return hr; + } + + return iobuf_write(resp_buf, io3_features, sizeof(io3_features)); +} + +static HRESULT io3_cmd_read_switches( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_read_switches req; + struct io3_switch_state state; + HRESULT hr; + + /* Read req */ + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + +#if 0 + dprintf("JVS I/O: Read switches, np=%i, bpp=%i\n", + req.num_players, + req.bytes_per_player); +#endif + + if (req.num_players != 2 || req.bytes_per_player != 2) { + dprintf("JVS I/O: Invalid read size\n"); + hr = iobuf_write_8(resp_buf, 0x02); + + return S_OK; + } + + /* Build response */ + + hr = iobuf_write_8(resp_buf, 0x01); /* Report byte */ + + if (FAILED(hr)) { + return hr; + } + + memset(&state, 0, sizeof(state)); + + if (io3->ops != NULL) { + io3->ops->read_switches(io3->ops_ctx, &state); + } + + hr = iobuf_write_8(resp_buf, state.system); /* Test, Tilt lines */ + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_be16(resp_buf, state.p1); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_be16(resp_buf, state.p2); + + if (FAILED(hr)) { + return hr; + } + + return hr; +} + +static HRESULT io3_cmd_read_coin( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_read_coin req; + uint16_t ncoins; + uint8_t i; + HRESULT hr; + + /* Read req */ + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + //dprintf("JVS I/O: Read coin, nslots=%i\n", req.nslots); + + /* Write report byte */ + + hr = iobuf_write_8(resp_buf, 0x01); + + if (FAILED(hr)) { + return hr; + } + + /* Write slot detail */ + + for (i = 0 ; i < req.nslots ; i++) { + if (io3->ops->consume_coins != NULL) { + ncoins = io3->ops->consume_coins(io3->ops_ctx, i); + } else { + ncoins = 0; + } + + hr = iobuf_write_be16(resp_buf, ncoins); + + if (FAILED(hr)) { + return hr; + } + } + + return hr; +} + +static HRESULT io3_cmd_read_analogs( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_read_analogs req; + uint16_t state; + uint8_t i; + HRESULT hr; + + /* Read req */ + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + //dprintf("JVS I/O: Read analogs, nanalogs=%i\n", req.nanalogs); + + /* Write report byte */ + + hr = iobuf_write_8(resp_buf, 0x01); + + if (FAILED(hr)) { + return hr; + } + + /* Write analogs */ + + for (i = 0 ; i < req.nanalogs ; i++) { + if (io3->ops->read_analog != NULL) { + state = io3->ops->read_analog(io3->ops_ctx, i); + } else { + state = 0; + } + + hr = iobuf_write_le16(resp_buf, state); + + if (FAILED(hr)) { + return hr; + } + } + + return hr; + +} + +static HRESULT io3_cmd_write_gpio( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t cmd; + uint8_t nbytes; + uint8_t bytes[3]; + HRESULT hr; + + /* Read request header */ + + hr = iobuf_read_8(req_buf, &cmd); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_8(req_buf, &nbytes); + + if (FAILED(hr)) { + return hr; + } + + if (nbytes != 3) { + dprintf("JVS I/O: Invalid GPIO write size\n"); + + return iobuf_write_8(resp_buf, 0x02); + } + + /* Read payload */ + + hr = iobuf_read(req_buf, bytes, sizeof(bytes)); + + if (FAILED(hr)) { + return hr; + } + + if (io3->ops->write_gpio != NULL) { + io3->ops->write_gpio( + io3->ops_ctx, + bytes[0] | (bytes[1] << 8) | (bytes[2] << 16)); + } + + /* Write report byte */ + + return iobuf_write_8(resp_buf, 0x01); +} + +static HRESULT io3_cmd_reset(struct io3 *io3, struct const_iobuf *req_buf) +{ + struct jvs_req_reset req; + HRESULT hr; + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Reset (param %02x)\n", req.unknown); + io3->addr = 0xFF; + + if (io3->ops->reset != NULL) { + io3->ops->reset(io3->ops_ctx); + } + + /* No ack for this since it really is addressed to everybody */ + + return S_OK; +} + +static HRESULT io3_cmd_assign_addr( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_assign_addr req; + bool sense; + HRESULT hr; + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + sense = jvs_node_sense(io3->jvs.next); + dprintf("JVS I/O: Assign addr %02x sense %i\n", req.addr, sense); + + if (sense) { + /* That address is for somebody else */ + return S_OK; + } + + io3->addr = req.addr; + + return iobuf_write_8(resp_buf, 0x01); +} diff --git a/board/io3.h b/board/io3.h new file mode 100644 index 0000000..c34c2db --- /dev/null +++ b/board/io3.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "jvs/jvs-bus.h" + +struct io3_switch_state { + /* Note: this struct is host-endian. The IO3 emulator handles the conversion + to protocol-endian. */ + + uint8_t system; + uint16_t p1; + uint16_t p2; +}; + +struct io3_ops { + void (*reset)(void *ctx); + void (*write_gpio)(void *ctx, uint32_t state); + void (*read_switches)(void *ctx, struct io3_switch_state *out); + uint16_t (*read_analog)(void *ctx, uint8_t analog_no); + uint16_t (*consume_coins)(void *ctx, uint8_t slot_no); +}; + +struct io3 { + struct jvs_node jvs; + uint8_t addr; + const struct io3_ops *ops; + void *ops_ctx; +}; + +void io3_init( + struct io3 *io3, + struct jvs_node *next, + const struct io3_ops *ops, + void *ops_ctx); diff --git a/board/meson.build b/board/meson.build index 64e4be4..6bde450 100644 --- a/board/meson.build +++ b/board/meson.build @@ -7,5 +7,7 @@ board_lib = static_library( capnhook.get_variable('hook_dep'), ], sources : [ + 'io3.c', + 'io3.h', ], )