diff --git a/Module.mk b/Module.mk index 235160d..2c708e5 100644 --- a/Module.mk +++ b/Module.mk @@ -104,13 +104,18 @@ include src/main/d3d9exhook/Module.mk include src/main/ddrhook-util/Module.mk include src/main/ddrhook1/Module.mk include src/main/ddrhook2/Module.mk +include src/main/ddrio-p3io/Module.mk include src/main/ddrio-mm/Module.mk include src/main/ddrio-smx/Module.mk +include src/main/ddriotest/Module.mk include src/main/ddrio/Module.mk include src/main/dinput/Module.mk include src/main/eamio-icca/Module.mk include src/main/eamio/Module.mk include src/main/eamiotest/Module.mk +include src/main/extio/Module.mk +include src/main/extiodrv/Module.mk +include src/main/extiotest/Module.mk include src/main/ezusb-emu/Module.mk include src/main/ezusb-iidx-16seg-emu/Module.mk include src/main/ezusb-iidx-emu/Module.mk @@ -166,7 +171,9 @@ include src/main/launcher/Module.mk include src/main/mempatch-hook/Module.mk include src/main/mm/Module.mk include src/main/p3io/Module.mk +include src/main/p3iodrv/Module.mk include src/main/p3ioemu/Module.mk +include src/main/p3io-ddr-tool/Module.mk include src/main/p4iodrv/Module.mk include src/main/p4ioemu/Module.mk include src/main/popnhook-util/Module.mk @@ -207,6 +214,7 @@ $(zipdir)/: $(zipdir)/tools.zip: \ build/bin/indep-32/aciotest.exe \ + build/bin/indep-32/ddriotest.exe \ build/bin/indep-32/eamiotest.exe \ build/bin/indep-32/ezusb-iidx-fpga-flash.exe \ build/bin/indep-32/ezusb-iidx-sram-flash.exe \ @@ -228,6 +236,7 @@ $(zipdir)/tools.zip: \ $(zipdir)/tools-x64.zip: \ build/bin/indep-64/aciotest.exe \ + build/bin/indep-64/ddriotest.exe \ build/bin/indep-64/eamiotest.exe \ build/bin/indep-64/iidxiotest.exe \ build/bin/indep-64/iidx-bio2-exit-hook.dll \ @@ -618,18 +627,6 @@ $(zipdir)/sdvx-hwio-x64.zip: \ $(V)echo ... $@ $(V)zip -j $@ $^ -$(zipdir)/ddr-hwio-x86.zip: \ - build/bin/indep-32/vigem-ddrio.exe \ - | $(zipdir)/ - $(V)echo ... $@ - $(V)zip -j $@ $^ - -$(zipdir)/ddr-hwio-x64.zip: \ - build/bin/indep-64/vigem-ddrio.exe \ - | $(zipdir)/ - $(V)echo ... $@ - $(V)zip -j $@ $^ - $(zipdir)/ddr-11.zip: \ build/bin/indep-32/inject.exe \ build/bin/avs2_803-32/ddrhook1.dll \ @@ -706,8 +703,6 @@ $(zipdir)/ddr-14-to-16.zip: \ build/bin/avs2_1508-32/unicorntail.dll \ build/bin/indep-32/config.exe \ build/bin/indep-32/ddrio.dll \ - build/bin/indep-32/ddrio-mm.dll \ - build/bin/indep-32/ddrio-smx.dll \ build/bin/indep-32/eamio.dll \ build/bin/indep-32/geninput.dll \ dist/ddr/config.bat \ @@ -724,8 +719,6 @@ $(zipdir)/ddr-16-x64.zip: \ build/bin/avs2_1603-64/unicorntail.dll \ build/bin/indep-64/config.exe \ build/bin/indep-64/ddrio.dll \ - build/bin/indep-64/ddrio-mm.dll \ - build/bin/indep-64/ddrio-smx.dll \ build/bin/indep-64/eamio.dll \ build/bin/indep-64/geninput.dll \ dist/ddr/config.bat \ @@ -734,6 +727,28 @@ $(zipdir)/ddr-16-x64.zip: \ $(V)echo ... $@ $(V)zip -j $@ $^ +$(zipdir)/ddr-hwio-x86.zip: \ + build/bin/indep-32/ddrio-p3io.dll \ + build/bin/indep-32/ddrio-mm.dll \ + build/bin/indep-32/ddrio-smx.dll \ + build/bin/indep-32/extiotest.exe \ + build/bin/indep-32/p3io-ddr-tool.exe \ + build/bin/indep-32/vigem-ddrio.exe \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/ddr-hwio-x64.zip: \ + build/bin/indep-64/ddrio-p3io.dll \ + build/bin/indep-64/ddrio-mm.dll \ + build/bin/indep-64/ddrio-smx.dll \ + build/bin/indep-64/extiotest.exe \ + build/bin/indep-64/p3io-ddr-tool.exe \ + build/bin/indep-64/vigem-ddrio.exe \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + $(zipdir)/bst.zip: \ build/bin/avs2_1603-64/bsthook.dll \ build/bin/avs2_1603-64/launcher.exe \ diff --git a/README.md b/README.md index 77b357f..62a54d8 100644 --- a/README.md +++ b/README.md @@ -100,9 +100,13 @@ The following games are supported with their corresponding hook-libraries. * [iidx-bio2-exit-hook](doc/tools/iidx-bio2-exit-hook.md): For IIDX with BIO2 IO * [iidx-ezusb2-exit-hook](doc/tools/iidx-ezusb-exit-hook.md): For IIDX with ezusb FX2 IO * Bemanitools API testing: Tools for testing bemanitools API implementations + * [ddriotest](doc/tools/ddriotest.md): For [ddrio API](doc/api.md#io-boards) * [eamiotest](doc/tools/eamiotest.md): For [eamio API](doc/api.md#eamuse-readers) * [iidxiotest](doc/tools/iidxiotest.md): For [iidxio API](doc/api.md#io-boards) * [jbiotest](doc/tools/jbiotest.md): For [jbio API](doc/api.md#io-boards) +* DDR IO testing: Tools for testing hardware of a real DDR cabinet + * [p3io-ddr-tool](doc/tools/p3io-ddr-tool.md) + * [extiotest](doc/tools/extiotest.md) * [aciotest](doc/tools/aciotest.md): Command line tool to quickly test ACIO devices * config: UI input/output configuration tool when using the default bemanitools API (geninput) * ir-beat-patch-9/10: Patch the IR beat phase on IIDX 9 and 10 diff --git a/doc/api.md b/doc/api.md index 63a5048..3c85111 100644 --- a/doc/api.md +++ b/doc/api.md @@ -22,8 +22,9 @@ The following implementations are already shipped with BT5. * bstio.dll (default): Keyboard, joystick and mouse input * Dance Dance Revolution * ddrio.dll (default): Keyboard, joystick and mouse input + * [ddrio-p3io.dll](ddrhook/ddrio-p3io.md): DDR P3IO (Dragon PCB) + EXTIO hardware * ddrio-mm.dll: Minimaid hardware - * ddrio-smx.dll: StepManiaX platforms + * [ddrio-smx.dll](ddrhook/ddrio-smx.md): StepManiaX platforms * Beatmania IIDX * iidxio.dll (default): Keyboard, joystick and mouse input * [iidxio-bio2.dll](iidxhook/iidxio-bio2.md): BIO2 driver diff --git a/doc/ddrhook/ddrio-p3io.md b/doc/ddrhook/ddrio-p3io.md new file mode 100644 index 0000000..59be0b2 --- /dev/null +++ b/doc/ddrhook/ddrio-p3io.md @@ -0,0 +1,27 @@ +# ddrio API implementation with DDR P3IO (Dragon) and EXTIO + +This implementation of BT5's ddrio API allows you to use the native DDR P3IO +of a "Dragon PCB" plus the EXTIO with anything the ddrio API supports. + +This is not required to run the actual games supporting the hardware natively. +However, there are various 3rd party applications using the ddrio API where you +might benefit from using actual SD cabinet hardware, e.g. +[vigem-ddrio](../vigem/README.md). + +## Setup + +For hooks, but likely applicable to 3rd party applications (consolidate their +manuals). + +* Driver: You must have the P3IO driver intalled on your system + * Driver from [bemanitools-supplements](https://github.com/djhackersdev/bemanitools-supplement/blob/master/ddr/p3io/README.md) +* Have `ddrio-p3io.dll` in the same folder as your `ddrhookX.dll` +* Rename `ddrio-p3io.dll` to `ddrio.dll` +* Ensure that your `gamestart.bat` actually injects the appropriate ddrhook dll, for example: +```bat +inject ddrhook1.dll ddr.exe ...* +``` +or +```bat +launcher -K ddrhook2.dll arkmdxp3.dll ...* +``` \ No newline at end of file diff --git a/doc/dev/journal/2023-05-28-ddr-p3io-driver-work.md b/doc/dev/journal/2023-05-28-ddr-p3io-driver-work.md new file mode 100644 index 0000000..98a67d8 --- /dev/null +++ b/doc/dev/journal/2023-05-28-ddr-p3io-driver-work.md @@ -0,0 +1,194 @@ +# DDR p3io driver work, various notes + +Date: 2023-05-28 +Author: icex2 + +Notes about my work on writing a DDR P3io driver. + +## Python IO board hardware breakout interface + +PCB breakout interfaces/connectors on the side of the boards. + +### P2IO DDR + +Descriptions assume cabinet hardware of an upgraded DDR "black SD cabinet" + +* `LINE OUT 1`: Primary audio out to amplifier +* `LINE OUT 2`: N.C. +* `RGB`: Video out to monitor, supports 15khz/31khz based on hardware switch for video freq/mode selection +* `COM1`: Virtual com port that connects to the EXTIO +* `COM2`: Virtual com port that connects to a pair of ICCA card readers +* `LAN`: Network connection +* `PWR`: 100V power in +* `ANALOG`: N.C. +* `PORT 1`: Lights output for cabinet lights, e.g. menu button lights, top header lights +* `PORT 2`: N.C. +* `DIPSW` + * `1`: ??? + * `2`: On = force all sensor polling mode and allow running the game without an EXTIO + * `3`: ??? + * `4`: On = force 15 khz monitor output +* `PLUG 1`: Black dongle +* `PLUG 2`: White dongle + +### P3IO GF/DM + +Descriptions assume usage of a P3IO from a GF/DM in a "chimera style PCB build" on an upgraded +DDR "black SD cabinet" + +* `PWR`: 100V power in +* `12V-OUT`: +12V out for external devices +* `PORT 1`: Lights output for cabinet lights, e.g. menu button lights, top header lights +* `PORT 2`: N.C. +* `COM1`: To EXTIO +* `COM2`: To card readers +* `RGB`: Video out to monitor, supports 15khz/31khz based on hardware switch for video freq/mode selection +* `LINE OUT 1`: Primary audio out to amplifier +* `LINE OUT 2`: N.C. +* `DIPSW` + * `1`: + * `2`: + * `3`: + * `4`: On = force 15 khz monitor output +* `PLUG 1`: Roundplug black dongle +* `PLUG 2`: Roundplug white dongle + +### P3IO DDR(X) + +Descriptions assume cabinet hardware of an upgraded DDR "black SD cabinet" + +* `PWR`: 100V power in +* `USB`: USB memory card readers on cabinet +* `PORT 1`: Lights output for cabinet lights, e.g. menu button lights, top header lights +* `COM3-4`: Virtual COM ports + * COM3 (VCOM1): Pins 1,3,5 on connector -> To card readers + * COM4 (VCOM0): Pins 2,4,6 on connector -> N.C. +* `PLUG`: Breakout to security round plugs (black and white dongles) +* `COM1`: To EXTIO + * Pinout (pins left to right) + * 1: TXD1 + * 2: RXD1 + * 3: N.C. + * 4: N.C. + * 5: GND +* `COM2`: N/A (light spires on black HD cabinet) + * Pinout (pins left to right) + * 1: TXD2 + * 2: RXD2 + * 3: GND +* `RGB`: Video out to monitor, supports 15khz/31khz based on hardware switch for video freq/mode selection +* `LINE OUT1`: Primary audio out to amplifier +* `LINE OUT2`: N.C. +* `LAN`: Network +* `DIP SW` + * `1`: + * `2`: + * `3`: + * `4`: On = force 15 khz monitor output + +## Python IO boards and differences + +### P3IO DDR(X) + +* Connects to USB and actually enumerates as a USB device and not a virtual COM port +* COM ports 1-4 on breakout of the PCB are being passed through as actual COM ports to the operating system + +### P3IO GF/DM + +* Connects to USB and actually enumerates as a USB device and not a virtual COM port +* COM ports 1-2 on breakout of the PCB are just virtual COM ports +* These do not show up as COM ports on the operating system +* The game drives the COM ports through the main P3IO protocol with additional P3IO commands + to open, read, write and close these virtual COM ports + +### P2IO DDR + +* Connects to USB and actually enumerates as a USB device and not a virtual COM port +* COM ports 1-2 on breakout of the PCB are just virtual COM ports +* These do not show up as COM ports on the operating system +* The game drives the COM ports through the main P3IO protocol with additional P3IO commands + to open, read, write and close these virtual COM ports + + +## Pinout card reader P1 -> P2 mini din8 male to mini din8 male + +Port 1 2 and 3: + +* Pin 3: TX +* Pin 4: GND +* Pin 5 : RX + +Pin 3 and 5 need to be reversed inbetween readers +They are all the same, so your cable needs to bridge them over. +A standard male to male mini din 8 cable does not do that. + +### Pinout card reader stock cable s-sub9 to round pin9 + +```text +dsub-9 female -> mini-din8 male +2 (TXD) -> 3 +3 (RXD) -> 5 +5 (GND) -> 4 +``` + +## ADE board and com port assignments + +Default or incorrectly configured? + +* `COM1` -> on mainboard +* `COM2` -> COM2 on P3IO breakout +* `COM3` -> ??? +* `COM4` -> COM1 on P3IO breakout + +## P3IO command init sequence on DDR 18 + +From the ddrio-python23 library + +```text +.data:1002D5D0 g_init_pakets db 0AAh, 2, 0, 1, 29h dup(0); field_0.field_0 +.data:1002D5D0 ; DATA XREF: initialize_and_send_pakets+5↑o +.data:1002D5D0 db 0AAh, 2, 0, 2Fh, 29h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 3, 1, 27h, 1, 28h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2 dup(2), 31h, 29h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2, 3, 1, 29h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 3, 4, 27h, 29h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2Bh, 5, 25h, 10h, 28h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2Bh, 6, 25h, 10h, 28h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2Bh, 7, 25h, 10h, 28h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2Bh, 8, 25h, 10h, 28h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2Bh, 9, 25h, 10h, 28h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2Bh, 0Ah, 25h, 29h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2Bh, 0Bh, 25h, 29h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2Bh, 0Ch, 25h, 29h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2Bh, 0Dh, 25h, 29h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2Bh, 0Eh, 25h, 29h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 3, 0Fh, 5, 29h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 3, 0, 2Bh, 1, 28h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 3, 1, 29h, 5, 28h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 3, 2, 5, 30h, 28h dup(0); field_0.field_0 +.data:1002D5D0 db 0AAh, 2 dup(3), 27h, 29h dup(0); field_0.field_0 +`` + +```text +// 01: get version <- this is part of a "flush" of old data? +// 2F: set mode +// 27: get cab type or dispsw +// 31: get coinstock +// 1: get version +// 27: get cab type or dipsw +// 25: read plug +// 25: read plug +// 25: read plug +// 25: read plug +// 25: read plug +// 25: read plug +// 25: read plug +// 25: read plug +// 25: read plug +// 25: read plug +// 5: set watchdog +// 2b: unknown +// 29: get video freq +// 5: set watchdog +// 27: get cab type or dipsw +``` \ No newline at end of file diff --git a/doc/tools/README.md b/doc/tools/README.md index 8168f44..a8cd655 100644 --- a/doc/tools/README.md +++ b/doc/tools/README.md @@ -6,6 +6,7 @@ BT5's supported games, but provides additional features for users and developers Various command line tools for quick and easy testing of BT5 API implementations witohut having to run any target games. +* [ddriotest](ddriotest.md): `ddrio` API * [eamiotest](eamiotest.md): `eamio` API * [iidxiotest](iidxiotest.md): `iidxio` API * [jbiotest](jbiotest.md): `jbio` API @@ -15,6 +16,10 @@ to run any target games. * [aciotest](aciotest.md): Command line tool for quick and easy testing of ACIO devices without having to run a game. +### P3IO DDR testing tool +* [p3io-ddr-tool](p3io-ddr-tool.md): Extensive command line tool to test and debug a real P3IO DDR + (Dragon PCB) IO board + EXTIO + ### Ezusb * [ezusb-iidx-fpga-flash](ezusb-iidx-fpga-flash.md): Tool for flashing the FPGA on ezusb 1 boards. Required if you want to run games without native ezusb support using BT5's iidxio API. diff --git a/doc/tools/ddriotest.md b/doc/tools/ddriotest.md new file mode 100644 index 0000000..3c620c7 --- /dev/null +++ b/doc/tools/ddriotest.md @@ -0,0 +1,3 @@ +Testing tool for development of ddrio libraries used for emulating the main io +hardware on bemanitools for DanceDanceRevoluion. Just place ddriotest.exe and your +custom ddrio.dll in the same folder and run ddriotest.exe. \ No newline at end of file diff --git a/doc/tools/p3io-ddr-tool.md b/doc/tools/p3io-ddr-tool.md new file mode 100644 index 0000000..eb6054f --- /dev/null +++ b/doc/tools/p3io-ddr-tool.md @@ -0,0 +1,2 @@ +Test your real DDR P3IO (Dragon) + EXTIO hardware connected to your machine +using this tool. Just execute it and follow the usage instructions. \ No newline at end of file diff --git a/src/main/ddrio-p3io/Module.mk b/src/main/ddrio-p3io/Module.mk new file mode 100644 index 0000000..43e0fd0 --- /dev/null +++ b/src/main/ddrio-p3io/Module.mk @@ -0,0 +1,16 @@ +dlls += ddrio-p3io + +ldflags_ddrio-p3io:= \ + -lsetupapi \ + +libs_ddrio-p3io := \ + cconfig \ + extio \ + extiodrv \ + p3io \ + p3iodrv \ + util \ + +src_ddrio-p3io := \ + config.c \ + ddrio.c \ diff --git a/src/main/ddrio-p3io/config.c b/src/main/ddrio-p3io/config.c new file mode 100644 index 0000000..c45254e --- /dev/null +++ b/src/main/ddrio-p3io/config.c @@ -0,0 +1,35 @@ +#include "cconfig/cconfig-util.h" + +#include "util/log.h" + +#include "config.h" + +#define DDRIO_P3IO_CONFIG_EXTIO_PORT_KEY "ddrio.p3io.extio_port" + +#define DDRIO_P3IO_CONFIG_EXTIO_PORT_VALUE "COM1" + +void ddrio_p3io_config_init(struct cconfig *config) +{ + cconfig_util_set_str( + config, + DDRIO_P3IO_CONFIG_EXTIO_PORT_KEY, + DDRIO_P3IO_CONFIG_EXTIO_PORT_VALUE, + "COM port the EXTIO is connected to, default COM1"); +} + +void ddrio_p3io_config_get( + struct ddrio_p3io_config *config_ddrio_p3io, struct cconfig *config) +{ + if (!cconfig_util_get_str( + config, + DDRIO_P3IO_CONFIG_EXTIO_PORT_KEY, + config_ddrio_p3io->extio_port, + sizeof(config_ddrio_p3io->extio_port) - 1, + DDRIO_P3IO_CONFIG_EXTIO_PORT_VALUE)) { + log_warning( + "Invalid value for key '%s' specified, fallback " + "to default '%s'", + DDRIO_P3IO_CONFIG_EXTIO_PORT_KEY, + DDRIO_P3IO_CONFIG_EXTIO_PORT_VALUE); + } +} diff --git a/src/main/ddrio-p3io/config.h b/src/main/ddrio-p3io/config.h new file mode 100644 index 0000000..784782f --- /dev/null +++ b/src/main/ddrio-p3io/config.h @@ -0,0 +1,17 @@ +#ifndef DDRIO_P3IO_CONFIG_H +#define DDRIO_P3IO_CONFIG_H + +#include + +#include "cconfig/cconfig.h" + +struct ddrio_p3io_config { + char extio_port[12]; +}; + +void ddrio_p3io_config_init(struct cconfig *config); + +void ddrio_p3io_config_get( + struct ddrio_p3io_config *config_ddrio_p3io, struct cconfig *config); + +#endif \ No newline at end of file diff --git a/src/main/ddrio-p3io/ddrio-p3io.def b/src/main/ddrio-p3io/ddrio-p3io.def new file mode 100644 index 0000000..f6e9fbb --- /dev/null +++ b/src/main/ddrio-p3io/ddrio-p3io.def @@ -0,0 +1,11 @@ +LIBRARY ddrio-p3io + +EXPORTS + ddr_io_set_loggers + ddr_io_fini + ddr_io_init + ddr_io_read_pad + ddr_io_set_lights_extio + ddr_io_set_lights_p3io + ddr_io_set_lights_hdxs_panel + ddr_io_set_lights_hdxs_rgb diff --git a/src/main/ddrio-p3io/ddrio.c b/src/main/ddrio-p3io/ddrio.c new file mode 100644 index 0000000..a38ce09 --- /dev/null +++ b/src/main/ddrio-p3io/ddrio.c @@ -0,0 +1,427 @@ +#define LOG_MODULE "ddrio-p3io" + +#include + +#include +#include +#include + +#include "bemanitools/ddrio.h" + +#include "cconfig/cconfig-main.h" + +#include "extiodrv/device.h" +#include "extiodrv/extio.h" + +#include "p3iodrv/ddr.h" +#include "p3iodrv/device.h" + +#include "util/log.h" +#include "util/thread.h" + +#include "config.h" + +static struct ddrio_p3io_config _ddr_io_p3io_config; +static HANDLE _ddr_io_p3io_handle; +static HANDLE _ddr_io_extio_handle; + +static HRESULT _ddr_io_p3io_scan_and_open(HANDLE *handle) +{ + HRESULT hr; + char path[MAX_PATH]; + + log_info("Scanning for p3io..."); + + hr = p3iodrv_device_scan(path); + + if (FAILED(hr)) { + log_warning("Cannot find a connected p3io: %lX", hr); + return hr; + } + + log_info("Opening p3io: %s", path); + + hr = p3iodrv_device_open(path, handle); + + if (FAILED(hr)) { + log_warning("Opening p3io failed: %lX", hr); + return hr; + } + + return hr; +} + +static HRESULT _ddr_io_p3io_print_version(HANDLE handle) +{ + HRESULT hr; + char version[P3IODRV_VERSION_MAX_LEN]; + + memset(version, 0, P3IODRV_VERSION_MAX_LEN); + + hr = p3iodrv_device_read_version(handle, version); + + if (FAILED(hr)) { + log_warning("Getting p3io version failed: %lX", hr); + return hr; + } + + log_info("P3IO version: %s", version); + + return hr; +} + +static HRESULT _ddr_io_ddr_p3io_print_version(HANDLE handle) +{ + HRESULT hr; + char str[4]; + uint32_t major; + uint32_t minor; + uint32_t patch; + + hr = p3iodrv_ddr_get_version(handle, str, &major, &minor, &patch); + + if (FAILED(hr)) { + log_warning("Getting version failed: %lX", hr); + return hr; + } + + log_info("DDR version: %s %d.%d.%d", str, major, minor, patch); + + return hr; +} + +static HRESULT _ddr_io_ddr_p3io_print_cabinet_type(HANDLE handle) +{ + HRESULT hr; + enum p3io_cab_type type; + + hr = p3iodrv_ddr_get_cab_type(handle, &type); + + if (FAILED(hr)) { + log_warning("Getting cab type failed: %lX", hr); + return hr; + } + + log_info("Cabinet type: %d", type); + + return hr; +} + +static HRESULT _ddr_io_ddr_p3io_print_dip_switches(HANDLE handle) +{ + HRESULT hr; + uint8_t dip_sw; + + hr = p3iodrv_ddr_get_dipsw(handle, &dip_sw); + + if (FAILED(hr)) { + log_warning("Getting dip switches failed"); + return hr; + } + + log_info("Dip switches: 0x%X", dip_sw); + + return hr; +} + +static HRESULT _ddr_io_ddr_p3io_print_video_freq(HANDLE handle) +{ + HRESULT hr; + enum p3io_video_freq video_freq; + + hr = p3iodrv_ddr_get_video_freq(handle, &video_freq); + + if (FAILED(hr)) { + log_warning("Getting video freq failed"); + return hr; + } + + log_info("Video freq: 0x%X", video_freq); + + return hr; +} + +static HRESULT _ddr_io_ddr_p3io_print_debug_info(HANDLE handle) +{ + return _ddr_io_p3io_print_version(handle) && + _ddr_io_ddr_p3io_print_version(handle) && + _ddr_io_ddr_p3io_print_cabinet_type(handle) && + _ddr_io_ddr_p3io_print_dip_switches(handle) && + _ddr_io_ddr_p3io_print_video_freq(handle); +} + +static HRESULT _ddr_io_p3io_init(HANDLE *handle) +{ + HRESULT hr; + + log_info("Initializing DDR P3IO..."); + + hr = _ddr_io_p3io_scan_and_open(handle); + + if (FAILED(hr)) { + return hr; + } + + hr = p3iodrv_ddr_init(*handle); + + if (FAILED(hr)) { + return hr; + } + + hr = _ddr_io_ddr_p3io_print_debug_info(*handle); + + if (FAILED(hr)) { + return hr; + } + + return hr; +} + +static HRESULT _ddr_io_p3io_close(HANDLE *handle) +{ + HRESULT hr; + + log_info("Closing p3io..."); + + hr = p3iodrv_device_close(handle); + + if (FAILED(hr)) { + log_warning("Closing p3io failed: %lX", hr); + return hr; + } + + return hr; +} + +static HRESULT _ddr_io_extio_init(HANDLE *handle) +{ + log_info("Initializing EXTIO..."); + + return extiodrv_device_open(_ddr_io_p3io_config.extio_port, handle); +} + +static HRESULT _ddr_io_extio_close(HANDLE *handle) +{ + log_info("Closing EXTIO..."); + + return extiodrv_device_close(handle); +} + +static HRESULT _ddr_io_flush_and_reset(HANDLE p3io_handle, HANDLE extio_handle) +{ + HRESULT hr; + struct p3io_ddr_jamma jamma; + struct p3io_ddr_output output; + struct extiodrv_extio_pad_lights pad_lights[2]; + bool neons; + + log_assert(p3io_handle != INVALID_HANDLE_VALUE); + log_assert(extio_handle != INVALID_HANDLE_VALUE); + + log_info("Flushing inputs and outputs"); + + memset(&output, 0, sizeof(output)); + memset(&pad_lights, 0, sizeof(pad_lights)); + neons = false; + + hr = p3iodrv_ddr_get_jamma(p3io_handle, &jamma); + + if (FAILED(hr)) { + log_warning("Reading jamma failed"); + return hr; + } + + hr = p3iodrv_ddr_set_outputs(p3io_handle, &output); + + if (FAILED(hr)) { + log_warning("Settings outputs failed"); + return hr; + } + + hr = extiodrv_extio_transfer( + extio_handle, EXTIODRV_EXTIO_SENSOR_READ_MODE_ALL, pad_lights, neons); + + if (FAILED(hr)) { + log_warning("EXTIO transfer failed"); + return hr; + } + + return hr; +} + +void ddr_io_set_loggers( + log_formatter_t misc, + log_formatter_t info, + log_formatter_t warning, + log_formatter_t fatal) +{ + log_to_external(misc, info, warning, fatal); +} + +static void _ddr_io_config_init(struct ddrio_p3io_config *config_ddrio_p3io) +{ + struct cconfig *config; + + config = cconfig_init(); + + ddrio_p3io_config_init(config); + + if (!cconfig_main_config_init( + config, + "--ddrio-p3io-config", + "ddrio-p3io.conf", + "--help", + "-h", + "ddrio-p3io", + CCONFIG_CMD_USAGE_OUT_STDOUT)) { + cconfig_finit(config); + exit(EXIT_FAILURE); + } + + ddrio_p3io_config_get(config_ddrio_p3io, config); + + cconfig_finit(config); +} + +bool ddr_io_init( + thread_create_t thread_create, + thread_join_t thread_join, + thread_destroy_t thread_destroy) +{ + HRESULT hr; + + _ddr_io_config_init(&_ddr_io_p3io_config); + + hr = _ddr_io_p3io_init(&_ddr_io_p3io_handle); + + if (FAILED(hr)) { + return false; + } + + hr = _ddr_io_extio_init(&_ddr_io_extio_handle); + + if (FAILED(hr)) { + return false; + } + + hr = _ddr_io_flush_and_reset(_ddr_io_p3io_handle, _ddr_io_extio_handle); + + if (FAILED(hr)) { + return false; + } + + return true; +} + +uint32_t ddr_io_read_pad(void) +{ + HRESULT hr; + struct p3io_ddr_jamma jamma; + + hr = p3iodrv_ddr_get_jamma(_ddr_io_p3io_handle, &jamma); + + if (FAILED(hr)) { + log_warning("Reading jamma failed"); + return 0; + } + + return _byteswap_ulong(*((uint32_t *) &jamma)); +} + +void ddr_io_set_lights_extio(uint32_t extio_lights) +{ + HRESULT hr; + struct extiodrv_extio_pad_lights pad_lights[EXTIO_PAD_LIGHT_MAX_PLAYERS]; + bool neons; + + pad_lights[0].up = (extio_lights & (1 << LIGHT_P1_UP)) > 0; + pad_lights[0].down = (extio_lights & (1 << LIGHT_P1_DOWN)) > 0; + pad_lights[0].left = (extio_lights & (1 << LIGHT_P1_LEFT)) > 0; + pad_lights[0].right = (extio_lights & (1 << LIGHT_P1_RIGHT)) > 0; + + pad_lights[1].up = (extio_lights & (1 << LIGHT_P2_UP)) > 0; + pad_lights[1].down = (extio_lights & (1 << LIGHT_P2_DOWN)) > 0; + pad_lights[1].left = (extio_lights & (1 << LIGHT_P2_LEFT)) > 0; + pad_lights[1].right = (extio_lights & (1 << LIGHT_P2_RIGHT)) > 0; + + neons = (extio_lights & (1 << LIGHT_NEONS)) > 0; + + hr = extiodrv_extio_transfer( + _ddr_io_extio_handle, + EXTIODRV_EXTIO_SENSOR_READ_MODE_ALL, + pad_lights, + neons); + + if (FAILED(hr)) { + log_warning("EXTIO transfer failed: %lX", hr); + } +} + +void ddr_io_set_lights_p3io(uint32_t p3io_lights) +{ + HRESULT hr; + struct p3io_ddr_output output; + + output.cabinet.top_p1_lower = + (p3io_lights & (1 << LIGHT_P1_LOWER_LAMP)) > 0 ? 1 : 0; + output.cabinet.top_p1_upper = + (p3io_lights & (1 << LIGHT_P1_UPPER_LAMP)) > 0 ? 1 : 0; + output.cabinet.top_p2_lower = + (p3io_lights & (1 << LIGHT_P2_LOWER_LAMP)) > 0 ? 1 : 0; + output.cabinet.top_p2_upper = + (p3io_lights & (1 << LIGHT_P2_UPPER_LAMP)) > 0 ? 1 : 0; + + output.cabinet.p1_menu_buttons = + (p3io_lights & (1 << LIGHT_P1_MENU)) > 0 ? 1 : 0; + output.cabinet.p2_menu_buttons = + (p3io_lights & (1 << LIGHT_P2_MENU)) > 0 ? 1 : 0; + + hr = p3iodrv_ddr_set_outputs(_ddr_io_p3io_handle, &output); + + if (FAILED(hr)) { + log_warning("Settings outputs failed: %lX", hr); + } +} + +void ddr_io_set_lights_hdxs_panel(uint32_t lights) +{ + // Unused + + (void) lights; +} + +void ddr_io_set_lights_hdxs_rgb(uint8_t idx, uint8_t r, uint8_t g, uint8_t b) +{ + // Unused + + (void) idx; + (void) r; + (void) g; + (void) b; +} + +void ddr_io_fini(void) +{ + HRESULT hr; + + hr = _ddr_io_flush_and_reset(_ddr_io_p3io_handle, _ddr_io_extio_handle); + + if (FAILED(hr)) { + log_warning("Flusing IO failed: %lX", hr); + return; + } + + hr = _ddr_io_p3io_close(&_ddr_io_p3io_handle); + + if (FAILED(hr)) { + log_warning("Closing P3IO failed: %lX", hr); + // continue + } + + hr = _ddr_io_extio_close(&_ddr_io_extio_handle); + + if (FAILED(hr)) { + log_warning("Closing EXTIO failed: %lX", hr); + // continue + } +} diff --git a/src/main/ddriotest/Module.mk b/src/main/ddriotest/Module.mk new file mode 100644 index 0000000..569d973 --- /dev/null +++ b/src/main/ddriotest/Module.mk @@ -0,0 +1,8 @@ +exes += ddriotest \ + +libs_ddriotest := \ + ddrio \ + util \ + +src_ddriotest := \ + main.c \ diff --git a/src/main/ddriotest/main.c b/src/main/ddriotest/main.c new file mode 100644 index 0000000..e03bc0c --- /dev/null +++ b/src/main/ddriotest/main.c @@ -0,0 +1,303 @@ +#include +#include +#include +#include + +#include + +#include "bemanitools/ddrio.h" + +#include "util/log.h" +#include "util/thread.h" + +int main(int argc, char **argv) +{ + enum log_level log_level; + + log_level = LOG_LEVEL_FATAL; + + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "-v")) { + log_level = LOG_LEVEL_WARNING; + } else if (!strcmp(argv[i], "-vv")) { + log_level = LOG_LEVEL_INFO; + } else if (!strcmp(argv[i], "-vvv")) { + log_level = LOG_LEVEL_MISC; + } + } + + log_to_writer(log_writer_stderr, NULL); + log_set_level(log_level); + + ddr_io_set_loggers( + log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + + if (!ddr_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy)) { + fprintf(stderr, "Initializing ddrio failed\n"); + return -1; + } + + fprintf( + stderr, + ">>> Initializing ddrio successful, press enter to continue <<<\n"); + + if (getchar() != '\n') { + return 0; + } + + // inputs + uint32_t pad = 0; + + // outputs + uint32_t extio_lights = 0; + uint32_t p3io_lights = 0; + + bool loop = true; + uint8_t cnt = 0; + + while (loop) { + ddr_io_set_lights_extio(extio_lights); + ddr_io_set_lights_p3io(p3io_lights); + + pad = ddr_io_read_pad(); + + system("cls"); + + printf("Counter: %d\n", cnt); + printf("Player 1 Player 2\n"); + printf("\n"); + printf("Pad\n"); + + printf( + "Up: %d Up: %d\n", + (pad & (1 << DDR_P1_UP)) > 0, + (pad & (1 << DDR_P2_UP)) > 0); + printf( + "Down: %d Down: %d\n", + (pad & (1 << DDR_P1_DOWN)) > 0, + (pad & (1 << DDR_P2_DOWN)) > 0); + printf( + "Left: %d Left: %d\n", + (pad & (1 << DDR_P1_LEFT)) > 0, + (pad & (1 << DDR_P2_LEFT)) > 0); + printf( + "Right: %d Right: %d\n", + (pad & (1 << DDR_P1_RIGHT)) > 0, + (pad & (1 << DDR_P2_RIGHT)) > 0); + printf("\n"); + printf("Menu\n"); + printf( + "Start: %d Start: %d\n", + (pad & (1 << DDR_P1_START)) > 0, + (pad & (1 << DDR_P2_START)) > 0); + printf( + "Up: %d Up: %d\n", + (pad & (1 << DDR_P1_MENU_UP)) > 0, + (pad & (1 << DDR_P2_MENU_UP)) > 0); + printf( + "Down: %d Down: %d\n", + (pad & (1 << DDR_P1_MENU_DOWN)) > 0, + (pad & (1 << DDR_P2_MENU_DOWN)) > 0); + printf( + "Left: %d Left: %d\n", + (pad & (1 << DDR_P1_MENU_LEFT)) > 0, + (pad & (1 << DDR_P2_MENU_LEFT)) > 0); + printf( + "Right: %d Right: %d\n", + (pad & (1 << DDR_P1_MENU_RIGHT)) > 0, + (pad & (1 << DDR_P2_MENU_RIGHT)) > 0); + printf("\n"); + printf("Operator\n"); + printf( + "Test: %d Service: %d Coin: %d\n", + (pad & (1 << DDR_TEST)) > 0, + (pad & (1 << DDR_SERVICE)) > 0, + (pad & (1 << DDR_COIN)) > 0); + + if ((pad & (1 << DDR_P1_UP)) > 0) { + extio_lights |= (1 << LIGHT_P1_UP); + } else { + extio_lights &= ~(1 << LIGHT_P1_UP); + } + + if ((pad & (1 << DDR_P1_DOWN)) > 0) { + extio_lights |= (1 << LIGHT_P1_DOWN); + } else { + extio_lights &= ~(1 << LIGHT_P1_DOWN); + } + + if ((pad & (1 << DDR_P1_LEFT)) > 0) { + extio_lights |= (1 << LIGHT_P1_LEFT); + } else { + extio_lights &= ~(1 << LIGHT_P1_LEFT); + } + + if ((pad & (1 << DDR_P1_RIGHT)) > 0) { + extio_lights |= (1 << LIGHT_P1_RIGHT); + } else { + extio_lights &= ~(1 << LIGHT_P1_RIGHT); + } + + if ((pad & (1 << DDR_P2_UP)) > 0) { + extio_lights |= (1 << LIGHT_P2_UP); + } else { + extio_lights &= ~(1 << LIGHT_P2_UP); + } + + if ((pad & (1 << DDR_P2_DOWN)) > 0) { + extio_lights |= (1 << LIGHT_P2_DOWN); + } else { + extio_lights &= ~(1 << LIGHT_P2_DOWN); + } + + if ((pad & (1 << DDR_P2_LEFT)) > 0) { + extio_lights |= (1 << LIGHT_P2_LEFT); + } else { + extio_lights &= ~(1 << LIGHT_P2_LEFT); + } + + if ((pad & (1 << DDR_P2_RIGHT)) > 0) { + extio_lights |= (1 << LIGHT_P2_RIGHT); + } else { + extio_lights &= ~(1 << LIGHT_P2_RIGHT); + } + + if ((pad & (1 << DDR_P1_START)) > 0 || + (pad & (1 << DDR_P1_MENU_UP)) > 0 || + (pad & (1 << DDR_P1_MENU_DOWN)) > 0 || + (pad & (1 << DDR_P1_MENU_LEFT)) > 0 || + (pad & (1 << DDR_P1_MENU_RIGHT)) > 0) { + p3io_lights |= (1 << LIGHT_P1_MENU); + } else { + p3io_lights &= ~(1 << LIGHT_P1_MENU); + } + + if ((pad & (1 << DDR_P2_START)) > 0 || + (pad & (1 << DDR_P2_MENU_UP)) > 0 || + (pad & (1 << DDR_P2_MENU_DOWN)) > 0 || + (pad & (1 << DDR_P2_MENU_LEFT)) > 0 || + (pad & (1 << DDR_P2_MENU_RIGHT)) > 0) { + p3io_lights |= (1 << LIGHT_P2_MENU); + } else { + p3io_lights &= ~(1 << LIGHT_P2_MENU); + } + + /* avoid CPU banging */ + Sleep(1); + ++cnt; + + /* process menu */ + if ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) != 0) { + system("cls"); + Sleep(5); + printf( + "Menu options:\n" + " 0: Exit menu and continue loop\n" + " 1: Exit\n" + " 2: Set neon state\n" + " 3: Top lamp state\n" + " 4: Set all lights on\n" + " 5: Set all lights off\n" + "Waiting for input: "); + + char c = getchar(); + + switch (c) { + case '1': { + loop = false; + break; + } + + case '2': { + int state; + int n; + + printf("Enter neon state (0/1): "); + + n = scanf("%d", &state); + + if (n > 0) { + extio_lights |= (1 << LIGHT_NEONS); + } else { + extio_lights &= ~(1 << LIGHT_NEONS); + } + + break; + } + + case '3': { + char buf[4]; + int n; + + printf("Enter top lamp state, chain of 0/1s: "); + + n = scanf("%8s", buf); + + if (n > 0) { + if (buf[0] == '1') { + p3io_lights |= (1 << LIGHT_P1_UPPER_LAMP); + } else { + p3io_lights &= ~(1 << LIGHT_P1_UPPER_LAMP); + } + + if (buf[1] == '1') { + p3io_lights |= (1 << LIGHT_P1_LOWER_LAMP); + } else { + p3io_lights &= ~(1 << LIGHT_P1_LOWER_LAMP); + } + + if (buf[2] == '1') { + p3io_lights |= (1 << LIGHT_P2_UPPER_LAMP); + } else { + p3io_lights &= ~(1 << LIGHT_P2_UPPER_LAMP); + } + + if (buf[3] == '1') { + p3io_lights |= (1 << LIGHT_P2_LOWER_LAMP); + } else { + p3io_lights &= ~(1 << LIGHT_P2_LOWER_LAMP); + } + } + + break; + } + + case '4': { + extio_lights |= (1 << LIGHT_NEONS); + + p3io_lights |= (1 << LIGHT_P1_MENU); + p3io_lights |= (1 << LIGHT_P2_MENU); + p3io_lights |= (1 << LIGHT_P1_UPPER_LAMP); + p3io_lights |= (1 << LIGHT_P1_LOWER_LAMP); + p3io_lights |= (1 << LIGHT_P2_UPPER_LAMP); + p3io_lights |= (1 << LIGHT_P2_LOWER_LAMP); + + break; + } + + case '5': { + extio_lights &= ~(1 << LIGHT_NEONS); + + p3io_lights &= ~(1 << LIGHT_P1_MENU); + p3io_lights &= ~(1 << LIGHT_P2_MENU); + p3io_lights &= ~(1 << LIGHT_P1_UPPER_LAMP); + p3io_lights &= ~(1 << LIGHT_P1_LOWER_LAMP); + p3io_lights &= ~(1 << LIGHT_P2_UPPER_LAMP); + p3io_lights &= ~(1 << LIGHT_P2_LOWER_LAMP); + + break; + } + + case '0': + default: + break; + } + } + } + + system("cls"); + + ddr_io_fini(); + + return 0; +} \ No newline at end of file diff --git a/src/main/extio/Module.mk b/src/main/extio/Module.mk new file mode 100644 index 0000000..9845d60 --- /dev/null +++ b/src/main/extio/Module.mk @@ -0,0 +1,4 @@ +libs += extio + +src_extio := \ + cmd.c \ diff --git a/src/main/extio/cmd.c b/src/main/extio/cmd.c new file mode 100644 index 0000000..e392fae --- /dev/null +++ b/src/main/extio/cmd.c @@ -0,0 +1,16 @@ +#include "cmd.h" + +uint8_t extio_cmd_checksum(const struct extio_cmd_write *write) +{ + const uint8_t *data; + uint32_t checksum; + + data = (const uint8_t *) write; + + checksum = 0; + checksum += data[0]; + checksum += data[1]; + checksum += data[2]; + + return (uint8_t) (checksum & 0x7F); +} \ No newline at end of file diff --git a/src/main/extio/cmd.h b/src/main/extio/cmd.h new file mode 100644 index 0000000..e764705 --- /dev/null +++ b/src/main/extio/cmd.h @@ -0,0 +1,49 @@ +#ifndef EXTIO_CMD_H +#define EXTIO_CMD_H + +#include + +#define EXTIO_PAD_LIGHT_MAX_PLAYERS 2 + +enum extio_status { + EXTIO_STATUS_OK = 0x11, +}; + +#pragma pack(push, 1) + +struct extio_cmd_pad_light { + uint8_t unknown : 3; + uint8_t right : 1; + uint8_t left : 1; + uint8_t down : 1; + uint8_t up : 1; + uint8_t unknown_80 : 1; +}; + +struct extio_cmd_write { + struct extio_cmd_pad_light pad_lights[EXTIO_PAD_LIGHT_MAX_PLAYERS]; + + uint8_t unknown3 : 3; + uint8_t sensor_read_mode : 3; + uint8_t neons : 1; + uint8_t unknown4 : 1; + + uint8_t checksum; +}; + +struct extio_cmd_read { + uint8_t status; +}; + +_Static_assert( + sizeof(struct extio_cmd_write) == 4, + "struct extio_cmd_write is the wrong size"); +_Static_assert( + sizeof(struct extio_cmd_read) == 1, + "struct extio_cmd_read is the wrong size"); + +#pragma pack(pop) + +uint8_t extio_cmd_checksum(const struct extio_cmd_write *write); + +#endif \ No newline at end of file diff --git a/src/main/extiodrv/Module.mk b/src/main/extiodrv/Module.mk new file mode 100644 index 0000000..5b4eacf --- /dev/null +++ b/src/main/extiodrv/Module.mk @@ -0,0 +1,5 @@ +libs += extiodrv + +src_extiodrv := \ + device.c \ + extio.c \ diff --git a/src/main/extiodrv/device.c b/src/main/extiodrv/device.c new file mode 100644 index 0000000..ba14fa7 --- /dev/null +++ b/src/main/extiodrv/device.c @@ -0,0 +1,182 @@ +#define LOG_MODULE "extiodrv-device" + +#include "device.h" + +#include "util/log.h" + +HRESULT extiodrv_device_open(const char *port, HANDLE *handle) +{ + HRESULT hr; + COMMTIMEOUTS ct; + DCB dcb; + + log_assert(port); + log_assert(handle); + + log_info("Opening extio on %s", port); + + *handle = CreateFile( + port, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + FILE_FLAG_WRITE_THROUGH | FILE_ATTRIBUTE_NORMAL, + NULL); + + if (*handle == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("Failed to open %s: %lX", port, hr); + + goto early_fail; + } + + if (!SetCommMask(*handle, EV_RXCHAR)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("SetCommMask failed: %lX", hr); + + goto fail; + } + + if (!SetupComm(*handle, 0x4000u, 0x4000u)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("SetupComm failed: %lX", hr); + + goto fail; + } + + if (!PurgeComm( + *handle, + PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("PurgeComm failed: %lX", hr); + + goto fail; + } + + ct.ReadTotalTimeoutConstant = 0; + ct.WriteTotalTimeoutConstant = 0; + ct.ReadIntervalTimeout = -1; + ct.ReadTotalTimeoutMultiplier = 10; + ct.WriteTotalTimeoutMultiplier = 100; + + if (!SetCommTimeouts(*handle, &ct)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("SetCommTimeouts failed: %lX", hr); + + goto fail; + } + + dcb.DCBlength = sizeof(dcb); + + if (!GetCommState(*handle, &dcb)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("GetCommState failed: %lX", hr); + + goto fail; + } + + dcb.BaudRate = 38400; + dcb.fBinary = TRUE; + dcb.fParity = FALSE; + dcb.fDtrControl = DTR_CONTROL_ENABLE; + dcb.fDsrSensitivity = FALSE; + dcb.fOutX = FALSE; + dcb.fInX = FALSE; + dcb.fErrorChar = FALSE; + dcb.fNull = FALSE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + dcb.fAbortOnError = FALSE; + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + dcb.XonChar = 17; + dcb.XoffChar = 19; + dcb.XonLim = 100; + dcb.XoffLim = 100; + + if (!SetCommState(*handle, &dcb)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("SetCommState failed: %lX", hr); + + goto fail; + } + + log_info("[%p] Opened extio device on %s", *handle, port); + + return S_OK; + +fail: + CloseHandle(*handle); + *handle = INVALID_HANDLE_VALUE; + +early_fail: + return hr; +} + +HRESULT extiodrv_device_close(HANDLE *handle) +{ + HRESULT hr; + + log_assert(handle); + + if (CloseHandle(*handle) == FALSE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; + } + + *handle = INVALID_HANDLE_VALUE; + + return S_OK; +} + +HRESULT extiodrv_device_read( + HANDLE handle, void *bytes, size_t nbytes, size_t *read_bytes) +{ + HRESULT hr; + DWORD dw_read_bytes; + + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(bytes); + log_assert(read_bytes); + + if (!ClearCommError(handle, NULL, NULL)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("[%p] ClearCommError failed: %lX", handle, hr); + + return hr; + } + + if (!ReadFile(handle, bytes, nbytes, &dw_read_bytes, NULL)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("[%p] ReadFile failed: %lX", handle, hr); + + return hr; + } + + *read_bytes = dw_read_bytes; + + return S_OK; +} + +HRESULT extiodrv_device_write( + HANDLE handle, const void *bytes, size_t nbytes, size_t *written_bytes) +{ + HRESULT hr; + DWORD dw_written_bytes; + + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(bytes); + log_assert(written_bytes); + + if (!WriteFile(handle, bytes, nbytes, &dw_written_bytes, NULL)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("[%p] WriteFile failed: %lX", handle, hr); + + return hr; + } + + *written_bytes = dw_written_bytes; + + return S_OK; +} \ No newline at end of file diff --git a/src/main/extiodrv/device.h b/src/main/extiodrv/device.h new file mode 100644 index 0000000..4121295 --- /dev/null +++ b/src/main/extiodrv/device.h @@ -0,0 +1,57 @@ +#ifndef EXTIODRV_DEVICE_H +#define EXTIODRV_DEVICE_H + +#include + +#include +#include + +/** + * Open an EXTIO device connected to a com port. + * + * @param port Com port the device is connected to, e.g. "COM1" + * @param handle Pointer to a HANDLE variable to return the handle to when + * succesfully opened + * @return S_OK on success with the handle returned in handle, other HRESULT + * value on error. + */ +HRESULT extiodrv_device_open(const char *port, HANDLE *handle); + +/** + * Close an opened EXTIO device + * + * @param handle Pointer to a handle variable that stores a valid and opened + * EXTIO handle. + * @return S_OK on success and the handle value is set to INVALID_HANDLE_VALUE, + * other HRESULT value on error. + */ +HRESULT extiodrv_device_close(HANDLE *handle); + +/** + * Read data from the EXTIO device. This call blocks until data is available. + * + * @param handle A valid handle to an opened EXTIO device. + * @param bytes Pointer to a buffer to read the data into + * @param nbytes (Maximum) number of bytes to read + * @param read_bytes Pointer to a variable to store the resulting read size to + * @return S_OK on success with read_bytes populated with the number of bytes + * read, other HRESULT value on error. + */ +HRESULT extiodrv_device_read( + HANDLE handle, void *bytes, size_t nbytes, size_t *read_bytes); + +/** + * WRite data to the EXTIO device. This call blocks until the data is written. + * + * @param handle A valid handle to an opened EXTIO device. + * @param bytes Pointer to a buffer with data to write to the device + * @param nbytes (Maximum) number of bytes to write + * @param written_bytes Pointer to a variable to store the resulting write size + * to + * @return S_OK on success with written_bytes populated with the number of bytes + * written, other HRESULT value on error. + */ +HRESULT extiodrv_device_write( + HANDLE handle, const void *bytes, size_t nbytes, size_t *written_bytes); + +#endif \ No newline at end of file diff --git a/src/main/extiodrv/extio.c b/src/main/extiodrv/extio.c new file mode 100644 index 0000000..8c50de2 --- /dev/null +++ b/src/main/extiodrv/extio.c @@ -0,0 +1,110 @@ +#define LOG_MODULE "extiodrv-extio" + +#include "extio.h" + +#include "util/log.h" + +// static uint8_t _extiodrv_extio_sensor_read_mode_map[5] = { +// 1, // all +// 2, // up +// 3, // down +// 4, // left +// 5, // right +// }; + +HRESULT extiodrv_extio_transfer( + HANDLE handle, + enum extiodrv_extio_sensor_read_mode sensor_read_mode, + const struct extiodrv_extio_pad_lights + pad_lights[EXTIO_PAD_LIGHT_MAX_PLAYERS], + bool neons) +{ + HRESULT hr; + struct extio_cmd_write write; + struct extio_cmd_read read; + size_t bytes_written; + size_t bytes_read; + const uint8_t *raw; + + log_assert(handle != INVALID_HANDLE_VALUE); + + memset(&write, 0, sizeof(write)); + + // TODO this doesn't seem to work, yet + // write.sensor_read_mode = + // 0;//_extiodrv_extio_sensor_read_mode_map[sensor_read_mode]; 0 = all 1 = + // mess? 2 = mess? 3 = mess? 4 = mess? 5 = mess? 6 = right sensor only 7 = + // right sensor only 8 = right sensor only + write.sensor_read_mode = 0; + + for (uint8_t i = 0; i < EXTIO_PAD_LIGHT_MAX_PLAYERS; i++) { + write.pad_lights[i].up = pad_lights[i].up ? 1 : 0; + write.pad_lights[i].down = pad_lights[i].down ? 1 : 0; + write.pad_lights[i].left = pad_lights[i].left ? 1 : 0; + write.pad_lights[i].right = pad_lights[i].right ? 1 : 0; + } + + write.neons = neons ? 1 : 0; + + // This MUST be set but only on the p1 packet + // Not setting it or also setting it on the p2 packet results in the EXTIO + // not responding at all + write.pad_lights[0].unknown_80 = 1; + + write.checksum = extio_cmd_checksum(&write); + + raw = (uint8_t *) &write; + + log_misc("Raw write paket: %X %X %X %X", raw[0], raw[1], raw[2], raw[3]); + + hr = extiodrv_device_write(handle, &write, sizeof(write), &bytes_written); + + if (FAILED(hr)) { + log_warning("Writing extio device failed"); + return hr; + } + + if (bytes_written != sizeof(write)) { + log_warning( + "Writing extio device failed, expected bytes %d != actual bytes %d", + (uint32_t) sizeof(write), + (uint32_t) bytes_written); + return HRESULT_FROM_WIN32(ERROR_BAD_LENGTH); + } + + // Read loop to keep reading until a full message is read + // There are various occasions, especially if polling from the host side is + // faster than the device can put the response to the wire. this can result + // in empty reads + while (true) { + hr = extiodrv_device_read(handle, &read, sizeof(read), &bytes_read); + + if (FAILED(hr)) { + log_warning("Reading extio device failed"); + return hr; + } + + if (bytes_read < sizeof(read)) { + log_misc("Empty read, retry read"); + continue; + } + + if (bytes_read != sizeof(read)) { + log_warning( + "Reading extio device failed, expected bytes %d != actual " + "bytes %d", + (uint32_t) sizeof(read), + (uint32_t) bytes_read); + return HRESULT_FROM_WIN32(ERROR_BAD_LENGTH); + } + + break; + } + + if (read.status != EXTIO_STATUS_OK) { + log_warning("Status not ok: 0x%X", read.status); + return HRESULT_FROM_WIN32(ERROR_GEN_FAILURE); + } + + return S_OK; +} \ No newline at end of file diff --git a/src/main/extiodrv/extio.h b/src/main/extiodrv/extio.h new file mode 100644 index 0000000..bc48c82 --- /dev/null +++ b/src/main/extiodrv/extio.h @@ -0,0 +1,45 @@ +#ifndef EXTIODRV_EXTIO_H +#define EXTIODRV_EXTIO_H + +#include + +#include + +#include "extio/cmd.h" + +#include "device.h" + +enum extiodrv_extio_sensor_read_mode { + EXTIODRV_EXTIO_SENSOR_READ_MODE_ALL = 0, + EXTIODRV_EXTIO_SENSOR_READ_MODE_UP = 1, + EXTIODRV_EXTIO_SENSOR_READ_MODE_DOWN = 2, + EXTIODRV_EXTIO_SENSOR_READ_MODE_LEFT = 3, + EXTIODRV_EXTIO_SENSOR_READ_MODE_RIGHT = 4 +}; + +struct extiodrv_extio_pad_lights { + bool up; + bool down; + bool left; + bool right; +}; + +/** + * Execute a data transfer (write + read) to the EXTIO device + * + * @param handle A valid and opened handle to an EXTIO device + * @param sensor_read_mode The sensor read mode to set on the EXTIO device. + * This only needs to be set once that input reads return the configured + * sensor read setting. + * @param pad_lights Pad light ouptut state to set + * @param neons Neon output state to set + * @return S_OK on success, other HRESULT value on error + */ +HRESULT extiodrv_extio_transfer( + HANDLE handle, + enum extiodrv_extio_sensor_read_mode sensor_read_mode, + const struct extiodrv_extio_pad_lights + pad_lights[EXTIO_PAD_LIGHT_MAX_PLAYERS], + bool neons); + +#endif \ No newline at end of file diff --git a/src/main/extiotest/Module.mk b/src/main/extiotest/Module.mk new file mode 100644 index 0000000..73ec1b0 --- /dev/null +++ b/src/main/extiotest/Module.mk @@ -0,0 +1,9 @@ +exes += extiotest \ + +libs_extiotest := \ + extiodrv \ + extio \ + util \ + +src_extiotest := \ + main.c \ diff --git a/src/main/extiotest/main.c b/src/main/extiotest/main.c new file mode 100644 index 0000000..3be152b --- /dev/null +++ b/src/main/extiotest/main.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +#include + +#include "extiodrv/extio.h" + +#include "util/log.h" + +int main(int argc, char **argv) +{ + HRESULT hr; + const char *port; + HANDLE handle; + struct extiodrv_extio_pad_lights pad_lights[EXTIO_PAD_LIGHT_MAX_PLAYERS]; + + if (argc < 2) { + fprintf(stderr, "Basic functional test of EXTIO\n"); + fprintf(stderr, "Usage: %s COM_PORT\n", argv[0]); + fprintf(stderr, " COM_PORT: For example COM1\n"); + } + + log_to_writer(log_writer_stderr, NULL); + log_set_level(LOG_LEVEL_MISC); + + port = argv[1]; + + hr = extiodrv_device_open(port, &handle); + + if (FAILED(hr)) { + fprintf(stderr, "Opening extio at port '%s' failed: %lX\n", port, hr); + return -1; + } + + memset(&pad_lights, 0, sizeof(pad_lights)); + + hr = extiodrv_extio_transfer( + handle, EXTIODRV_EXTIO_SENSOR_READ_MODE_ALL, pad_lights, false); + + if (FAILED(hr)) { + fprintf(stderr, "Extio transfer failed: %lX\n", hr); + return -1; + } + + hr = extiodrv_device_close(&handle); + + if (FAILED(hr)) { + fprintf(stderr, "Closing extio failed: %lX", hr); + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/src/main/p3io-ddr-tool/Module.mk b/src/main/p3io-ddr-tool/Module.mk new file mode 100644 index 0000000..2c76b42 --- /dev/null +++ b/src/main/p3io-ddr-tool/Module.mk @@ -0,0 +1,16 @@ +exes += p3io-ddr-tool \ + +ldflags_p3io-ddr-tool := \ + -lsetupapi \ + +libs_p3io-ddr-tool := \ + extiodrv \ + extio \ + p3iodrv \ + p3io \ + hook \ + util \ + +src_p3io-ddr-tool := \ + main.c \ + mode-test.c \ diff --git a/src/main/p3io-ddr-tool/main.c b/src/main/p3io-ddr-tool/main.c new file mode 100644 index 0000000..939e1e7 --- /dev/null +++ b/src/main/p3io-ddr-tool/main.c @@ -0,0 +1,774 @@ +#define LOG_MODULE "p3io-ddr-tool" + +#include + +#include +#include +#include +#include + +#include "extiodrv/extio.h" + +#include "p3io/ddr.h" +#include "p3iodrv/ddr.h" +#include "p3iodrv/device.h" + +#include "util/log.h" +#include "util/thread.h" + +#include "mode-test.h" + +enum mode { + MODE_INVALID = 0, + MODE_SCAN = 1, + MODE_VERP3IO = 2, + MODE_INIT = 3, + MODE_VERDDR = 4, + MODE_WATCHDOGON = 5, + MODE_WATCHDOGOFF = 6, + MODE_CABTYPE = 7, + MODE_DIPSW = 8, + MODE_VIDEOFREQ = 9, + MODE_TEST = 10, + MODE_LIGHTOFF = 11, + MODE_POLL = 12, + MODE_SENSORES = 13, + MODE_TOTAL_COUNT = 14, +}; + +typedef bool (*mode_proc)(HANDLE handle); + +static bool _mode_invalid(HANDLE handle); +static bool _mode_scan(HANDLE handle); +static bool _mode_verp3io(HANDLE handle); +static bool _mode_init(HANDLE handle); +static bool _mode_verddr(HANDLE handle); +static bool _mode_watchdogon(HANDLE handle); +static bool _mode_watchdogoff(HANDLE handle); +static bool _mode_cabtype(HANDLE handle); +static bool _mode_dipsw(HANDLE handle); +static bool _mode_videofreq(HANDLE handle); +static bool _mode_test(HANDLE handle); +static bool _mode_lightoff(HANDLE handle); +static bool _mode_poll(HANDLE handle); +static bool _mode_sensores(HANDLE handle); + +static mode_proc _mode_procs[MODE_TOTAL_COUNT] = { + _mode_invalid, + _mode_scan, + _mode_verp3io, + _mode_init, + _mode_verddr, + _mode_watchdogon, + _mode_watchdogoff, + _mode_cabtype, + _mode_dipsw, + _mode_videofreq, + _mode_test, + _mode_lightoff, + _mode_poll, + _mode_sensores, +}; + +static enum log_level _log_level = LOG_LEVEL_FATAL; +static bool _extio_enabled = true; +static const char *_p3io_device_path = ""; +static const char *_extio_com_port = "COM1"; +static enum mode _mode = MODE_INVALID; + +static bool _scan_and_open(HANDLE *handle) +{ + HRESULT hr; + char path[MAX_PATH]; + + log_info("Scanning for p3io..."); + + hr = p3iodrv_device_scan(path); + + if (FAILED(hr)) { + log_warning("Cannot find a connected p3io: %lX", hr); + return false; + } + + log_info("Opening p3io: %s", path); + + hr = p3iodrv_device_open(path, handle); + + if (FAILED(hr)) { + log_warning("Opening p3io failed: %lX", hr); + return false; + } + + return true; +} + +static bool _close(HANDLE handle) +{ + HRESULT hr; + + log_info("Closing p3io..."); + + hr = p3iodrv_device_close(handle); + + if (FAILED(hr)) { + log_warning("Closing p3io failed: %lX", hr); + return false; + } + + return true; +} + +static bool _process_cmd_args(int argc, char **argv) +{ + char *mode; + + if (argc < 2) { + fprintf(stderr, "DDR P3IO and EXTIO CLI testing tool\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Usage: [ARG ...] MODE\n"); + fprintf(stderr, "Arguments:\n"); + fprintf( + stderr, + " -v Enable additional logging, log level 'warning'\n"); + fprintf( + stderr, + " -vv Enable additional logging, log level 'info'\n"); + fprintf( + stderr, + " -vvv Enable additional logging, log level 'misc' (very " + "verbose)\n"); + fprintf( + stderr, + " -noextio Run the tool without an EXTIO. This also impacts " + "various functionality, e.g. sensor polling, light outputs\n"); + fprintf( + stderr, + " -p3io Explicit device path pointing to a P3IO device to " + "open. If not provided, the device will be automatically scanned " + "for.\n"); + fprintf( + stderr, + " -extio Explicitly set the COM port the EXTIO is connected " + "to, e.g. COM4. If not provided COM1 is used.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Modes:\n"); + fprintf( + stderr, + " scan: Scan for a connected P3IO device. Outputs the full device " + "path to stdout if found.\n"); + fprintf( + stderr, + " verp3io: Get and print the P3IO version information to " + "stdout.\n"); + fprintf(stderr, " init: Initialize the P3IO (\"DDR mode\").\n"); + fprintf( + stderr, + " verddr: Get and print the DDR P3IO specific information to " + "stdout.\n"); + fprintf(stderr, " watchdogon: Turn the watchdog on.\n"); + fprintf(stderr, " watchdogoff: Turn the watchdog off.\n"); + fprintf( + stderr, + " cabtype: Get and print the cabinet type information to " + "stdout.\n"); + fprintf( + stderr, " dipsw: Get and print the DIP switch state to stdout.\n"); + fprintf( + stderr, + " videofreq: Get and print the detected video frequency to " + "stdout.\n"); + fprintf( + stderr, + " test: Run interactive test mode, executes a polling read/write " + "loop to drive the IO and displays the current input states.\n"); + fprintf(stderr, " lightoff: Turn all the lights off\n"); + fprintf( + stderr, + " poll: Run a simple polling loop that polls data from the JAMMA " + "endpoint with input state output.\n"); + fprintf( + stderr, + " sensores: Run cycling individual sensores in a simple polling " + "loop with input state ouptut.\n"); + + return false; + } + + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "-v")) { + _log_level = LOG_LEVEL_WARNING; + } else if (!strcmp(argv[i], "-vv")) { + _log_level = LOG_LEVEL_INFO; + } else if (!strcmp(argv[i], "-vvv")) { + _log_level = LOG_LEVEL_MISC; + } else if (!strcmp(argv[i], "-noextio")) { + _extio_enabled = false; + } else if (!strcmp(argv[i], "-p3io")) { + if (i + 1 < argc) { + _p3io_device_path = argv[++i]; + } else { + fprintf(stderr, "Missing value for -p3io argument\n"); + } + } else if (!strcmp(argv[i], "-extio")) { + if (i + 1 < argc) { + _extio_com_port = argv[++i]; + } else { + fprintf(stderr, "Missing value for -extio argument\n"); + } + } + } + + mode = argv[argc - 1]; + + if (!strcmp(mode, "scan")) { + _mode = MODE_SCAN; + } else if (!strcmp(mode, "verp3io")) { + _mode = MODE_VERP3IO; + } else if (!strcmp(mode, "init")) { + _mode = MODE_INIT; + } else if (!strcmp(mode, "verddr")) { + _mode = MODE_VERDDR; + } else if (!strcmp(mode, "watchdogon")) { + _mode = MODE_WATCHDOGON; + } else if (!strcmp(mode, "watchdogoff")) { + _mode = MODE_WATCHDOGOFF; + } else if (!strcmp(mode, "cabtype")) { + _mode = MODE_CABTYPE; + } else if (!strcmp(mode, "dipsw")) { + _mode = MODE_DIPSW; + } else if (!strcmp(mode, "videofreq")) { + _mode = MODE_VIDEOFREQ; + } else if (!strcmp(mode, "test")) { + _mode = MODE_TEST; + } else if (!strcmp(mode, "lightoff")) { + _mode = MODE_LIGHTOFF; + } else if (!strcmp(mode, "poll")) { + _mode = MODE_POLL; + } else if (!strcmp(mode, "sensores")) { + _mode = MODE_SENSORES; + } else { + fprintf(stderr, "Invalid mode '%s'", mode); + return false; + } + + return true; +} + +static void _init_logging() +{ + log_to_writer(log_writer_stderr, NULL); + log_set_level(_log_level); +} + +static bool _mode_invalid(HANDLE handle) +{ + fprintf(stderr, "Invalid mode"); + return false; +} + +static bool _mode_scan(HANDLE handle) +{ + HRESULT hr; + char path[MAX_PATH]; + + log_info("Scanning for p3io..."); + + hr = p3iodrv_device_scan(path); + + if (FAILED(hr)) { + log_warning("Cannot find a connected p3io: %lX", hr); + return false; + } + + log_info("Found p3io: %s", path); + + printf("%s\n", path); + + return true; +} + +static bool _mode_verp3io(HANDLE handle) +{ + HRESULT hr; + char version[P3IODRV_VERSION_MAX_LEN]; + + memset(version, 0, P3IODRV_VERSION_MAX_LEN); + + hr = p3iodrv_device_read_version(handle, version); + + if (FAILED(hr)) { + log_warning("Getting version failed: %lX", hr); + return _close(handle); + } + + log_info("Version (P3IO): %s", version); + + printf("%s\n", version); + + return true; +} + +static bool _mode_init(HANDLE handle) +{ + HRESULT hr; + + hr = p3iodrv_ddr_init(handle); + + if (FAILED(hr)) { + log_warning("Init failed: %lX", hr); + return false; + } + + log_info("Initialized"); + + return true; +} + +static bool _mode_verddr(HANDLE handle) +{ + HRESULT hr; + char str[4]; + uint32_t major; + uint32_t minor; + uint32_t patch; + + hr = p3iodrv_ddr_get_version(handle, str, &major, &minor, &patch); + + if (FAILED(hr)) { + log_warning("Getting DDR version failed: %lX", hr); + return false; + } + + log_info("Version (DDR): %s %d.%d.%d", str, major, minor, patch); + + printf("%s %d.%d.%d\n", str, major, minor, patch); + + return true; +} + +static bool _mode_watchdogon(HANDLE handle) +{ + HRESULT hr; + + hr = p3iodrv_ddr_set_watchdog(handle, true); + + if (FAILED(hr)) { + log_warning("Enabling watchdog failed: %lX", hr); + return false; + } + + log_info("Watchdog on"); + + return true; +} + +static bool _mode_watchdogoff(HANDLE handle) +{ + HRESULT hr; + + hr = p3iodrv_ddr_set_watchdog(handle, false); + + if (FAILED(hr)) { + log_warning("Disabling watchdog failed: %lX", hr); + return false; + } + + log_info("Watchdog off"); + + return true; +} + +static bool _mode_cabtype(HANDLE handle) +{ + HRESULT hr; + enum p3io_cab_type type; + const char *type_str; + + hr = p3iodrv_ddr_get_cab_type(handle, &type); + + if (FAILED(hr)) { + log_warning("Getting cab type failed: %lX", hr); + return false; + } + + log_info("Cabinet type: %d", type); + + switch (type) { + case P3IO_CAB_TYPE_SD: + type_str = "sd"; + break; + + case P3IO_CAB_TYPE_HD: + type_str = "hd"; + break; + + default: + type_str = "unknown"; + break; + } + + printf("%s\n", type_str); + + return true; +} + +static bool _mode_dipsw(HANDLE handle) +{ + HRESULT hr; + uint8_t dip_sw; + + hr = p3iodrv_ddr_get_dipsw(handle, &dip_sw); + + if (FAILED(hr)) { + log_warning("Getting dip switches failed: %lX", hr); + return false; + } + + log_info("Dip switches: 0x%X", dip_sw); + + printf("0x%X\n", dip_sw); + + return true; +} + +static bool _mode_videofreq(HANDLE handle) +{ + HRESULT hr; + enum p3io_video_freq video_freq; + const char *video_freq_str; + + hr = p3iodrv_ddr_get_video_freq(handle, &video_freq); + + if (FAILED(hr)) { + log_warning("Getting video freq failed: %lX", hr); + return false; + } + + log_info("Video freq: 0x%X", video_freq); + + switch (video_freq) { + case P3IO_VIDEO_FREQ_15KHZ: + video_freq_str = "15khz"; + break; + + case P3IO_VIDEO_FREQ_31KHZ: + video_freq_str = "31khz"; + break; + + default: + video_freq_str = "unknown"; + break; + } + + printf("%s\n", video_freq_str); + + return true; +} + +static bool _mode_test(HANDLE handle_p3io) +{ + HRESULT hr; + HANDLE handle_extio; + bool result; + + if (_extio_enabled) { + hr = extiodrv_device_open(_extio_com_port, &handle_extio); + + if (FAILED(hr)) { + log_warning( + "Failed opening EXTIO on com port '%s': %lX", + _extio_com_port, + hr); + return false; + } + } else { + handle_extio = INVALID_HANDLE_VALUE; + } + + result = mode_test_proc(handle_p3io, handle_extio); + + if (_extio_enabled) { + hr = extiodrv_device_close(&handle_extio); + + if (FAILED(hr)) { + log_warning("Failed closing EXTIO: %lX", hr); + result = false; + } + } + + return result; +} + +static bool _mode_lightoff(HANDLE handle_p3io) +{ + HRESULT hr; + HANDLE handle_extio; + + struct p3io_ddr_output output; + struct extiodrv_extio_pad_lights pad_lights[EXTIO_PAD_LIGHT_MAX_PLAYERS]; + bool neons; + + memset(&output, 0, sizeof(output)); + memset(&pad_lights, 0, sizeof(pad_lights)); + neons = false; + + if (_extio_enabled) { + hr = extiodrv_device_open(_extio_com_port, &handle_extio); + + if (FAILED(hr)) { + log_warning( + "Failed opening EXTIO on com port '%s': %lX", + _extio_com_port, + hr); + return false; + } + } else { + handle_extio = INVALID_HANDLE_VALUE; + } + + hr = p3iodrv_ddr_set_outputs(handle_p3io, &output); + + if (FAILED(hr)) { + log_warning("Setting outputs failed: %lX", hr); + return false; + } + + if (_extio_enabled) { + hr = extiodrv_extio_transfer( + handle_extio, + EXTIODRV_EXTIO_SENSOR_READ_MODE_ALL, + pad_lights, + neons); + + if (FAILED(hr)) { + log_warning("extio transfer failed: %lX", hr); + return false; + } + } + + if (_extio_enabled) { + hr = extiodrv_device_close(&handle_extio); + + if (FAILED(hr)) { + log_warning("Failed closing EXTIO: %lX", hr); + return false; + } + } + + return true; +} + +static bool _mode_poll(HANDLE handle_p3io) +{ + HRESULT hr; + HANDLE handle_extio; + + struct p3io_ddr_jamma jamma; + struct p3io_ddr_output output; + struct extiodrv_extio_pad_lights pad_lights[EXTIO_PAD_LIGHT_MAX_PLAYERS]; + bool neons; + + uint32_t cnt; + + memset(&output, 0, sizeof(output)); + memset(&pad_lights, 0, sizeof(pad_lights)); + neons = false; + + if (_extio_enabled) { + hr = extiodrv_device_open(_extio_com_port, &handle_extio); + + if (FAILED(hr)) { + log_warning( + "Failed opening EXTIO on com port '%s': %lX", + _extio_com_port, + hr); + return false; + } + } else { + handle_extio = INVALID_HANDLE_VALUE; + } + + fprintf( + stderr, + ">>> Press enter to start endless polling loop. Press Escape to exit " + "to exit polling loop <<<\n"); + + if (getchar() != '\n') { + return true; + } + + cnt = 0; + + while ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) == 0) { + hr = p3iodrv_ddr_set_outputs(handle_p3io, &output); + + if (FAILED(hr)) { + log_warning("Setting outputs failed: %lX", hr); + return false; + } + + if (_extio_enabled) { + hr = extiodrv_extio_transfer( + handle_extio, + EXTIODRV_EXTIO_SENSOR_READ_MODE_ALL, + pad_lights, + neons); + + if (FAILED(hr)) { + log_warning("extio transfer failed: %lX", hr); + return false; + } + } + + hr = p3iodrv_ddr_get_jamma(handle_p3io, &jamma); + + if (FAILED(hr)) { + log_warning("Reading jamma failed: %lX", hr); + return false; + } + + printf( + "%d: %08X %08X %08X\n", + cnt, + *((uint32_t *) &jamma) & 0xFFFFFF00, + jamma.unused, + jamma.unused2); + + cnt++; + } + + if (_extio_enabled) { + hr = extiodrv_device_close(&handle_extio); + + if (FAILED(hr)) { + log_warning("Failed closing EXTIO: %lX", hr); + return false; + } + } + + return true; +} + +static bool _mode_sensores(HANDLE handle_p3io) +{ + HRESULT hr; + HANDLE handle_extio; + + struct p3io_ddr_jamma jamma; + struct p3io_ddr_output output; + struct extiodrv_extio_pad_lights pad_lights[EXTIO_PAD_LIGHT_MAX_PLAYERS]; + bool neons; + + uint32_t cnt; + uint8_t sensor_read; + + memset(&output, 0, sizeof(output)); + memset(&pad_lights, 0, sizeof(pad_lights)); + neons = false; + + if (_extio_enabled) { + hr = extiodrv_device_open(_extio_com_port, &handle_extio); + + if (FAILED(hr)) { + log_warning( + "Failed opening EXTIO on com port '%s': %lX", + _extio_com_port, + hr); + return false; + } + } else { + handle_extio = INVALID_HANDLE_VALUE; + } + + fprintf( + stderr, + ">>> Press enter to start endless sensor cycling loop. Press Escape to " + "exit to exit sensor cycling loop <<<\n"); + + if (getchar() != '\n') { + return true; + } + + cnt = 0; + sensor_read = 0; + + while ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) == 0) { + hr = p3iodrv_ddr_set_outputs(handle_p3io, &output); + + if (FAILED(hr)) { + log_warning("Setting outputs failed: %lX", hr); + return false; + } + + if (_extio_enabled) { + hr = extiodrv_extio_transfer( + handle_extio, sensor_read + 1, pad_lights, neons); + + if (FAILED(hr)) { + log_warning("extio transfer failed: %lX", hr); + return false; + } + } + + hr = p3iodrv_ddr_get_jamma(handle_p3io, &jamma); + + if (FAILED(hr)) { + log_warning("Reading jamma failed: %lX", hr); + return false; + } + + printf( + "%d (%d): %08X %08X %08X\n", + cnt, + sensor_read, + *((uint32_t *) &jamma) & 0xFFFFFF00, + jamma.unused, + jamma.unused2); + + cnt++; + sensor_read++; + + if (sensor_read > 3) { + sensor_read = 0; + } + } + + if (_extio_enabled) { + hr = extiodrv_device_close(&handle_extio); + + if (FAILED(hr)) { + log_warning("Failed closing EXTIO: %lX", hr); + return false; + } + } + + return true; +} + +int main(int argc, char **argv) +{ + HANDLE handle; + bool result; + + if (!_process_cmd_args(argc, argv)) { + return -1; + } + + _init_logging(); + + if (_mode != MODE_SCAN) { + if (!_scan_and_open(&handle)) { + result = false; + } else { + result = _mode_procs[_mode](handle); + + if (!_close(&handle)) { + result = false; + } + } + } else { + result = _mode_procs[_mode](INVALID_HANDLE_VALUE); + } + + return result ? 0 : -1; +} \ No newline at end of file diff --git a/src/main/p3io-ddr-tool/mode-test.c b/src/main/p3io-ddr-tool/mode-test.c new file mode 100644 index 0000000..791e6d2 --- /dev/null +++ b/src/main/p3io-ddr-tool/mode-test.c @@ -0,0 +1,406 @@ +#define LOG_MODULE "mode-test" + +#include +#include + +#include "extiodrv/extio.h" + +#include "p3io/ddr.h" +#include "p3iodrv/ddr.h" +#include "p3iodrv/device.h" + +#include "util/log.h" + +#include "mode-test.h" + +struct mode_test_output_state { + struct p3io_ddr_output output; + struct extiodrv_extio_pad_lights pad_lights[EXTIO_PAD_LIGHT_MAX_PLAYERS]; + bool neons; +}; + +struct mode_test_input_state { + struct p3io_ddr_jamma jamma[4]; +}; + +static bool _check_input_state_change( + const struct mode_test_input_state *cur_state, + const struct mode_test_input_state *prev_state) +{ + return !memcmp(cur_state, prev_state, sizeof(struct mode_test_input_state)); +} + +static void _input_state_swap( + struct mode_test_input_state **cur_state, + struct mode_test_input_state **prev_state) +{ + struct mode_test_input_state *tmp; + + tmp = *cur_state; + *cur_state = *prev_state; + *prev_state = tmp; +} + +static bool _update_io_frame( + HANDLE handle_p3io, + HANDLE handle_extio, + bool polling_mode, + struct mode_test_input_state *input, + const struct mode_test_output_state *output) +{ + HRESULT hr; + + hr = p3iodrv_ddr_set_outputs(handle_p3io, &output->output); + + if (FAILED(hr)) { + log_warning("Setting outputs failed: %lX", hr); + return false; + } + + if (polling_mode) { + hr = extiodrv_extio_transfer( + handle_extio, + EXTIODRV_EXTIO_SENSOR_READ_MODE_ALL, + output->pad_lights, + output->neons); + + if (FAILED(hr)) { + log_warning("extio transfer failed: %lX", hr); + return false; + } + + hr = p3iodrv_ddr_get_jamma(handle_p3io, &input->jamma[0]); + + if (FAILED(hr)) { + log_warning("Reading jamma failed: %lX", hr); + return false; + } + } else { + for (uint8_t i = 0; i < 4; i++) { + hr = extiodrv_extio_transfer( + handle_extio, i + 1, output->pad_lights, output->neons); + + if (FAILED(hr)) { + log_warning("extio transfer failed: %lX", hr); + return false; + } + + hr = p3iodrv_ddr_get_jamma(handle_p3io, &input->jamma[i]); + + if (FAILED(hr)) { + log_warning("Reading jamma failed: %lX", hr); + return false; + } + } + } + + return true; +} + +static void _set_outputs_on_inputs( + const struct mode_test_input_state *input, + struct mode_test_output_state *output) +{ + memset(&output->pad_lights, 0, sizeof(output->pad_lights)); + + output->output.cabinet.p1_menu_buttons = 0; + output->output.cabinet.p2_menu_buttons = 0; + + for (uint8_t i = 0; i < 4; i++) { + output->pad_lights[0].up |= input->jamma[i].p1.pad_up ? 1 : 0; + output->pad_lights[0].down |= input->jamma[i].p1.pad_down ? 1 : 0; + output->pad_lights[0].left |= input->jamma[i].p1.pad_left ? 1 : 0; + output->pad_lights[0].right |= input->jamma[i].p1.pad_right ? 1 : 0; + + output->pad_lights[1].up |= input->jamma[i].p2.pad_up ? 1 : 0; + output->pad_lights[1].down |= input->jamma[i].p2.pad_down ? 1 : 0; + output->pad_lights[1].left |= input->jamma[i].p2.pad_left ? 1 : 0; + output->pad_lights[1].right |= input->jamma[i].p2.pad_right ? 1 : 0; + + output->output.cabinet.p1_menu_buttons |= + (input->jamma[i].p1.menu_left || input->jamma[i].p1.menu_right || + input->jamma[i].p1.menu_start) ? + 1 : + 0; + output->output.cabinet.p2_menu_buttons |= + (input->jamma[i].p2.menu_left || input->jamma[i].p2.menu_right || + input->jamma[i].p2.menu_start) ? + 1 : + 0; + } +} + +static void _render_main( + uint8_t cnt, bool polling_mode, const struct mode_test_input_state *state) +{ + printf("Counter: %d\n", cnt); + printf("Polling mode: %d\n", polling_mode); + printf("Player 1 Player 2\n"); + printf("\n"); + printf("Pad\n"); + + if (polling_mode) { + printf( + "Up: %d Up: %d\n", + state->jamma[0].p1.pad_up, + state->jamma[0].p2.pad_up); + printf( + "Down: %d Down: %d\n", + state->jamma[0].p1.pad_down, + state->jamma[0].p2.pad_down); + printf( + "Left: %d Left: %d\n", + state->jamma[0].p1.pad_left, + state->jamma[0].p2.pad_left); + printf( + "Right: %d Right: %d\n", + state->jamma[0].p1.pad_right, + state->jamma[0].p2.pad_right); + } else { + printf( + "Up: %c%c%c%c Up: %c%c%c%c\n", + state->jamma[0].p1.pad_up ? 'U' : '_', + state->jamma[1].p1.pad_up ? 'D' : '_', + state->jamma[2].p1.pad_up ? 'L' : '_', + state->jamma[3].p1.pad_up ? 'R' : '_', + state->jamma[0].p2.pad_up ? 'U' : '_', + state->jamma[1].p2.pad_up ? 'D' : '_', + state->jamma[2].p2.pad_up ? 'L' : '_', + state->jamma[3].p2.pad_up) ? + 'R' : + '_'; + printf( + "Down: %c%c%c%c Down: %c%c%c%c\n", + state->jamma[0].p1.pad_down ? 'U' : '_', + state->jamma[1].p1.pad_down ? 'D' : '_', + state->jamma[2].p1.pad_down ? 'L' : '_', + state->jamma[3].p1.pad_down ? 'R' : '_', + state->jamma[0].p2.pad_down ? 'U' : '_', + state->jamma[1].p2.pad_down ? 'D' : '_', + state->jamma[2].p2.pad_down ? 'L' : '_', + state->jamma[3].p2.pad_down ? 'R' : '_'); + printf( + "Left: %c%c%c%c Left: %c%c%c%c\n", + state->jamma[0].p1.pad_left ? 'U' : '_', + state->jamma[1].p1.pad_left ? 'D' : '_', + state->jamma[2].p1.pad_left ? 'L' : '_', + state->jamma[3].p1.pad_left ? 'R' : '_', + state->jamma[0].p2.pad_left ? 'U' : '_', + state->jamma[1].p2.pad_left ? 'D' : '_', + state->jamma[2].p2.pad_left ? 'L' : '_', + state->jamma[3].p2.pad_left ? 'R' : '_'); + printf( + "Right: %c%c%c%c Right: %c%c%c%c\n", + state->jamma[0].p1.pad_right ? 'U' : '_', + state->jamma[1].p1.pad_right ? 'D' : '_', + state->jamma[2].p1.pad_right ? 'L' : '_', + state->jamma[3].p1.pad_right ? 'R' : '_', + state->jamma[0].p2.pad_right ? 'U' : '_', + state->jamma[1].p2.pad_right ? 'D' : '_', + state->jamma[2].p2.pad_right ? 'L' : '_', + state->jamma[3].p2.pad_right ? 'R' : '_'); + } + + printf("\n"); + printf("Menu\n"); + printf( + "Start: %d Start: %d\n", + state->jamma[0].p1.menu_start, + state->jamma[0].p2.menu_start); + printf( + "Up: %d Up: %d\n", + state->jamma[0].operator.p1_menu_up, + state->jamma[0].operator.p2_menu_up); + printf( + "Down: %d Down: %d\n", + state->jamma[0].operator.p1_menu_down, + state->jamma[0].operator.p2_menu_down); + printf( + "Left: %d Left: %d\n", + state->jamma[0].p1.menu_left, + state->jamma[0].p2.menu_left); + printf( + "Right: %d Right: %d\n", + state->jamma[0].p1.menu_right, + state->jamma[0].p2.menu_right); + printf("\n"); + printf("Operator\n"); + printf( + "Test: %d Service: %d Coin: %d\n", + state->jamma[0].operator.test, + state->jamma[0].operator.service, + state->jamma[0].operator.coin); +} + +static void +_render_menu(bool *continue_loop, struct mode_test_output_state *output) +{ + printf( + "Menu options:\n" + " 0: Exit menu and continue loop\n" + " 1: Exit\n" + " 2: Set neon state\n" + " 3: Top lamp state\n" + " 4: Set all lights on\n" + " 5: Set all lights off\n" + "Waiting for input: "); + + char c = getchar(); + + // Keyboard input debounce + Sleep(10); + + switch (c) { + case '1': { + *continue_loop = false; + break; + } + + case '2': { + int state; + int n; + + printf("Enter neon state (0/1): "); + + n = scanf("%d", &state); + + if (n > 0) { + output->neons = state > 0; + } + + break; + } + + case '3': { + char buf[4]; + int n; + + printf("Enter top lamp state, chain of 0/1s: "); + + n = scanf("%8s", buf); + + if (n > 0) { + output->output.cabinet.top_p1_upper = buf[0] == '1'; + output->output.cabinet.top_p1_lower = buf[1] == '1'; + output->output.cabinet.top_p2_upper = buf[2] == '1'; + output->output.cabinet.top_p2_lower = buf[3] == '1'; + } + + break; + } + + case '4': { + output->output.cabinet.top_p1_upper = 1; + output->output.cabinet.top_p1_lower = 1; + output->output.cabinet.top_p2_upper = 1; + output->output.cabinet.top_p2_lower = 1; + + output->output.cabinet.p1_menu_buttons = 1; + output->output.cabinet.p2_menu_buttons = 1; + + output->neons = true; + + output->pad_lights[0].up = true; + output->pad_lights[0].down = true; + output->pad_lights[0].left = true; + output->pad_lights[0].right = true; + + output->pad_lights[1].up = true; + output->pad_lights[1].down = true; + output->pad_lights[1].left = true; + output->pad_lights[1].right = true; + + break; + } + + case '5': { + output->output.cabinet.top_p1_upper = 0; + output->output.cabinet.top_p1_lower = 0; + output->output.cabinet.top_p2_upper = 0; + output->output.cabinet.top_p2_lower = 0; + + output->output.cabinet.p1_menu_buttons = 0; + output->output.cabinet.p2_menu_buttons = 0; + + output->neons = false; + + output->pad_lights[0].up = false; + output->pad_lights[0].down = false; + output->pad_lights[0].left = false; + output->pad_lights[0].right = false; + + output->pad_lights[1].up = false; + output->pad_lights[1].down = false; + output->pad_lights[1].left = false; + output->pad_lights[1].right = false; + + break; + } + + case '0': + default: + break; + } +} + +bool mode_test_proc(HANDLE handle_p3io, HANDLE handle_extio) +{ + struct mode_test_input_state state[2]; + struct mode_test_input_state *cur_input_state; + struct mode_test_input_state *prev_input_state; + struct mode_test_output_state output_state; + + bool polling_mode; + bool loop; + uint8_t cnt; + + memset(&state, 0, sizeof(state)); + cur_input_state = &state[0]; + prev_input_state = &state[1]; + memset(&output_state, 0, sizeof(output_state)); + + polling_mode = true; + loop = true; + cnt = 0; + + fprintf(stderr, ">>> Press enter to start running test mode <<<\n"); + + if (getchar() != '\n') { + return true; + } + + while (loop) { + if ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) != 0) { + system("cls"); + + _render_menu(&loop, &output_state); + } else { + _input_state_swap(&cur_input_state, &prev_input_state); + + memset(cur_input_state, 0, sizeof(struct mode_test_input_state)); + + if (!_update_io_frame( + handle_p3io, + handle_extio, + polling_mode, + cur_input_state, + &output_state)) { + return false; + } + + _set_outputs_on_inputs(cur_input_state, &output_state); + + if (_check_input_state_change(cur_input_state, prev_input_state)) { + system("cls"); + + _render_main(cnt, polling_mode, cur_input_state); + } + } + + /* avoid CPU banging */ + Sleep(1); + ++cnt; + } + + return true; +} \ No newline at end of file diff --git a/src/main/p3io-ddr-tool/mode-test.h b/src/main/p3io-ddr-tool/mode-test.h new file mode 100644 index 0000000..884ad66 --- /dev/null +++ b/src/main/p3io-ddr-tool/mode-test.h @@ -0,0 +1,11 @@ +#ifndef P3IO_DDR_TOOL_MODE_TEST_H +#define P3IO_DDR_TOOL_MODE_TEST_H + +#include + +#include +#include + +bool mode_test_proc(HANDLE handle_p3io, HANDLE handle_extio); + +#endif \ No newline at end of file diff --git a/src/main/p3io/cmd.c b/src/main/p3io/cmd.c index 6a0a7ff..bfb4e82 100644 --- a/src/main/p3io/cmd.c +++ b/src/main/p3io/cmd.c @@ -4,25 +4,49 @@ #include "util/log.h" -uint8_t p3io_req_cmd(const union p3io_req_any *src) +uint8_t p3io_get_full_req_size(const union p3io_req_any *req) { - log_assert(src != NULL); + /* Length byte in this packet format counts everything from the length + byte onwards. The length byte itself occurs at the start of the frame. */ - /* In requests, the command byte is the first byte after the header. */ - - return src->raw[sizeof(struct p3io_hdr)]; + return req->hdr.nbytes + 1; } -void p3io_resp_init( - struct p3io_hdr *dest, size_t nbytes, const struct p3io_hdr *req) +uint8_t p3io_get_full_resp_size(const union p3io_resp_any *resp) { - log_assert(dest != NULL); - log_assert(req != NULL); - log_assert(nbytes < 0x100); + /* Length byte in this packet format counts everything from the length + byte onwards. The length byte itself occurs at the start of the frame. */ + + return resp->hdr.nbytes + 1; +} + +void p3io_req_hdr_init( + struct p3io_hdr *req_hdr, uint8_t seq_no, uint8_t cmd, size_t size) +{ + log_assert(req_hdr != NULL); + log_assert(size < P3IO_MAX_MESSAGE_SIZE); + + memset(req_hdr, 0, sizeof(struct p3io_hdr)); /* Length byte in this packet format counts everything from the length byte onwards. The length byte itself occurs at the start of the frame. */ - dest->nbytes = nbytes - 1; - dest->seq_no = req->seq_no; + req_hdr->nbytes = size - 1; + req_hdr->seq_no = seq_no; + req_hdr->cmd = cmd; +} + +void p3io_resp_hdr_init( + struct p3io_hdr *resp_hdr, size_t nbytes, const struct p3io_hdr *req_hdr) +{ + log_assert(resp_hdr != NULL); + log_assert(req_hdr != NULL); + log_assert(nbytes < P3IO_MAX_MESSAGE_SIZE); + + /* Length byte in this packet format counts everything from the length + byte onwards. The length byte itself occurs at the start of the frame. */ + + resp_hdr->nbytes = nbytes - 1; + resp_hdr->seq_no = req_hdr->seq_no; + resp_hdr->cmd = req_hdr->cmd; } diff --git a/src/main/p3io/cmd.h b/src/main/p3io/cmd.h index 498b6e2..5359ec4 100644 --- a/src/main/p3io/cmd.h +++ b/src/main/p3io/cmd.h @@ -5,15 +5,24 @@ #include "p3io/frame.h" +// Max message size of bulk endpoint for p3io command messaging +#define P3IO_MAX_MESSAGE_SIZE 256 + enum { P3IO_CMD_GET_VERSION = 0x01, + // Appears to be some alternative "initialize" (set mode) command call on + // DDR but currently unclear how this is being used. So far, it causes the + // p3io (on a SD cab) to get stuck/crash when issued. + P3IO_CMD_INIT2 = 0x03, P3IO_CMD_SET_WATCHDOG = 0x05, P3IO_CMD_POWEROFF = 0x22, P3IO_CMD_SET_OUTPUTS = 0x24, P3IO_CMD_READ_PLUG = 0x25, P3IO_CMD_GET_CAB_TYPE_OR_DIPSW = 0x27, P3IO_CMD_GET_VIDEO_FREQ = 0x29, - P3IO_CMD_SET_MODE = 0x2F, + // Unknown command that is part of DDR's IO setup routine + P3IO_CMD_UNKNOWN_2B = 0x2B, + P3IO_CMD_INIT = 0x2F, P3IO_CMD_GET_COINSTOCK = 0x31, P3IO_CMD_SET_COINCOUNTER = 0x32, P3IO_CMD_RS232_OPEN_CLOSE = 0x38, @@ -21,58 +30,119 @@ enum { P3IO_CMD_RS232_READ = 0x3B, }; -enum { +enum p3io_rs232_cmd { P3IO_RS232_CMD_OPEN = 0x00, P3IO_RS232_CMD_CLOSE = 0xFF, }; -enum { +enum p3io_rs232_baud_rate { P3IO_RS232_BAUD_19200 = 0x02, P3IO_RS232_BAUD_38400 = 0x03, P3IO_RS232_BAUD_57600 = 0x04, }; +/** + * Selector for the P3IO_CMD_GET_CAB_TYPE_OR_DIPSW command + */ +enum p3io_cab_type_dip_sw_selector { + P3IO_DIP_SW_SELECTOR = 0, + P3IO_CAB_TYPE_SELECTOR = 1, +}; + +/** + * Enum for available video frequencies to select from. Depending on the + * game, this feature is used or unused. + * + * Set by a jumper wire on the Jamma harness. + */ +enum p3io_video_freq { + P3IO_VIDEO_FREQ_15KHZ = 0xFF, + P3IO_VIDEO_FREQ_31KHZ = 0x7F, +}; + +/** + * Enum for available cabinet/display types to select from. Depending on the + * game, this feature is used or unused. + * + * The p3io detects the monitor type through its monitor passthrough. If the + * monitor provides an EDID, it assumes it's an HD monitor. Otherwise, it + * assumes SD because CRT monitors likely do not provide EDIDs. + */ +enum p3io_cab_type { + P3IO_CAB_TYPE_SD = 0, + P3IO_CAB_TYPE_HD = 1, +}; + #pragma pack(push, 1) +/** + * p3io command message header of any message (request and response) + */ struct p3io_hdr { uint8_t nbytes; uint8_t seq_no; + uint8_t cmd; }; -struct p3io_req_u8 { +// ----------------------------------------------------------------------------- + +struct p3io_req_version { struct p3io_hdr hdr; - uint8_t cmd; - uint8_t u8; }; -struct p3io_req_get_cab_type_or_dipsw { +struct p3io_req_init2 { struct p3io_hdr hdr; - uint8_t cmd; - uint8_t cab_type_or_dipsw; + uint8_t unknown_AA; }; -struct p3io_req_set_coin_counter { +struct p3io_req_watchdog { struct p3io_hdr hdr; - uint8_t cmd; - uint8_t coin_counter[2]; + uint8_t enable; }; struct p3io_req_set_outputs { struct p3io_hdr hdr; - uint8_t cmd; - uint8_t unk_00; + uint8_t unk_FF; uint32_t outputs; }; struct p3io_req_read_plug { struct p3io_hdr hdr; - uint8_t cmd; uint8_t flags; }; +struct p3io_req_get_cab_type_or_dipsw { + struct p3io_hdr hdr; + uint8_t cab_type_or_dipsw; +}; + +struct p3io_req_get_video_freq { + struct p3io_hdr hdr; + uint8_t unknown_05; +}; + +struct p3io_req_unknown_2b { + struct p3io_hdr hdr; + // either 0 or 1 set by game (boolean value) + // set to 0 for p3io dragon (SD cab?) + uint8_t unknown; +}; + +struct p3io_req_init { + struct p3io_hdr hdr; +}; + +struct p3io_req_coin_stock { + struct p3io_hdr hdr; +}; + +struct p3io_req_set_coin_counter { + struct p3io_hdr hdr; + uint8_t coin_counter[2]; +}; + struct p3io_req_rs232_open_close { struct p3io_hdr hdr; - uint8_t cmd; uint8_t port_no; uint8_t subcmd; uint8_t baud_code; @@ -80,96 +150,157 @@ struct p3io_req_rs232_open_close { struct p3io_req_rs232_read { struct p3io_hdr hdr; - uint8_t cmd; uint8_t port_no; uint8_t nbytes; }; struct p3io_req_rs232_write { struct p3io_hdr hdr; - uint8_t cmd; uint8_t port_no; uint8_t nbytes; uint8_t bytes[128]; }; +struct p3io_req_raw { + uint8_t data[P3IO_MAX_MESSAGE_SIZE]; +}; + +// Structs sorted in command ID order union p3io_req_any { struct p3io_hdr hdr; - struct p3io_req_u8 u8; - struct p3io_req_get_cab_type_or_dipsw cab_type_or_dipsw; - struct p3io_req_set_coin_counter set_coin_counter; + struct p3io_req_version version; + struct p3io_req_init2 init2; + struct p3io_req_watchdog watchdog; struct p3io_req_set_outputs set_outputs; struct p3io_req_read_plug read_plug; + struct p3io_req_get_cab_type_or_dipsw cab_type_or_dipsw; + struct p3io_req_get_video_freq video_freq; + struct p3io_req_unknown_2b unknown_2b; + struct p3io_req_init init; + struct p3io_req_coin_stock coin_stock; + struct p3io_req_set_coin_counter set_coin_counter; struct p3io_req_rs232_open_close rs232_open_close; struct p3io_req_rs232_read rs232_read; struct p3io_req_rs232_write rs232_write; - uint8_t raw[128]; + struct p3io_req_raw raw; }; -struct p3io_resp_u8 { +// ----------------------------------------------------------------------------- + +struct p3io_resp_version { + struct p3io_hdr hdr; + char str[4]; + uint8_t major; + uint8_t minor; + uint8_t patch; +}; + +struct p3io_resp_init2 { struct p3io_hdr hdr; uint8_t status; - uint8_t u8; +}; + +struct p3io_resp_watchdog { + struct p3io_hdr hdr; + uint8_t state; +}; + +struct p3io_resp_set_outputs { + struct p3io_hdr hdr; + // Apparently always 0xFF on real hardware + uint8_t unkn_FF; + uint8_t unkn; +}; + +struct p3io_resp_read_plug { + struct p3io_hdr hdr; + uint8_t present; + uint8_t rom[8]; + uint8_t eeprom[32]; }; struct p3io_resp_get_cab_type_or_dipsw { + struct p3io_hdr hdr; + uint8_t state; +}; + +struct p3io_resp_get_video_freq { + struct p3io_hdr hdr; + uint8_t video_freq; +}; + +struct p3io_resp_unknown_2b { + struct p3io_hdr hdr; + uint8_t unknown; +}; + +struct p3io_resp_init { + struct p3io_hdr hdr; + uint8_t status; +}; + +struct p3io_resp_set_coin_counter { struct p3io_hdr hdr; uint8_t status; }; struct p3io_resp_coin_stock { struct p3io_hdr hdr; - uint8_t status; uint8_t error; uint16_t slots[2]; }; -struct p3io_resp_read_plug { +struct p3io_resp_rs232_open_close { struct p3io_hdr hdr; uint8_t status; - uint8_t present; - uint8_t rom[8]; - uint8_t eeprom[32]; }; struct p3io_resp_rs232_read { struct p3io_hdr hdr; - uint8_t status; uint8_t nbytes; uint8_t bytes[126]; }; struct p3io_resp_rs232_write { struct p3io_hdr hdr; - uint8_t status; uint8_t nbytes; }; -struct p3io_resp_version { - struct p3io_hdr hdr; - uint8_t status; - char str[4]; - uint32_t major; - uint32_t minor; - uint32_t patch; +struct p3io_resp_raw { + uint8_t data[P3IO_MAX_MESSAGE_SIZE]; }; union p3io_resp_any { struct p3io_hdr hdr; - struct p3io_resp_u8 u8; - struct p3io_resp_get_cab_type_or_dipsw cab_type_or_dipsw; - struct p3io_resp_coin_stock coin_stock; + struct p3io_resp_version version; + struct p3io_resp_init2 init2; + struct p3io_resp_watchdog watchdog; + struct p3io_resp_set_outputs set_outputs; struct p3io_resp_read_plug read_plug; + struct p3io_resp_get_cab_type_or_dipsw cab_type_or_dipsw; + struct p3io_resp_get_video_freq video_freq; + struct p3io_resp_unknown_2b unknown_2b; + struct p3io_resp_init init; + struct p3io_resp_coin_stock coin_stock; + struct p3io_resp_set_coin_counter set_coin_counter; + struct p3io_resp_rs232_open_close rs232_open_close; struct p3io_resp_rs232_read rs232_read; struct p3io_resp_rs232_write rs232_write; - struct p3io_resp_version version; + struct p3io_resp_raw raw; }; +// ----------------------------------------------------------------------------- + #pragma pack(pop) -uint8_t p3io_req_cmd(const union p3io_req_any *src); +uint8_t p3io_get_full_req_size(const union p3io_req_any *req); -void p3io_resp_init( - struct p3io_hdr *dest, size_t nbytes, const struct p3io_hdr *req); +uint8_t p3io_get_full_resp_size(const union p3io_resp_any *resp); + +void p3io_req_hdr_init( + struct p3io_hdr *hdr, uint8_t seq_no, uint8_t cmd, size_t size); + +void p3io_resp_hdr_init( + struct p3io_hdr *resp_hdr, size_t nbytes, const struct p3io_hdr *req_hdr); #endif diff --git a/src/main/p3io/ddr.h b/src/main/p3io/ddr.h new file mode 100644 index 0000000..85c1693 --- /dev/null +++ b/src/main/p3io/ddr.h @@ -0,0 +1,81 @@ +#ifndef P3IO_DDR_H +#define P3IO_DDR_H + +#include + +#pragma pack(push, 1) + +struct p3io_ddr_player { + uint8_t menu_start : 1; + uint8_t pad_up : 1; + uint8_t pad_down : 1; + uint8_t pad_left : 1; + uint8_t pad_right : 1; + uint8_t unknown : 1; + uint8_t menu_left : 1; + uint8_t menu_right : 1; +}; + +struct p3io_ddr_operator { + // Yes, this also contains the HD cab menu up and down buttons + // which are not "operator" inputs. I suppose they just had to go + // somewhere ¯\_(ツ)_/¯ + uint8_t p1_menu_up : 1; + uint8_t p1_menu_down : 1; + uint8_t p2_menu_up : 1; + uint8_t p2_menu_down : 1; + uint8_t test : 1; + uint8_t coin : 1; + uint8_t service : 1; + uint8_t unknown : 1; +}; + +struct p3io_ddr_jamma { + uint8_t unknown_80; + + struct p3io_ddr_player p1; + struct p3io_ddr_player p2; + struct p3io_ddr_operator operator; + + uint32_t unused; + uint32_t unused2; +}; + +struct p3io_ddr_cabinet_light { + uint8_t p1_menu_buttons : 1; + uint8_t p2_menu_buttons : 1; + uint8_t unknown : 2; + uint8_t top_p1_lower : 1; + uint8_t top_p1_upper : 1; + uint8_t top_p2_lower : 1; + uint8_t top_p2_upper : 1; +}; + +struct p3io_ddr_output { + uint8_t unused_00[3]; + struct p3io_ddr_cabinet_light cabinet; +}; + +_Static_assert( + sizeof(struct p3io_ddr_player) == 1, + "struct p3io_ddr_player is the wrong size"); + +_Static_assert( + sizeof(struct p3io_ddr_operator) == 1, + "struct p3io_ddr_operator is the wrong size"); + +_Static_assert( + sizeof(struct p3io_ddr_jamma) == sizeof(uint32_t) * 3, + "struct p3io_ddr_jamma is the wrong size"); + +_Static_assert( + sizeof(struct p3io_ddr_cabinet_light) == 1, + "struct p3io_ddr_cabinet_light is the wrong size"); + +_Static_assert( + sizeof(struct p3io_ddr_output) == sizeof(uint32_t), + "struct p3io_ddr_output is the wrong size"); + +#pragma pack(pop) + +#endif \ No newline at end of file diff --git a/src/main/p3io/frame.c b/src/main/p3io/frame.c index df00016..bc50dd4 100644 --- a/src/main/p3io/frame.c +++ b/src/main/p3io/frame.c @@ -8,6 +8,9 @@ #include "util/iobuf.h" #include "util/log.h" +#define P3IO_FRAME_SOF 0xAA +#define P3IO_FRAME_ESCAPE 0xFF + HRESULT p3io_frame_encode(struct iobuf *dest, const void *ptr, size_t nbytes) { @@ -24,17 +27,17 @@ p3io_frame_encode(struct iobuf *dest, const void *ptr, size_t nbytes) goto trunc; } - dest->bytes[dest->pos++] = 0xAA; + dest->bytes[dest->pos++] = P3IO_FRAME_SOF; for (i = 0; i < nbytes; i++) { b = bytes[i]; - if (b == 0xAA || b == 0xFF) { + if (b == P3IO_FRAME_SOF || b == P3IO_FRAME_ESCAPE) { if (dest->pos + 1 >= dest->nbytes) { goto trunc; } - dest->bytes[dest->pos++] = 0xFF; + dest->bytes[dest->pos++] = P3IO_FRAME_ESCAPE; dest->bytes[dest->pos++] = ~b; } else { if (dest->pos >= dest->nbytes) { @@ -64,7 +67,7 @@ p3io_frame_decode(struct iobuf *dest, struct const_iobuf *src) log_assert(dest != NULL); log_assert(src != NULL); - if (src->pos >= src->nbytes || src->bytes[src->pos] != 0xAA) { + if (src->pos >= src->nbytes || src->bytes[src->pos] != P3IO_FRAME_SOF) { return E_FAIL; } @@ -78,9 +81,9 @@ p3io_frame_decode(struct iobuf *dest, struct const_iobuf *src) b = src->bytes[src->pos++]; - if (b == 0xAA) { + if (b == P3IO_FRAME_SOF) { return E_FAIL; - } else if (b == 0xFF) { + } else if (b == P3IO_FRAME_ESCAPE) { if (escape) { return E_FAIL; } diff --git a/src/main/p3iodrv/Module.mk b/src/main/p3iodrv/Module.mk new file mode 100644 index 0000000..7116bc2 --- /dev/null +++ b/src/main/p3iodrv/Module.mk @@ -0,0 +1,10 @@ +libs += p3iodrv + +libs_p4iodrv := \ + p3io \ + util \ + +src_p3iodrv := \ + ddr.c \ + device.c \ + diff --git a/src/main/p3iodrv/ddr.c b/src/main/p3iodrv/ddr.c new file mode 100644 index 0000000..2cc4526 --- /dev/null +++ b/src/main/p3iodrv/ddr.c @@ -0,0 +1,326 @@ +#define LOG_MODULE "p3iodrv-ddr" + +#include "p3io/cmd.h" + +#include "util/log.h" + +#include "ddr.h" +#include "device.h" + +static uint8_t p3iodrv_ddr_seq_counter; + +static uint8_t p3iodrv_ddr_get_and_update_seq_counter() +{ + return p3iodrv_ddr_seq_counter++ & 0xF; +} + +static HRESULT p3iodrv_ddr_check_resp( + const union p3io_resp_any *resp, + uint8_t expected_size, + const union p3io_req_any *corresponding_req) +{ + uint8_t actual_size; + + log_assert(resp); + + actual_size = p3io_get_full_resp_size(resp); + + if (actual_size != expected_size) { + log_warning( + "Incorrect response size, actual %d != expected %d", + actual_size, + expected_size); + return HRESULT_FROM_WIN32(ERROR_BAD_LENGTH); + } + + if (resp->hdr.seq_no != corresponding_req->hdr.seq_no) { + log_warning( + "Incorrect sequence num in response, actual %d != expected %d", + resp->hdr.seq_no, + corresponding_req->hdr.seq_no); + return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + + if (resp->hdr.cmd != corresponding_req->hdr.cmd) { + log_warning( + "Incorrect command in response, actual 0x%d != expected 0x%d", + resp->hdr.cmd, + corresponding_req->hdr.cmd); + return HRESULT_FROM_WIN32(ERROR_BAD_COMMAND); + } + + return S_OK; +} + +HRESULT p3iodrv_ddr_init(HANDLE handle) +{ + HRESULT hr; + uint8_t seq_cnt; + union p3io_req_any req; + union p3io_resp_any resp; + + log_assert(handle != INVALID_HANDLE_VALUE); + + seq_cnt = p3iodrv_ddr_get_and_update_seq_counter(); + + p3io_req_hdr_init(&req.hdr, seq_cnt, P3IO_CMD_INIT, sizeof(req.init)); + + hr = p3iodrv_device_transfer(handle, &req, &resp); + + if (FAILED(hr)) { + return hr; + } + + hr = p3iodrv_ddr_check_resp(&resp, sizeof(resp.init), &req); + + if (FAILED(hr)) { + return hr; + } + + if (resp.init.status != 0) { + log_warning("Initialization failed"); + return HRESULT_FROM_WIN32(ERROR_GEN_FAILURE); + } + + return S_OK; +} + +HRESULT p3iodrv_ddr_get_version( + HANDLE handle, + char str[4], + uint32_t *major, + uint32_t *minor, + uint32_t *patch) +{ + HRESULT hr; + uint8_t seq_cnt; + union p3io_req_any req; + union p3io_resp_any resp; + + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(major); + log_assert(minor); + log_assert(patch); + + seq_cnt = p3iodrv_ddr_get_and_update_seq_counter(); + + p3io_req_hdr_init( + &req.hdr, seq_cnt, P3IO_CMD_GET_VERSION, sizeof(req.version)); + + hr = p3iodrv_device_transfer(handle, &req, &resp); + + if (FAILED(hr)) { + return hr; + } + + hr = p3iodrv_ddr_check_resp(&resp, sizeof(resp.version), &req); + + if (FAILED(hr)) { + return hr; + } + + memcpy(str, resp.version.str, sizeof(char[4])); + *major = resp.version.major; + *minor = resp.version.minor; + *patch = resp.version.patch; + + return S_OK; +} + +HRESULT p3iodrv_ddr_set_watchdog(HANDLE handle, bool enable) +{ + HRESULT hr; + uint8_t seq_cnt; + union p3io_req_any req; + union p3io_resp_any resp; + + log_assert(handle != INVALID_HANDLE_VALUE); + + seq_cnt = p3iodrv_ddr_get_and_update_seq_counter(); + + p3io_req_hdr_init( + &req.hdr, seq_cnt, P3IO_CMD_SET_WATCHDOG, sizeof(req.watchdog)); + + req.watchdog.enable = enable ? 1 : 0; + + hr = p3iodrv_device_transfer(handle, &req, &resp); + + if (FAILED(hr)) { + return hr; + } + + hr = p3iodrv_ddr_check_resp(&resp, sizeof(resp.watchdog), &req); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +HRESULT p3iodrv_ddr_get_dipsw(HANDLE handle, uint8_t *state) +{ + HRESULT hr; + uint8_t seq_cnt; + union p3io_req_any req; + union p3io_resp_any resp; + + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(state); + + seq_cnt = p3iodrv_ddr_get_and_update_seq_counter(); + + p3io_req_hdr_init( + &req.hdr, + seq_cnt, + P3IO_CMD_GET_CAB_TYPE_OR_DIPSW, + sizeof(req.cab_type_or_dipsw)); + + req.cab_type_or_dipsw.cab_type_or_dipsw = P3IO_DIP_SW_SELECTOR; + + hr = p3iodrv_device_transfer(handle, &req, &resp); + + if (FAILED(hr)) { + return hr; + } + + hr = p3iodrv_ddr_check_resp(&resp, sizeof(resp.cab_type_or_dipsw), &req); + + if (FAILED(hr)) { + return hr; + } + + *state = resp.cab_type_or_dipsw.state; + + return S_OK; +} + +HRESULT p3iodrv_ddr_get_cab_type(HANDLE handle, enum p3io_cab_type *type) +{ + HRESULT hr; + uint8_t seq_cnt; + union p3io_req_any req; + union p3io_resp_any resp; + + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(type); + + seq_cnt = p3iodrv_ddr_get_and_update_seq_counter(); + + p3io_req_hdr_init( + &req.hdr, + seq_cnt, + P3IO_CMD_GET_CAB_TYPE_OR_DIPSW, + sizeof(req.cab_type_or_dipsw)); + + req.cab_type_or_dipsw.cab_type_or_dipsw = P3IO_CAB_TYPE_SELECTOR; + + hr = p3iodrv_device_transfer(handle, &req, &resp); + + if (FAILED(hr)) { + return hr; + } + + hr = p3iodrv_ddr_check_resp(&resp, sizeof(resp.cab_type_or_dipsw), &req); + + if (FAILED(hr)) { + return hr; + } + + *type = resp.cab_type_or_dipsw.state; + + return S_OK; +} + +HRESULT p3iodrv_ddr_get_video_freq(HANDLE handle, enum p3io_video_freq *freq) +{ + HRESULT hr; + uint8_t seq_cnt; + union p3io_req_any req; + union p3io_resp_any resp; + + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(freq); + + seq_cnt = p3iodrv_ddr_get_and_update_seq_counter(); + + p3io_req_hdr_init( + &req.hdr, seq_cnt, P3IO_CMD_GET_VIDEO_FREQ, sizeof(req.video_freq)); + + // Must be set to 5 in order to return the right values. There might be more + // to this, e.g. this being some kind of selector where to read from the + // JAMMA harness (?) but this is good for now to get what we want to know + // for DDR + req.video_freq.unknown_05 = 5; + + hr = p3iodrv_device_transfer(handle, &req, &resp); + + if (FAILED(hr)) { + return hr; + } + + hr = p3iodrv_ddr_check_resp(&resp, sizeof(resp.video_freq), &req); + + if (FAILED(hr)) { + return hr; + } + + *freq = (enum p3io_video_freq) resp.video_freq.video_freq; + + return S_OK; +} + +HRESULT p3iodrv_ddr_get_jamma(HANDLE handle, struct p3io_ddr_jamma *jamma) +{ + HRESULT hr; + uint32_t *jamma_raw; + + jamma_raw = (uint32_t *) jamma; + + hr = p3iodrv_device_read_jamma(handle, jamma_raw); + + if (FAILED(hr)) { + return hr; + } + + // Inputs are active low for p1, p2 and operator bits + jamma_raw[0] ^= 0xFFFFFF00; + + return S_OK; +} + +HRESULT +p3iodrv_ddr_set_outputs(HANDLE handle, const struct p3io_ddr_output *state) +{ + HRESULT hr; + uint8_t seq_cnt; + union p3io_req_any req; + union p3io_resp_any resp; + + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(state); + + seq_cnt = p3iodrv_ddr_get_and_update_seq_counter(); + + p3io_req_hdr_init( + &req.hdr, seq_cnt, P3IO_CMD_SET_OUTPUTS, sizeof(req.set_outputs)); + + memcpy(&req.set_outputs.outputs, state, sizeof(req.set_outputs.outputs)); + + // Always set by the game like this + req.set_outputs.unk_FF = 0xFF; + + hr = p3iodrv_device_transfer(handle, &req, &resp); + + if (FAILED(hr)) { + return hr; + } + + hr = p3iodrv_ddr_check_resp(&resp, sizeof(resp.set_outputs), &req); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} \ No newline at end of file diff --git a/src/main/p3iodrv/ddr.h b/src/main/p3iodrv/ddr.h new file mode 100644 index 0000000..2f8987d --- /dev/null +++ b/src/main/p3iodrv/ddr.h @@ -0,0 +1,115 @@ +#ifndef P3IODRV_DDR_H +#define P3IODRV_DDR_H + +#include +#include +#include + +#include "p3io/cmd.h" +#include "p3io/ddr.h" + +#define P3IODRV_DDR_VERSION_STR_LEN 4 + +/** + * Initialize the p3io device for operation. + * + * This call is also somewhat referred to as "set mode" in the game, though it + * is part of a larger setup routine. + * + * If the p3io device is not initialized, the menu buttons (left, right, start) + * on (a SD) cabinet will periodically blink. Once this function is called, this + * blinking stops indicating the p3io device is initialized. + * + * Other p3io commands might work (partially) even if this function is not + * called. However, it is recommended to call this first after every p3io + * device reset. + * + * @param handle A handle to an opened p3io device. + * @result S_OK on success, any other HRESULT value on error + */ +HRESULT p3iodrv_ddr_init(HANDLE handle); + +/** + * Get the DDR specific version information from the p3io device. + * + * @param handle A handle to an opened p3io device. + * @param str Pointer to a buffer to write the version string to + * @param major Pointer to a uint32 to write the major version number to + * @param minor Pointer to a uint32 to write the minor version number to + * @param patch Poitner to a uint32 to ewrite the patch version number to + * @result S_OK on success, any other HRESULT value on error + */ +HRESULT p3iodrv_ddr_get_version( + HANDLE handle, + char str[P3IODRV_DDR_VERSION_STR_LEN], + uint32_t *major, + uint32_t *minor, + uint32_t *patch); + +/** + * Enable/disable the device side watchdog. The watchdog will reset the device + * if it is not reset periodically. + * + * If you stop sending commands to the p3io, it will trigger after about 5-7 + * seconds of not receiving anything. You can notice the watchdog triggered when + * the menu buttons start to blink again which indicates the device is not + * initialized (anymore). + * + * The watchdog seems to expect that you periodically send the "get version" + * command to reset it and keep the p3io alive (p3iodrv_ddr_get_version). + * + * @param handle A handle to an opened p3io device. + * @param enable true to enable the watchdog, false to disable + * @result S_OK on success, any other HRESULT value on error + */ +HRESULT p3iodrv_ddr_set_watchdog(HANDLE handle, bool enable); + +/** + * Get the current state of the dip switches from the p3io. + * + * @param handle A handle to an opened p3io device. + * @param state Pointer to a uint8 to write the resulting dip switch state to + * @result S_OK on success, any other HRESULT value on error + */ +HRESULT p3iodrv_ddr_get_dipsw(HANDLE handle, uint8_t *state); + +/** + * Get the detected cabinet type from the p3io. + * + * @param handle A handle to an opened p3io device. + * @param type Pointer to a enum p3io_cab_type to write the resulting state to + * @result S_OK on success, any other HRESULT value on error + */ +HRESULT p3iodrv_ddr_get_cab_type(HANDLE handle, enum p3io_cab_type *type); + +/** + * Get the detected video frequency of the monitor from the p3io. + * + * @param handle A handle to an opened p3io device. + * @param type Pointer to a enum p3io_video_freq to write the resulting state to + * @result S_OK on success, any other HRESULT value on error + */ +HRESULT p3iodrv_ddr_get_video_freq(HANDLE handle, enum p3io_video_freq *freq); + +/** + * Get the current state of the JAMMA edge/input from the p3io. + * + * @param handle A handle to an opened p3io device. + * @param type Pointer to a struct p3io_ddr_jamma to write the resulting state + * to + * @result S_OK on success, any other HRESULT value on error + */ +HRESULT p3iodrv_ddr_get_jamma(HANDLE handle, struct p3io_ddr_jamma *jamma); + +/** + * Set (some cabinet) light output state on the p3io. + * + * @param handle A handle to an opened p3io device. + * @param type Pointer to a struct p3io_ddr_output with the light output data to + * set on the p3io + * @result S_OK on success, any other HRESULT value on error + */ +HRESULT +p3iodrv_ddr_set_outputs(HANDLE handle, const struct p3io_ddr_output *state); + +#endif \ No newline at end of file diff --git a/src/main/p3iodrv/device.c b/src/main/p3iodrv/device.c new file mode 100644 index 0000000..dbbb04a --- /dev/null +++ b/src/main/p3iodrv/device.c @@ -0,0 +1,429 @@ +#define LOG_MODULE "p3iodrv-device" + +#include +#include +#include + +// clang-format off +// Don't format because the order is important here +#include +#include +// clang-format on + +#include "p3io/cmd.h" +#include "p3io/guid.h" +#include "p3io/ioctl.h" + +#include "p3iodrv/device.h" + +#include "util/log.h" +#include "util/str.h" + +#define P3IO_DEVICE_FILENMAME "\\p3io" + +static HRESULT _p3iodrv_write_file_iobuf(HANDLE handle, struct iobuf *buffer) +{ + HRESULT res; + DWORD bytes_processed; + + if (!WriteFile( + handle, buffer->bytes, buffer->pos, &bytes_processed, NULL)) { + res = HRESULT_FROM_WIN32(GetLastError()); + log_warning("WriteFile failed: %lX", res); + return res; + } + + log_misc("Written length: %ld", bytes_processed); + + if (bytes_processed != buffer->pos) { + log_warning( + "WriteFile didn't finish: %ld != %Id", + bytes_processed, + buffer->pos); + return HRESULT_FROM_WIN32(ERROR_BAD_LENGTH); + } + + return S_OK; +} + +static HRESULT _p3iodrv_read_file_iobuf(HANDLE handle, struct iobuf *buffer) +{ + HRESULT hr; + DWORD bytes_processed; + + if (!ReadFile( + handle, + buffer->bytes + buffer->pos, + buffer->nbytes - buffer->pos, + &bytes_processed, + NULL)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + log_warning("ReadFile failed: %lX", hr); + return hr; + } + + log_misc("Read length: %ld", bytes_processed); + + buffer->pos += bytes_processed; + + return S_OK; +} + +static HRESULT +_p3iodrv_write_message(HANDLE handle, const union p3io_req_any *req) +{ + HRESULT hr; + uint8_t req_framed_bytes[P3IO_MAX_MESSAGE_SIZE]; + + struct iobuf req_deframed; + struct iobuf req_framed; + + memset(req_framed_bytes, 0, sizeof(req_framed_bytes)); + + // Used for logging the buffer, only + req_deframed.bytes = (uint8_t *) req; + req_deframed.nbytes = sizeof(union p3io_req_any); + req_deframed.pos = req->hdr.nbytes + 1; + + req_framed.bytes = req_framed_bytes; + req_framed.nbytes = sizeof(req_framed_bytes); + req_framed.pos = 0; + + iobuf_log(&req_deframed, "p3iodrv-device request deframed"); + + hr = p3io_frame_encode(&req_framed, req, req->hdr.nbytes + 1); + + if (FAILED(hr)) { + log_warning("Encoding request payload failed: %lX", hr); + return hr; + } + + iobuf_log(&req_framed, "p3iodrv-device request framed"); + + hr = _p3iodrv_write_file_iobuf(handle, &req_framed); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +/** + * Read a full response message and not just a part of it. + * + * Because someone decided to implement a general "streaming bytes" solution + * with framing (ACIO) on top of a streaming byte solution with framing (USB), + * there is no guarantee that single reads are complete. This even applies to + * reads that are less then the max USB endpoint size. + * + * When polling to read data too fast in succession to writes, e.g. on a p3io + * command which first writes a request, then reads a response, the p3io + * hardware might not be fast enough to put all bytes of the full response into + * the device hardware side buffer. It appears that the device hardware/the + * firmware pushes out chunks of/single bytes instead of preparing full messages + * that are framed to the available USB buffer size. + * + * Therefore, messages can get fragmented and require multiple read calls to + * ensure the read buffer is fully flushed on the device side and received on + * the host. The following solution does exactly that it "reads until read + * everything". The following heuristics are applied to determine when we think + * a response message is fully received: + * + * 1. Read until you get parts of a header to determine how long the entire ACIO + * message is supposed to be. This must assume that the device will always put + * the correct amount of bytes onto the wire at some point. If it stopps doing + * that, there is no way to possibility to detect and take action on that. + * 2. Read until you get the complete ACIO paket based on the ACIO paket length + * field. + */ +static HRESULT +_p3iodrv_read_full_response_message(HANDLE handle, union p3io_resp_any *resp) +{ + HRESULT hr; + uint8_t resp_framed_bytes[P3IO_MAX_MESSAGE_SIZE]; + + struct iobuf resp_framed; + struct iobuf resp_framed_flip_read; + struct iobuf resp_deframed; + + memset(resp_framed_bytes, 0, sizeof(resp_framed_bytes)); + + resp_framed.bytes = resp_framed_bytes; + resp_framed.nbytes = sizeof(resp_framed_bytes); + resp_framed.pos = 0; + + log_misc("Receiving response"); + + // Read as long as we do not consider the message to be complete + while (true) { + hr = _p3iodrv_read_file_iobuf(handle, &resp_framed); + + if (FAILED(hr)) { + return hr; + } + + // Read at least 0xAA + header size, otherwise keep reading + if (resp_framed.pos < 1 + sizeof(struct p3io_hdr)) { + log_misc( + "Read truncated message, length %ld less than header, read " + "again", + (DWORD) resp_framed.pos); + continue; + } + + log_misc("Response received, length: %ld", (DWORD) resp_framed.pos); + + iobuf_log(&resp_framed, "p3iodrv-device response framed"); + + // Potential pre-mature deframing because we cannot be sure we already + // received the entire frame + + // Use a flipped buffer to read from the framed response + resp_framed_flip_read.bytes = resp_framed.bytes; + resp_framed_flip_read.nbytes = resp_framed.pos; + resp_framed_flip_read.pos = 0; + + // Init for de-framing + resp_deframed.bytes = resp->raw.data; + resp_deframed.nbytes = sizeof(resp->raw); + resp_deframed.pos = 0; + + hr = p3io_frame_decode( + &resp_deframed, (struct const_iobuf *) &resp_framed_flip_read); + + iobuf_log(&resp_deframed, "p3iodrv-device response deframed"); + + // Verify if the de-framed message is actually complete + // +1 for the length byte + if (resp->hdr.nbytes + 1 > resp_deframed.pos) { + log_warning( + "Truncated de-framed message, length %ld less than size on " + "header %ld, read again", + (DWORD) resp_deframed.pos, + (DWORD) resp->hdr.nbytes); + continue; + } + + // Eror check decoding only after verification that frame is completed + // Otherwise, this leads to failed decodings on truncated pakets that + // require another read + if (FAILED(hr)) { + log_warning("Decoding response payload failed: %lX", hr); + return hr; + } + + log_misc( + "Recieved complete response, length de-framed %ld", + (DWORD) resp_deframed.pos); + + break; + } + + return S_OK; +} + +HRESULT p3iodrv_device_scan(char path[MAX_PATH]) +{ + HRESULT res; + PSP_DEVICE_INTERFACE_DETAIL_DATA_A detail_data; + HDEVINFO devinfo; + DWORD required_size; + SP_DEVICE_INTERFACE_DATA interface_data; + DWORD err; + + detail_data = NULL; + + devinfo = SetupDiGetClassDevsA( + &p3io_guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + + if (devinfo == INVALID_HANDLE_VALUE) { + // No device with GUID found + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + if (!SetupDiEnumDeviceInterfaces( + devinfo, NULL, &p3io_guid, 0, &interface_data)) { + res = HRESULT_FROM_WIN32(GetLastError()); + log_warning("SetupDiEnumDeviceInterfaces failed: %lX", res); + SetupDiDestroyDeviceInfoList(devinfo); + return res; + } + + if (!SetupDiGetDeviceInterfaceDetailA( + devinfo, &interface_data, NULL, 0, &required_size, NULL)) { + err = GetLastError(); + res = HRESULT_FROM_WIN32(err); + + if (err != ERROR_INSUFFICIENT_BUFFER) { + log_warning("SetupDiGetDeviceInterfaceDetailA failed: %lX", res); + SetupDiDestroyDeviceInfoList(devinfo); + return res; + } + } + + detail_data = malloc(required_size); + detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); + + if (!SetupDiGetDeviceInterfaceDetailA( + devinfo, &interface_data, detail_data, required_size, NULL, NULL)) { + res = HRESULT_FROM_WIN32(GetLastError()); + log_warning("SetupDiGetDeviceInterfaceDetailA failed: %lX", res); + free(detail_data); + SetupDiDestroyDeviceInfoList(devinfo); + return res; + } + + str_cpy(path, MAX_PATH, detail_data->DevicePath); + str_cat(path, MAX_PATH, P3IO_DEVICE_FILENMAME); + + free(detail_data); + SetupDiDestroyDeviceInfoList(devinfo); + + return S_OK; +} + +HRESULT p3iodrv_device_open(const char *path, HANDLE *handle) +{ + HRESULT res; + + log_assert(path); + log_assert(handle); + + *handle = CreateFileA( + path, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + 0, + NULL); + + if (*handle == INVALID_HANDLE_VALUE) { + res = HRESULT_FROM_WIN32(GetLastError()); + log_warning("CreateFileA failed: %lX", res); + return res; + } + + return S_OK; +} + +HRESULT p3iodrv_device_close(HANDLE *handle) +{ + HRESULT res; + + log_assert(handle); + log_assert(*handle != INVALID_HANDLE_VALUE); + + if (CloseHandle(*handle) == FALSE) { + res = HRESULT_FROM_WIN32(GetLastError()); + log_warning("Closing failed: %lX", res); + return res; + } + + *handle = INVALID_HANDLE_VALUE; + + return S_OK; +} + +HRESULT p3iodrv_device_read_version( + HANDLE handle, char version[P3IODRV_VERSION_MAX_LEN]) +{ + HRESULT res; + DWORD bytes_returned; + + log_assert(handle != INVALID_HANDLE_VALUE); + + memset(version, 0, sizeof(char[P3IODRV_VERSION_MAX_LEN])); + + if (!DeviceIoControl( + handle, + P3IO_IOCTL_GET_VERSION, + NULL, + 0, + version, + sizeof(char[P3IODRV_VERSION_MAX_LEN]), + &bytes_returned, + NULL)) { + res = HRESULT_FROM_WIN32(GetLastError()); + log_warning("DeviceIoControl failed: %lX", res); + return res; + } + + if (bytes_returned < 1) { + log_warning( + "DeviceIoControl returned size invalid: %ld", bytes_returned); + return HRESULT_FROM_WIN32(ERROR_BAD_LENGTH); + } + + return S_OK; +} + +HRESULT +p3iodrv_device_read_jamma(HANDLE handle, uint32_t jamma[P3IO_DRV_JAMMA_MAX_LEN]) +{ + HRESULT res; + DWORD bytes_returned; + + log_assert(handle != INVALID_HANDLE_VALUE); + + if (!DeviceIoControl( + handle, + P3IO_IOCTL_READ_JAMMA, + NULL, + 0, + jamma, + sizeof(uint32_t[P3IO_DRV_JAMMA_MAX_LEN]), + &bytes_returned, + NULL)) { + res = HRESULT_FROM_WIN32(GetLastError()); + log_warning("ioctl read jamma failed: %lX", res); + return res; + } + + if (bytes_returned != sizeof(uint32_t[P3IO_DRV_JAMMA_MAX_LEN])) { + log_warning( + "DeviceIoControl returned size invalid: %ld", bytes_returned); + return HRESULT_FROM_WIN32(ERROR_BAD_LENGTH); + } + + return S_OK; +} + +HRESULT p3iodrv_device_transfer( + HANDLE handle, const union p3io_req_any *req, union p3io_resp_any *resp) +{ + HRESULT hr; + + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(req); + log_assert(resp); + + log_misc( + "TRANSFER START, req nbytes %d, seq_no %d, cmd 0x%X", + req->hdr.nbytes, + req->hdr.seq_no, + req->hdr.cmd); + + hr = _p3iodrv_write_message(handle, req); + + if (FAILED(hr)) { + return hr; + } + + hr = _p3iodrv_read_full_response_message(handle, resp); + + if (FAILED(hr)) { + return hr; + } + + log_misc( + "TRANSFER FINISHED, resp nbytes %d, seq_no %d, cmd 0x%X", + resp->hdr.nbytes, + resp->hdr.seq_no, + resp->hdr.cmd); + + return S_OK; +} \ No newline at end of file diff --git a/src/main/p3iodrv/device.h b/src/main/p3iodrv/device.h new file mode 100644 index 0000000..f8ea144 --- /dev/null +++ b/src/main/p3iodrv/device.h @@ -0,0 +1,105 @@ +#ifndef P3IODRV_DEVICE_H +#define P3IODRV_DEVICE_H + +#include +#include +#include + +#include "p3io/cmd.h" + +#include "util/iobuf.h" + +#define P3IODRV_VERSION_MAX_LEN 128 +#define P3IO_DRV_JAMMA_MAX_LEN 3 + +/** + * Scan for a connected p3io device. Currently, this does not support multiple + * p3io devices and only returns the "first one" found. + * + * @param path Buffer of size MAX_PATH this function writes the full device path + * to when a device was found + * @return S_OK when a device was found, ERROR_FILE_NOT_FOUND if none was found + * or any other HRESULT error on any errors while scanning occured. + */ +HRESULT p3iodrv_device_scan(char path[MAX_PATH]); + +/** + * Open a p3io device by the given device path. + * + * @param path Pointer to a buffer with the full device path of the p3io device + * to open + * @param handle Pointer to a handle variable to return the opened device handle + * to when opening was successful + * @return S_OK if opening was successful, ERROR_FILE_NOT_FOUND if the device + * with the given path was not found, or any other HRESULT error when + * trying to open the device. + */ +HRESULT p3iodrv_device_open(const char *path, HANDLE *handle); + +/** + * Close an open p3io device by its handle + * + * @param handle Pointer to the handle to close. The variable will be set to + * INVALID_HANDLE_VALUE on success + * @return S_OK if closing was successful, or any other HRESULT value on errors + */ +HRESULT p3iodrv_device_close(HANDLE *handle); + +/** + * Read the version of the p3io device. + * + * This call is not guaranteed to be supported with all p3io drivers, e.g. we + * know this is supported by the 64-bit p3io driver but apparently not by the + * 32-bit one. + * + * @param handle A valid handle to an opened p3io device + * @param version Pointer to a buffer to write the resulting version string to + * @return S_OK if the operation was successful, or any other HRESULT value on + * errors + */ +HRESULT p3iodrv_device_read_version( + HANDLE handle, char version[P3IODRV_VERSION_MAX_LEN]); + +/** + * Read input data from the JAMMA edge. As this is supported by different games + * and a core functionality of the p3io, the data is not further specified + * here. This function is optimised for low latency inputs by continously + * polling it on the host. + * + * @param handle A valid handle to an opened p3io device + * @param jamma Buffer to write the read input data to on success + * @return S_OK if the operation was successful, or any other HRESULT value on + * errors + */ +HRESULT p3iodrv_device_read_jamma( + HANDLE handle, uint32_t jamma[P3IO_DRV_JAMMA_MAX_LEN]); + +/** + * Execute a generic data transfer. + * + * This is _the_ generic messaging interface of the p3io to exchange data with + * the device. Depending on the type of p3io and firmware, different command + * codes support different functionality. + * + * Note that this interface is not optimized for low latency data transfers + * and is a fully blocking "endpoint". The caller of this function is blocked + * entirely until the host request is fully processed and the device response is + * fully received. + * + * Implementation detail: It implements (sort of?) the ACIO protocol on top of + * the USB as a transport layer. Encoding and framing is being handling by this + * function transparently, so the caller does not have to worry about that + * anymore. + * + * @param handle A valid handle to an opened p3io device + * @param req Pointer to a buffer/struct with a p3io formatted request + * @param resp Pointer to a buffer to receive the device's response to. Ensure + * this is large enough for the expceted response matching the + * issued type of request. + * @return S_OK if the transfer was successful and a response was received and + * written to the buffer, or any other HRESULT value on errors + */ +HRESULT p3iodrv_device_transfer( + HANDLE handle, const union p3io_req_any *req, union p3io_resp_any *resp); + +#endif \ No newline at end of file diff --git a/src/main/p3ioemu/emu.c b/src/main/p3ioemu/emu.c index 4b42e59..c72f9d8 100644 --- a/src/main/p3ioemu/emu.c +++ b/src/main/p3ioemu/emu.c @@ -35,35 +35,28 @@ static HRESULT p3io_emu_handle_write(struct irp *irp); static HRESULT p3io_cmd_dispatch(const union p3io_req_any *req); static void p3io_cmd_get_version( - const struct p3io_hdr *req, struct p3io_resp_version *resp); - -static void -p3io_cmd_set_watchdog(const struct p3io_req_u8 *req, struct p3io_resp_u8 *resp); - + const struct p3io_req_version *req, struct p3io_resp_version *resp); +static void p3io_cmd_set_watchdog( + const struct p3io_req_watchdog *req, struct p3io_resp_watchdog *resp); static void p3io_cmd_set_outputs( - const struct p3io_req_set_outputs *req, struct p3io_resp_u8 *resp); - + const struct p3io_req_set_outputs *req, struct p3io_resp_set_outputs *resp); static void p3io_cmd_read_plug( const struct p3io_req_read_plug *req, struct p3io_resp_read_plug *resp); - static void p3io_cmd_get_cab_type_or_dipsw( const struct p3io_req_get_cab_type_or_dipsw *req, struct p3io_resp_get_cab_type_or_dipsw *resp); - static void p3io_cmd_get_video_freq( - const struct p3io_req_u8 *req, struct p3io_resp_u8 *resp); - + const struct p3io_req_get_video_freq *req, + struct p3io_resp_get_video_freq *resp); static void -p3io_cmd_set_mode(const struct p3io_req_u8 *req, struct p3io_resp_u8 *resp); - +p3io_cmd_init(const struct p3io_req_init *req, struct p3io_resp_init *resp); static void p3io_cmd_get_coinstock( - const struct p3io_req_u8 *req, struct p3io_resp_coin_stock *resp); - + const struct p3io_req_coin_stock *req, struct p3io_resp_coin_stock *resp); static void p3io_cmd_set_coin_counter( - const struct p3io_req_set_coin_counter *req, struct p3io_resp_u8 *resp); - + const struct p3io_req_set_coin_counter *req, + struct p3io_resp_set_coin_counter *resp); static void -p3io_cmd_unknown(const union p3io_req_any *req, struct p3io_resp_u8 *resp); +p3io_cmd_unknown(const union p3io_req_any *req, struct p3io_resp_raw *resp); void p3io_emu_init(const struct p3io_ops *ops, void *ctx) { @@ -218,7 +211,7 @@ static HRESULT p3io_emu_handle_write(struct irp *irp) memset(&req, 0, sizeof(req)); - deframe.bytes = req.raw; + deframe.bytes = req.raw.data; deframe.nbytes = sizeof(req.raw); deframe.pos = 0; @@ -242,23 +235,20 @@ static HRESULT p3io_emu_handle_write(struct irp *irp) static HRESULT p3io_cmd_dispatch(const union p3io_req_any *req) { union p3io_resp_any resp; - uint8_t cmd; - cmd = p3io_req_cmd(req); - - switch (cmd) { + switch (req->hdr.cmd) { case P3IO_CMD_GET_VERSION: - p3io_cmd_get_version(&req->hdr, &resp.version); + p3io_cmd_get_version(&req->version, &resp.version); break; case P3IO_CMD_SET_WATCHDOG: - p3io_cmd_set_watchdog(&req->u8, &resp.u8); + p3io_cmd_set_watchdog(&req->watchdog, &resp.watchdog); break; case P3IO_CMD_SET_OUTPUTS: - p3io_cmd_set_outputs(&req->set_outputs, &resp.u8); + p3io_cmd_set_outputs(&req->set_outputs, &resp.set_outputs); break; @@ -274,27 +264,29 @@ static HRESULT p3io_cmd_dispatch(const union p3io_req_any *req) break; case P3IO_CMD_GET_VIDEO_FREQ: - p3io_cmd_get_video_freq(&req->u8, &resp.u8); + p3io_cmd_get_video_freq(&req->video_freq, &resp.video_freq); break; - case P3IO_CMD_SET_MODE: - p3io_cmd_set_mode(&req->u8, &resp.u8); + case P3IO_CMD_INIT: + p3io_cmd_init(&req->init, &resp.init); break; case P3IO_CMD_GET_COINSTOCK: - p3io_cmd_get_coinstock(&req->u8, &resp.coin_stock); + p3io_cmd_get_coinstock(&req->coin_stock, &resp.coin_stock); break; case P3IO_CMD_SET_COINCOUNTER: - p3io_cmd_set_coin_counter(&req->set_coin_counter, &resp.u8); + p3io_cmd_set_coin_counter( + &req->set_coin_counter, &resp.set_coin_counter); break; case P3IO_CMD_RS232_OPEN_CLOSE: - p3io_uart_cmd_open_close(&req->rs232_open_close, &resp.u8); + p3io_uart_cmd_open_close( + &req->rs232_open_close, &resp.rs232_open_close); break; @@ -309,7 +301,7 @@ static HRESULT p3io_cmd_dispatch(const union p3io_req_any *req) break; default: - p3io_cmd_unknown(req, &resp.u8); + p3io_cmd_unknown(req, &resp.raw); break; } @@ -319,34 +311,34 @@ static HRESULT p3io_cmd_dispatch(const union p3io_req_any *req) return S_OK; } -static void -p3io_cmd_get_version(const struct p3io_hdr *req, struct p3io_resp_version *resp) +static void p3io_cmd_get_version( + const struct p3io_req_version *req, struct p3io_resp_version *resp) { - log_misc("%s", __func__); + log_misc("Getting version"); + + p3io_resp_hdr_init(&resp->hdr, sizeof(*resp), &req->hdr); - p3io_resp_init(&resp->hdr, sizeof(resp), req); - resp->status = 0; resp->str[0] = 'H'; resp->str[1] = 'D'; resp->str[2] = 'X'; resp->str[3] = '\0'; resp->major = 1; resp->minor = 2; - resp->patch = 3; + // resp->patch = 3; } -static void -p3io_cmd_set_watchdog(const struct p3io_req_u8 *req, struct p3io_resp_u8 *resp) +static void p3io_cmd_set_watchdog( + const struct p3io_req_watchdog *req, struct p3io_resp_watchdog *resp) { - log_misc("%s(%02x)", __func__, req->u8); + log_misc("Setting watchdog: %d", req->enable); - p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); - resp->status = 0; - resp->u8 = 0; + p3io_resp_hdr_init(&resp->hdr, sizeof(*resp), &req->hdr); + + resp->state = 0; } static void p3io_cmd_set_outputs( - const struct p3io_req_set_outputs *req, struct p3io_resp_u8 *resp) + const struct p3io_req_set_outputs *req, struct p3io_resp_set_outputs *resp) { uint32_t outputs; HRESULT hr; @@ -358,9 +350,13 @@ static void p3io_cmd_set_outputs( hr = S_OK; } - p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); - resp->status = FAILED(hr); - resp->u8 = 0; + p3io_resp_hdr_init(&resp->hdr, sizeof(*resp), &req->hdr); + + if (FAILED(hr)) { + log_warning("Setting outputs failed: %lX", hr); + } + + resp->unkn_FF = 0xFF; } static void p3io_cmd_read_plug( @@ -368,9 +364,10 @@ static void p3io_cmd_read_plug( { HRESULT hr; - log_misc("%s(%02x)", __func__, req->flags); + log_misc("Reading plug: %02x", req->flags); + + p3io_resp_hdr_init(&resp->hdr, sizeof(*resp), &req->hdr); - p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); resp->present = 1; /* @@ -427,8 +424,6 @@ static void p3io_cmd_read_plug( resp->eeprom[3], resp->eeprom[4], resp->eeprom[5]); - - resp->status = FAILED(hr); } static void p3io_cmd_get_cab_type_or_dipsw( @@ -439,22 +434,22 @@ static void p3io_cmd_get_cab_type_or_dipsw( uint8_t dipsw; enum p3io_cab_type type; - p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); + p3io_resp_hdr_init(&resp->hdr, sizeof(*resp), &req->hdr); hr = S_OK; - if (req->cab_type_or_dipsw == 0) { + if (req->cab_type_or_dipsw == P3IO_CAB_TYPE_SELECTOR) { if (p3io_ops->get_cab_type) { hr = p3io_ops->get_cab_type(p3io_ops_ctx, &type); if (hr == S_OK) { switch (type) { case P3IO_CAB_TYPE_SD: - resp->status = 1; + resp->state = P3IO_CAB_TYPE_SD; break; case P3IO_CAB_TYPE_HD: - resp->status = 2; + resp->state = P3IO_CAB_TYPE_HD; break; default: @@ -462,59 +457,49 @@ static void p3io_cmd_get_cab_type_or_dipsw( break; } } else { - resp->status = 0; + resp->state = 0; } - } else { - resp->status = 0; } - } else if (req->cab_type_or_dipsw == 1) { + } else if (req->cab_type_or_dipsw == P3IO_DIP_SW_SELECTOR) { if (p3io_ops->get_dipsw) { hr = p3io_ops->get_dipsw(p3io_ops_ctx, &dipsw); if (hr == S_OK) { - resp->status = dipsw; + resp->state = dipsw; } else { - resp->status = 0; + resp->state = 0; } - } else { - resp->status = 0; } } else { log_warning( "Unknown value for cab type or dipsw: %d", req->cab_type_or_dipsw); - resp->status = 0; } } static void p3io_cmd_get_video_freq( - const struct p3io_req_u8 *req, struct p3io_resp_u8 *resp) + const struct p3io_req_get_video_freq *req, + struct p3io_resp_get_video_freq *resp) { HRESULT hr; - enum p3io_video_freq freq; - p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); + p3io_resp_hdr_init(&resp->hdr, sizeof(*resp), &req->hdr); if (p3io_ops->get_video_freq != NULL) { - hr = p3io_ops->get_video_freq(p3io_ops_ctx, &freq); + hr = p3io_ops->get_video_freq( + p3io_ops_ctx, (enum p3io_video_freq *) &resp->video_freq); } else { hr = S_OK; - freq = P3IO_VIDEO_FREQ_31KHZ; + resp->video_freq = P3IO_VIDEO_FREQ_31KHZ; } if (hr == S_OK) { - switch (freq) { + switch (resp->video_freq) { case P3IO_VIDEO_FREQ_15KHZ: log_misc("%s: Returning 15 kHz", __func__); - resp->status = 0; - resp->u8 = 0x00; - break; case P3IO_VIDEO_FREQ_31KHZ: log_misc("%s: Returning 31 kHz", __func__); - resp->status = 0; - resp->u8 = 0x80; - break; default: @@ -523,23 +508,21 @@ static void p3io_cmd_get_video_freq( } } else { log_misc("%s: Returning error! (hr=%x)", __func__, (int) hr); - resp->status = 1; - resp->u8 = 0x00; } } static void -p3io_cmd_set_mode(const struct p3io_req_u8 *req, struct p3io_resp_u8 *resp) +p3io_cmd_init(const struct p3io_req_init *req, struct p3io_resp_init *resp) { - log_misc("%s(%02x)", __func__, req->u8); + log_misc("Init"); + + p3io_resp_hdr_init(&resp->hdr, sizeof(*resp), &req->hdr); - p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); resp->status = 0; - resp->u8 = 0; } static void p3io_cmd_get_coinstock( - const struct p3io_req_u8 *req, struct p3io_resp_coin_stock *resp) + const struct p3io_req_coin_stock *req, struct p3io_resp_coin_stock *resp) { uint16_t slots[2]; HRESULT hr; @@ -552,30 +535,37 @@ static void p3io_cmd_get_coinstock( hr = S_OK; } - p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); - resp->status = 0; + p3io_resp_hdr_init(&resp->hdr, sizeof(*resp), &req->hdr); + resp->error = FAILED(hr); resp->slots[0] = _byteswap_ushort(slots[0]); resp->slots[1] = _byteswap_ushort(slots[1]); } static void p3io_cmd_set_coin_counter( - const struct p3io_req_set_coin_counter *req, struct p3io_resp_u8 *resp) + const struct p3io_req_set_coin_counter *req, + struct p3io_resp_set_coin_counter *resp) { log_misc( - "%s(%02x %02x)", __func__, req->coin_counter[0], req->coin_counter[1]); + "Setting coin counter: %02x %02x", + req->coin_counter[0], + req->coin_counter[1]); + + p3io_resp_hdr_init(&resp->hdr, sizeof(*resp), &req->hdr); - p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); resp->status = 0; - resp->u8 = 0; } static void -p3io_cmd_unknown(const union p3io_req_any *req, struct p3io_resp_u8 *resp) +p3io_cmd_unknown(const union p3io_req_any *req, struct p3io_resp_raw *resp) { - log_warning("Unsupported P3IO command: %02x", p3io_req_cmd(req)); + log_warning("Unsupported P3IO command: %02x", req->hdr.cmd); - p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); - resp->status = 1; - resp->u8 = 0; + p3io_resp_hdr_init( + (struct p3io_hdr *) &resp->data, sizeof(*resp), &req->hdr); + + // Not always applicable/correct as there are several commands not + // responding with any data, but fine for the majority of (unsupported) + // commands + resp->data[sizeof(struct p3io_hdr) + 0] = 0; } diff --git a/src/main/p3ioemu/emu.h b/src/main/p3ioemu/emu.h index a013986..4f6f054 100644 --- a/src/main/p3ioemu/emu.h +++ b/src/main/p3ioemu/emu.h @@ -8,23 +8,7 @@ #include "hook/iohook.h" -/** - * Enum for available video frequencies to select from. Depending on the - * game, this feature is used or unused. - */ -enum p3io_video_freq { - P3IO_VIDEO_FREQ_15KHZ = 0, - P3IO_VIDEO_FREQ_31KHZ = 1, -}; - -/** - * Enum for available cabinet/display types to select from. Depending on the - * game, this feature is used or unused. - */ -enum p3io_cab_type { - P3IO_CAB_TYPE_SD = 0, - P3IO_CAB_TYPE_HD = 1, -}; +#include "p3io/cmd.h" /** * P3IO operation dispatching table. Many operations are optional and not used diff --git a/src/main/p3ioemu/uart.c b/src/main/p3ioemu/uart.c index 68717b5..a4a9f73 100644 --- a/src/main/p3ioemu/uart.c +++ b/src/main/p3ioemu/uart.c @@ -38,7 +38,8 @@ void p3io_uart_set_path(size_t uart_no, const wchar_t *path) } void p3io_uart_cmd_open_close( - const struct p3io_req_rs232_open_close *req, struct p3io_resp_u8 *resp) + const struct p3io_req_rs232_open_close *req, + struct p3io_resp_rs232_open_close *resp) { const wchar_t *path; uint32_t baud_rate; @@ -104,9 +105,9 @@ void p3io_uart_cmd_open_close( } end: - p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); - resp->status = 0; - resp->u8 = FAILED(hr); + p3io_resp_hdr_init(&resp->hdr, sizeof(*resp), &req->hdr); + + resp->status = FAILED(hr); } void p3io_uart_cmd_read( @@ -140,10 +141,13 @@ void p3io_uart_cmd_read( end: /* Variable-length response, init the header manually */ + if (FAILED(hr)) { + log_warning("Reading failed: %lX", hr); + } resp->hdr.nbytes = iobuf.pos + 3; resp->hdr.seq_no = req->hdr.seq_no; - resp->status = FAILED(hr); + resp->hdr.cmd = req->hdr.cmd; resp->nbytes = iobuf.pos; } @@ -167,8 +171,12 @@ void p3io_uart_cmd_write( hr = p3io_uart_write(p3io_uart_fds[req->port_no], &iobuf); end: - p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); - resp->status = FAILED(hr); + if (FAILED(hr)) { + log_warning("Writing failed: %lX", hr); + } + + p3io_resp_hdr_init(&resp->hdr, sizeof(*resp), &req->hdr); + resp->nbytes = iobuf.pos; } diff --git a/src/main/p3ioemu/uart.h b/src/main/p3ioemu/uart.h index e0d417b..36e201b 100644 --- a/src/main/p3ioemu/uart.h +++ b/src/main/p3ioemu/uart.h @@ -6,7 +6,8 @@ void p3io_uart_set_path(size_t uart_no, const wchar_t *path); void p3io_uart_cmd_open_close( - const struct p3io_req_rs232_open_close *req, struct p3io_resp_u8 *resp); + const struct p3io_req_rs232_open_close *req, + struct p3io_resp_rs232_open_close *resp); void p3io_uart_cmd_read( const struct p3io_req_rs232_read *req, struct p3io_resp_rs232_read *resp); diff --git a/src/main/unicorntail/p3io.c b/src/main/unicorntail/p3io.c index f28103c..fd0a85c 100644 --- a/src/main/unicorntail/p3io.c +++ b/src/main/unicorntail/p3io.c @@ -136,7 +136,7 @@ static HRESULT p3io_handle_write(struct irp *irp) struct iobuf desc; HRESULT hr; - desc.bytes = req.raw; + desc.bytes = req.raw.data; desc.nbytes = sizeof(req); desc.pos = 0; @@ -146,10 +146,11 @@ static HRESULT p3io_handle_write(struct irp *irp) return hr; } - switch (p3io_req_cmd(&req)) { + switch (req.hdr.cmd) { case P3IO_CMD_RS232_OPEN_CLOSE: EnterCriticalSection(&p3io_cmd_lock); - p3io_uart_cmd_open_close(&req.rs232_open_close, &resp.u8); + p3io_uart_cmd_open_close( + &req.rs232_open_close, &resp.rs232_open_close); break; diff --git a/src/main/util/iobuf.c b/src/main/util/iobuf.c index c2d0ad1..45884ea 100644 --- a/src/main/util/iobuf.c +++ b/src/main/util/iobuf.c @@ -14,11 +14,18 @@ void iobuf_log(struct iobuf *buffer, const char *tag) str = xmalloc(str_len); log_misc( - "[%s] (%d %d)", tag, (uint32_t) buffer->nbytes, (uint32_t) buffer->pos); + "[%s] (nbytes %d, pos %d)", + tag, + (uint32_t) buffer->nbytes, + (uint32_t) buffer->pos); hex_encode_uc(buffer->bytes, buffer->nbytes, str, str_len); - log_misc("[%s]: %s", tag, str); + log_misc("full [%s]: %s", tag, str); + + hex_encode_uc(buffer->bytes, buffer->pos, str, str_len); + + log_misc("pos [%s]: %s", tag, str); free(str); } @@ -32,11 +39,18 @@ void iobuf_log_const(struct const_iobuf *buffer, const char *tag) str = xmalloc(str_len); log_misc( - "[%s] (%d %d)", tag, (uint32_t) buffer->nbytes, (uint32_t) buffer->pos); + "[%s] (nbytes %d, pos %d)", + tag, + (uint32_t) buffer->nbytes, + (uint32_t) buffer->pos); hex_encode_uc(buffer->bytes, buffer->nbytes, str, str_len); - log_misc("[%s]: %s", tag, str); + log_misc("full [%s]: %s", tag, str); + + hex_encode_uc(buffer->bytes, buffer->pos, str, str_len); + + log_misc("pos [%s]: %s", tag, str); free(str); } diff --git a/src/main/util/log.c b/src/main/util/log.c index f1f697e..7da1d63 100644 --- a/src/main/util/log.c +++ b/src/main/util/log.c @@ -10,14 +10,14 @@ static log_writer_t log_writer; static void *log_writer_ctx; -static unsigned int log_level; +static enum log_level log_level; static void log_builtin_fatal(const char *module, const char *fmt, ...); static void log_builtin_info(const char *module, const char *fmt, ...); static void log_builtin_misc(const char *module, const char *fmt, ...); static void log_builtin_warning(const char *module, const char *fmt, ...); static void log_builtin_format( - unsigned int msg_level, const char *module, const char *fmt, va_list ap); + enum log_level msg_level, const char *module, const char *fmt, va_list ap); #define IMPLEMENT_SINK(name, msg_level) \ static void name(const char *module, const char *fmt, ...) \ @@ -29,16 +29,16 @@ static void log_builtin_format( va_end(ap); \ } -IMPLEMENT_SINK(log_builtin_info, 2) -IMPLEMENT_SINK(log_builtin_misc, 3) -IMPLEMENT_SINK(log_builtin_warning, 1) +IMPLEMENT_SINK(log_builtin_info, LOG_LEVEL_INFO) +IMPLEMENT_SINK(log_builtin_misc, LOG_LEVEL_MISC) +IMPLEMENT_SINK(log_builtin_warning, LOG_LEVEL_WARNING) static void log_builtin_fatal(const char *module, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - log_builtin_format(0, module, fmt, ap); + log_builtin_format(LOG_LEVEL_FATAL, module, fmt, ap); va_end(ap); DebugBreak(); @@ -46,7 +46,7 @@ static void log_builtin_fatal(const char *module, const char *fmt, ...) } static void log_builtin_format( - unsigned int msg_level, const char *module, const char *fmt, va_list ap) + enum log_level msg_level, const char *module, const char *fmt, va_list ap) { static const char chars[] = "FWIM"; @@ -55,13 +55,11 @@ static void log_builtin_format( char msg[65536]; int result; - str_vformat(msg, sizeof(msg), fmt, ap); - result = str_format( - line, sizeof(line), "%c:%s: %s\n", chars[msg_level], module, msg); - - // TODO move this up because there is no reason to format and do all the - // above if disabled if (msg_level <= log_level) { + str_vformat(msg, sizeof(msg), fmt, ap); + result = str_format( + line, sizeof(line), "%c:%s: %s\n", chars[msg_level], module, msg); + log_writer(log_writer_ctx, line, result); } } @@ -98,7 +96,7 @@ void log_to_writer(log_writer_t writer, void *ctx) } } -void log_set_level(unsigned int new_level) +void log_set_level(enum log_level new_level) { log_level = new_level; } @@ -133,4 +131,4 @@ log_formatter_t log_impl_info = log_builtin_info; log_formatter_t log_impl_warning = log_builtin_warning; log_formatter_t log_impl_fatal = log_builtin_fatal; static log_writer_t log_writer = log_writer_null; -static unsigned int log_level = 4; +static enum log_level log_level = LOG_LEVEL_MISC; diff --git a/src/main/util/log.h b/src/main/util/log.h index a60cdb7..47603fa 100644 --- a/src/main/util/log.h +++ b/src/main/util/log.h @@ -59,6 +59,13 @@ extern log_formatter_t log_impl_info; extern log_formatter_t log_impl_warning; extern log_formatter_t log_impl_fatal; +enum log_level { + LOG_LEVEL_FATAL = 0, + LOG_LEVEL_WARNING = 1, + LOG_LEVEL_INFO = 2, + LOG_LEVEL_MISC = 3, +}; + void log_assert_body(const char *file, int line, const char *function); void log_to_external( log_formatter_t misc, @@ -67,7 +74,7 @@ void log_to_external( log_formatter_t fatal); void log_to_writer(log_writer_t writer, void *ctx); -void log_set_level(unsigned int new_level); +void log_set_level(enum log_level new_level); /* I tried to make this API match the function signature of the AVS log writer callback, but then the signature changed and the explicit line breaks