diff --git a/Module.mk b/Module.mk index 235160d..c87fd20 100644 --- a/Module.mk +++ b/Module.mk @@ -166,6 +166,7 @@ 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/p4iodrv/Module.mk include src/main/p4ioemu/Module.mk 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