1
0
mirror of https://github.com/djhackersdev/bemanitools.git synced 2024-12-01 01:27:18 +01:00

feat(p3iodrv): Add DDR compatible P3IO driver

Initial version that supports the most important
features to operate all inputs and outputs of a
DDR SD cabinet.
This commit is contained in:
icex2 2023-06-11 17:13:03 +02:00 committed by icex2
parent 1a86cc7ee5
commit c22ec4fcff
6 changed files with 986 additions and 0 deletions

View File

@ -166,6 +166,7 @@ include src/main/launcher/Module.mk
include src/main/mempatch-hook/Module.mk include src/main/mempatch-hook/Module.mk
include src/main/mm/Module.mk include src/main/mm/Module.mk
include src/main/p3io/Module.mk include src/main/p3io/Module.mk
include src/main/p3iodrv/Module.mk
include src/main/p3ioemu/Module.mk include src/main/p3ioemu/Module.mk
include src/main/p4iodrv/Module.mk include src/main/p4iodrv/Module.mk
include src/main/p4ioemu/Module.mk include src/main/p4ioemu/Module.mk

View File

@ -0,0 +1,10 @@
libs += p3iodrv
libs_p4iodrv := \
p3io \
util \
src_p3iodrv := \
ddr.c \
device.c \

326
src/main/p3iodrv/ddr.c Normal file
View File

@ -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;
}

115
src/main/p3iodrv/ddr.h Normal file
View File

@ -0,0 +1,115 @@
#ifndef P3IODRV_DDR_H
#define P3IODRV_DDR_H
#include <stdbool.h>
#include <stddef.h>
#include <windows.h>
#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

429
src/main/p3iodrv/device.c Normal file
View File

@ -0,0 +1,429 @@
#define LOG_MODULE "p3iodrv-device"
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
// clang-format off
// Don't format because the order is important here
#include <windows.h>
#include <setupapi.h>
// 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;
}

105
src/main/p3iodrv/device.h Normal file
View File

@ -0,0 +1,105 @@
#ifndef P3IODRV_DEVICE_H
#define P3IODRV_DEVICE_H
#include <stdbool.h>
#include <stddef.h>
#include <windows.h>
#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