mirror of
https://github.com/djhackersdev/bemanitools.git
synced 2025-02-20 20:41:10 +01:00
fix(iidx/ezusb): Fix IO buffer inconsistency on ezusb ioctl level
This commit is contained in:
parent
4c127d26e5
commit
da94ad8f76
@ -28,6 +28,10 @@
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
// The max buffer size in iidx's ezusb client library is 4096 for the initial
|
||||
// firmware download.
|
||||
#define MAX_IOCTL_BUFFER_SIZE 4096
|
||||
|
||||
static HRESULT ezusb_get_device_descriptor(struct irp *irp);
|
||||
static HRESULT ezusb_vendor_req(struct irp *irp);
|
||||
static HRESULT ezusb_upload_fw(struct irp *irp);
|
||||
@ -115,43 +119,114 @@ static HRESULT ezusb_open(struct irp *irp)
|
||||
|
||||
static HRESULT ezusb_ioctl(struct irp *irp)
|
||||
{
|
||||
HRESULT res;
|
||||
|
||||
// Stack alloc'd and fixed sized buffers to avoid processing costs with
|
||||
// allocations. Ensure buffers are large enough for any operation
|
||||
uint8_t write_buffer_local[MAX_IOCTL_BUFFER_SIZE];
|
||||
uint8_t read_buffer_local[MAX_IOCTL_BUFFER_SIZE];
|
||||
|
||||
uint8_t *write_buffer_orig;
|
||||
uint8_t *read_buffer_orig;
|
||||
|
||||
// Save original buffer that is owned and managed by the caller/the game
|
||||
// Do NOT read/write these buffers directly because the game's ezusb
|
||||
// interface library does not access to them entirely thread safe.
|
||||
// This causes verious odd bugs due to data read/write inconsistencies
|
||||
// between data access and modification by at least two different threads.
|
||||
//
|
||||
// The game's ezusb (client) library was created on a platform with no
|
||||
// true multi-core processing (Pentium 4 era of hardware). However, the
|
||||
// developers utilized threading primitives of the Win32 API to ensure
|
||||
// a high rate of data updates to reduce input latency. This was architected
|
||||
// using a dedicated polling thread in the ezusb client library that drives
|
||||
// the IO polling on a high update rate. The captured data was stored in
|
||||
// a shared buffer that is also accessible by other threads from the main
|
||||
// game binary. However, the data access to the same shared buffer with the
|
||||
// polling backend is not synchronized
|
||||
//
|
||||
// Thus, the various odd and flaky ezusb communication bugs we see on
|
||||
// modern, and true multi-core hardware, are the concurrency management
|
||||
// mistakes that are now creeping up. The developers back then had no
|
||||
// proper means to test these due to the lack of hardware capabilities.
|
||||
|
||||
// Save the IO buffer that is used by the ezusb client backend and shared
|
||||
// with the game's main thread
|
||||
write_buffer_orig = (uint8_t*) irp->write.bytes;
|
||||
read_buffer_orig = irp->read.bytes;
|
||||
|
||||
// Prepare our own thread locally managed and non shared buffers for any
|
||||
// further data operations that are part of the ezusb emulation stack
|
||||
memset(write_buffer_local, 0, sizeof(write_buffer_local));
|
||||
memset(read_buffer_local, 0, sizeof(read_buffer_local));
|
||||
|
||||
// Sanity check and visibility, in case this ever overflows
|
||||
if (irp->write.nbytes > sizeof(write_buffer_local)) {
|
||||
log_fatal("Insufficient local write buffer available for ioctl, local "
|
||||
"size %d, ioctl buffer size %d",
|
||||
sizeof(write_buffer_local),
|
||||
irp->write.nbytes);
|
||||
}
|
||||
|
||||
if (irp->read.nbytes > sizeof(read_buffer_local)) {
|
||||
log_fatal("Insufficient local read buffer available for ioctl, local "
|
||||
"size %d, ioctl buffer size %d",
|
||||
sizeof(read_buffer_local),
|
||||
irp->read.nbytes);
|
||||
}
|
||||
|
||||
// Temporarily hook our local buffers to the irp
|
||||
irp->write.bytes = write_buffer_local;
|
||||
irp->read.bytes = read_buffer_local;
|
||||
|
||||
// Move data from the shared buffers to the local one
|
||||
// Probably the "most atomic way possible" to have the least amount of
|
||||
// overlap with the game's shared buffer
|
||||
memcpy(write_buffer_local, write_buffer_orig, irp->write.nbytes);
|
||||
memcpy(read_buffer_local, read_buffer_orig, irp->read.nbytes);
|
||||
|
||||
/* For debugging */
|
||||
#ifdef EZUSB_EMU_DEBUG_DUMP
|
||||
/* For debugging */
|
||||
ezusb_emu_util_log_usb_msg(
|
||||
"BEFORE",
|
||||
irp->ioctl,
|
||||
irp->read.bytes,
|
||||
irp->read.nbytes,
|
||||
irp->read.bytes,
|
||||
irp->read.nbytes,
|
||||
irp->write.bytes,
|
||||
irp->write.nbytes);
|
||||
read_buffered.bytes,
|
||||
read_buffered.nbytes,
|
||||
read_buffered.bytes,
|
||||
read_buffered.nbytes,
|
||||
write_buffered.bytes,
|
||||
write_buffered.nbytes);
|
||||
#endif
|
||||
|
||||
/* Cases are listed in order of first receipt */
|
||||
switch (irp->ioctl) {
|
||||
case IOCTL_Ezusb_GET_DEVICE_DESCRIPTOR:
|
||||
return ezusb_get_device_descriptor(irp);
|
||||
res = ezusb_get_device_descriptor(irp);
|
||||
break;
|
||||
|
||||
case IOCTL_Ezusb_VENDOR_REQUEST:
|
||||
return ezusb_vendor_req(irp);
|
||||
res = ezusb_vendor_req(irp);
|
||||
break;
|
||||
|
||||
case IOCTL_EZUSB_ANCHOR_DOWNLOAD:
|
||||
return ezusb_upload_fw(irp);
|
||||
res = ezusb_upload_fw(irp);
|
||||
break;
|
||||
|
||||
case IOCTL_EZUSB_BULK_READ:
|
||||
/* Misnomer: can be bulk or interrupt. */
|
||||
return ezusb_pipe_read(irp);
|
||||
res = ezusb_pipe_read(irp);
|
||||
break;
|
||||
|
||||
case IOCTL_EZUSB_BULK_WRITE:
|
||||
/* Ditto. */
|
||||
return ezusb_pipe_write(irp);
|
||||
res = ezusb_pipe_write(irp);
|
||||
break;
|
||||
|
||||
default:
|
||||
log_warning("Unknown ioctl %08x", irp->ioctl);
|
||||
|
||||
return E_INVALIDARG;
|
||||
res = E_INVALIDARG;
|
||||
}
|
||||
|
||||
#ifdef EZUSB_EMU_DEBUG_DUMP
|
||||
@ -159,13 +234,24 @@ static HRESULT ezusb_ioctl(struct irp *irp)
|
||||
ezusb_emu_util_log_usb_msg(
|
||||
"AFTER",
|
||||
irp->ioctl,
|
||||
irp->read.bytes,
|
||||
irp->read.nbytes,
|
||||
irp->read.bytes,
|
||||
irp->read.nbytes,
|
||||
irp->write.bytes,
|
||||
irp->write.nbytes);
|
||||
read_buffered.bytes,
|
||||
read_buffered.nbytes,
|
||||
read_buffered.bytes,
|
||||
read_buffered.nbytes,
|
||||
write_buffered.bytes,
|
||||
write_buffered.nbytes);
|
||||
#endif
|
||||
|
||||
// Move data back to shared IO buffer. Again, keep this keeps the access
|
||||
// overlap as minimal as possible
|
||||
memcpy(write_buffer_orig, write_buffer_local, irp->write.nbytes);
|
||||
memcpy(read_buffer_orig, read_buffer_local, irp->read.nbytes);
|
||||
|
||||
// Re-store the original irp buffer state
|
||||
irp->write.bytes = (const uint8_t*) write_buffer_orig;
|
||||
irp->read.bytes = read_buffer_orig;
|
||||
|
||||
return res;
|
||||
}
|
||||
/*
|
||||
* USB TRANSFER LAYER
|
||||
|
Loading…
x
Reference in New Issue
Block a user