Files
ARMSX2/pcsx2/USB/USB.cpp
T

651 lines
16 KiB
C++

/* USBlinuz
* Copyright (C) 2002-2004 USBlinuz Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdexcept>
#include <cstdlib>
#include <string>
#include <cerrno>
#include <cassert>
#include "version.h" //CMake generated
#include "USB.h"
#include "platcompat.h"
#include "osdebugout.h"
#include "qemu-usb/USBinternal.h"
#include "qemu-usb/desc.h"
#include "shared/shared.h"
#include "deviceproxy.h"
#define PSXCLK 36864000 /* 36.864 Mhz */
static char libraryName[256];
OHCIState *qemu_ohci = NULL;
USBDevice *usb_device[2] = { NULL };
bool configChanged = false;
Config conf;
char USBfreezeID[] = "USBqemuW01";
typedef struct {
char freezeID[11];
s64 cycles;
s64 remaining;
OHCIState t;
struct {
DeviceType index;
u32 size;
USBDevice dev;
} device[2];
struct usb_packet {
USBEndpoint ep; //usb packet endpoint
int dev_index;
int data_size;
} usb_packet;
} USBfreezeData;
u8 *ram = 0;
FILE *usbLog;
int64_t usb_frame_time;
int64_t usb_bit_time;
s64 clocks = 0;
s64 remaining = 0;
#if _WIN32
HWND gsWnd = nullptr;
#else
#include "gtk.h"
#include <gdk/gdkx.h>
#include <X11/X.h>
Display *g_GSdsp;
Window g_GSwin;
#endif
Config::Config(): Log(0)
{
memset(&WheelType, 0, sizeof(WheelType));
}
void __Log(const char *fmt, ...) {
va_list list;
if (!conf.Log ||!usbLog) return;
va_start(list, fmt);
vfprintf(usbLog, fmt, list);
va_end(list);
}
//Simpler to reset and reattach after USBclose/USBopen
void Reset()
{
if(qemu_ohci)
ohci_hard_reset(qemu_ohci);
}
void DestroyDevices()
{
for (int i=0; i<2; i++)
{
if(qemu_ohci && qemu_ohci->rhport[i].port.dev) {
qemu_ohci->rhport[i].port.dev->klass.unrealize(qemu_ohci->rhport[i].port.dev);
qemu_ohci->rhport[i].port.dev = nullptr;
}
else if(usb_device[i])
usb_device[i]->klass.unrealize(usb_device[i]);
usb_device[i] = nullptr;
}
}
USBDevice* CreateDevice(DeviceType index, int port)
{
DeviceProxyBase *devProxy;
USBDevice* device = nullptr;
if (index == DEVTYPE_NONE)
return nullptr;
devProxy = RegisterDevice::instance().Device(index);
if (devProxy)
device = devProxy->CreateDevice(port);
else
SysMessage(TEXT("Device %d: Unknown device type"), 1 - port);
if (!device) {
USB_LOG("USBqemu: failed to create device type %d on port %d\n", index, port);
}
return device;
}
//TODO re-do sneaky attach
void USBAttach(int port, USBDevice *dev, bool sneaky = false)
{
if (!qemu_ohci) return;
USBDevice *tmp = qemu_ohci->rhport[port].port.dev;
if (tmp)
{
if (!sneaky)
usb_detach (&qemu_ohci->rhport[port].port);
tmp->klass.unrealize(tmp);
}
qemu_ohci->rhport[port].port.dev = dev;
if (dev)
{
dev->attached = true;
usb_attach (&qemu_ohci->rhport[port].port); //.ops->attach(&(qemu_ohci->rhport[port].port));
}
}
USBDevice* CreateDevice(const std::string& name, int port)
{
DeviceProxyBase *devProxy;
USBDevice* device = nullptr;
if (!name.empty())
{
devProxy = RegisterDevice::instance().Device(name);
if (devProxy)
device = devProxy->CreateDevice(port);
else
SysMessage(TEXT("Port %d: Unknown device type"), port);
}
if (!device) {
USB_LOG("USBqemu: failed to create device '%s' on port %d\n", name.c_str(), port);
}
return device;
}
void CreateDevices()
{
if(!qemu_ohci) return; //No USBinit yet ie. called from config. dialog
DestroyDevices();
for (int i=0; i<2; i++)
{
usb_device[i] = CreateDevice(conf.Port[i], i);
USBAttach(i, usb_device[i]);
}
}
s32 USBinit() {
OSDebugOut(TEXT("USBinit\n"));
RegisterDevice::Register();
LoadConfig();
if (conf.Log && !usbLog)
{
usbLog =
#if _UNICODE
_wfopen(LogDir.c_str(), L"wb");// L"wb,ccs=UNICODE");
#else
fopen(LogDir.c_str(), "w");
#endif
//if(usbLog) setvbuf(usbLog, NULL, _IONBF, 0);
USB_LOG("usbqemu wheel mod plugin version %d.%d.%d\n", VER_REV, VER_BLD, VER_FIX);
USB_LOG("USBinit\n");
}
qemu_ohci = ohci_create(0x1f801600,2);
if(!qemu_ohci) return 1;
clocks = 0;
remaining = 0;
return 0;
}
void USBshutdown() {
OSDebugOut(TEXT("USBshutdown\n"));
DestroyDevices();
RegisterDevice::instance().Unregister();
free(qemu_ohci);
ram = 0;
//#ifdef _DEBUG
if (conf.Log && usbLog) {
fclose(usbLog);
usbLog = nullptr;
}
//#endif
}
s32 USBopen(void *pDsp) {
if (conf.Log && !usbLog)
{
usbLog = fopen("logs/usbLog.txt", "a");
//if(usbLog) setvbuf(usbLog, NULL, _IONBF, 0);
USB_LOG("usbqemu wheel mod plugin version %d.%d.%d\n", VER_REV, VER_BLD, VER_FIX);
}
USB_LOG("USBopen\n");
OSDebugOut(TEXT("USBopen\n"));
#if _WIN32
HWND hWnd=(HWND)pDsp;
//HWND hWnd=(HWND)((uptr*)pDsp)[0];
if (!IsWindow (hWnd))
hWnd = *(HWND*)hWnd;
if (!IsWindow (hWnd))
hWnd = NULL;
else
{
while (GetWindowLong (hWnd, GWL_STYLE) & WS_CHILD)
hWnd = GetParent (hWnd);
}
gsWnd = hWnd;
pDsp = gsWnd;
#else
g_GSdsp = (Display *)((uptr*)pDsp)[0];
g_GSwin = (Window)((uptr*)pDsp)[1];
OSDebugOut("X11 display %p Xwindow %lu\n", g_GSdsp, g_GSwin);
#endif
try {
shared::Initialize(pDsp);
} catch (std::runtime_error &e) {
SysMessage(TEXT("%" SFMTs "\n"), e.what());
}
if (configChanged || (!usb_device[0] && !usb_device[1]))
{
configChanged = false;
CreateDevices(); //TODO Pass pDsp to init?
}
//TODO Pass pDsp to open probably so dinput can bind to this HWND
if(usb_device[0] && usb_device[0]->klass.open)
usb_device[0]->klass.open(usb_device[0]/*, pDsp*/);
if(usb_device[1] && usb_device[1]->klass.open)
usb_device[1]->klass.open(usb_device[1]/*, pDsp*/);
return 0;
}
void USBclose() {
OSDebugOut(TEXT("USBclose\n"));
if(usb_device[0] && usb_device[0]->klass.close)
usb_device[0]->klass.close(usb_device[0]);
if(usb_device[1] && usb_device[1]->klass.close)
usb_device[1]->klass.close(usb_device[1]);
shared::Uninitialize();
}
u8 USBread8(u32 addr) {
USB_LOG("* Invalid 8bit read at address %lx\n", addr);
return 0;
}
u16 USBread16(u32 addr) {
USB_LOG("* Invalid 16bit read at address %lx\n", addr);
return 0;
}
u32 USBread32(u32 addr) {
u32 hard;
hard=ohci_mem_read(qemu_ohci,addr);
USB_LOG("* Known 32bit read at address %lx: %lx\n", addr, hard);
return hard;
}
void USBwrite8(u32 addr, u8 value) {
USB_LOG("* Invalid 8bit write at address %lx value %x\n", addr, value);
}
void USBwrite16(u32 addr, u16 value) {
USB_LOG("* Invalid 16bit write at address %lx value %x\n", addr, value);
}
void USBwrite32(u32 addr, u32 value) {
USB_LOG("* Known 32bit write at address %lx value %lx\n", addr, value);
ohci_mem_write(qemu_ohci,addr,value);
}
extern u32 bits;
void USBsetRAM(void *mem) {
ram = (u8*)mem;
Reset();
}
s32 USBfreeze(int mode, freezeData *data) {
USBfreezeData usbd = { 0 };
//TODO FREEZE_SIZE mismatch causes loading to fail in PCSX2 beforehand
if (mode == FREEZE_LOAD)
{
if(data->size < sizeof(USBfreezeData))
{
SysMessage(TEXT("ERROR: Unable to load freeze data! Got %d bytes, expected >= %d.\n"), data->size, sizeof(USBfreezeData));
return -1;
}
usbd = *(USBfreezeData*)data->data;
usbd.freezeID[10] = 0;
if( strcmp(usbd.freezeID, USBfreezeID) != 0)
{
SysMessage(TEXT("ERROR: Unable to load freeze data! Found ID '%") TEXT(SFMTs) TEXT("', expected ID '%") TEXT(SFMTs) TEXT("'.\n"), usbd.freezeID, USBfreezeID);
return -1;
}
//TODO Subsequent save state loadings make USB "stall" for n seconds since previous load
//clocks = usbd.cycles;
//remaining = usbd.remaining;
for(int i=0; i< qemu_ohci->num_ports; i++)
{
usbd.t.rhport[i].port.opaque = qemu_ohci;
usbd.t.rhport[i].port.ops = qemu_ohci->rhport[i].port.ops;
usbd.t.rhport[i].port.dev = qemu_ohci->rhport[i].port.dev;
}
*qemu_ohci = usbd.t;
s8 *ptr = data->data + sizeof(USBfreezeData);
// Load the state of the attached devices
if (data->size != sizeof(USBfreezeData) + usbd.device[0].size + usbd.device[1].size + 8192)
return -1;
RegisterDevice& regInst = RegisterDevice::instance();
for (int i=0; i<2; i++)
{
auto index = regInst.Index(conf.Port[i]);
auto proxy = regInst.Device(index);
//TODO FREEZE_SIZE mismatch causes loading to fail in PCSX2 beforehand
// but just in case, recreate the same device type as was saved
if (usbd.device[i].index != index)
{
index = usbd.device[i].index;
USBDevice *dev = qemu_ohci->rhport[i].port.dev;
qemu_ohci->rhport[i].port.dev = nullptr;
if (dev)
{
assert(usb_device[i] == dev);
dev->klass.unrealize(dev);
}
proxy = regInst.Device(index);
usb_device[i] = CreateDevice(index, i);
USBAttach(i, usb_device[i], index != DEVTYPE_MSD);
}
if (proxy && usb_device[i]) /* usb device creation may have failed for some reason */
{
if (proxy->Freeze(FREEZE_SIZE, usb_device[i], nullptr) != usbd.device[i].size)
{
SysMessage(TEXT("Port %d: device's freeze size doesn't match.\n"), 1+(1-i));
return -1;
}
const USBDevice& tmp = usbd.device[i].dev;
usb_device[i]->addr = tmp.addr;
usb_device[i]->attached = tmp.attached;
usb_device[i]->auto_attach = tmp.auto_attach;
usb_device[i]->configuration = tmp.configuration;
usb_device[i]->ninterfaces = tmp.ninterfaces;
usb_device[i]->flags = tmp.flags;
usb_device[i]->state = tmp.state;
usb_device[i]->remote_wakeup = tmp.remote_wakeup;
usb_device[i]->setup_state = tmp.setup_state;
usb_device[i]->setup_len = tmp.setup_len;
usb_device[i]->setup_index = tmp.setup_index;
memcpy(usb_device[i]->data_buf, tmp.data_buf, sizeof(tmp.data_buf));
memcpy(usb_device[i]->setup_buf, tmp.setup_buf, sizeof(tmp.setup_buf));
usb_desc_set_config(usb_device[i], tmp.configuration);
for (int k = 0; k < 16; k++) {
usb_device[i]->altsetting[k] = tmp.altsetting[k];
usb_desc_set_interface(usb_device[i], k, tmp.altsetting[k]);
}
proxy->Freeze(FREEZE_LOAD, usb_device[i], ptr);
//TODO reset port if save state's and configured wheel types are different
usb_detach (&qemu_ohci->rhport[i].port);
usb_attach (&qemu_ohci->rhport[i].port);
}
else if (!proxy && index != DEVTYPE_NONE)
{
SysMessage(TEXT("Port %d: unknown device.\nPlugin is probably too old for this save.\n"), 1+(1-i));
return -1;
}
ptr += usbd.device[i].size;
}
int dev_index = usbd.usb_packet.dev_index;
// restore USBPacket for OHCIState
//if (qemu_ohci->usb_packet.iov.iov)
usb_packet_cleanup(&qemu_ohci->usb_packet);
usb_packet_init(&qemu_ohci->usb_packet);
if (usb_device[dev_index])
{
USBPacket *p = &qemu_ohci->usb_packet;
p->actual_length = usbd.usb_packet.data_size;
QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov;
iov_from_buf(iov->iov, iov->niov, 0, ptr, p->actual_length);
if (usbd.usb_packet.ep.pid == USB_TOKEN_SETUP)
{
if (usb_device[dev_index]->ep_ctl.ifnum == usbd.usb_packet.ep.ifnum)
qemu_ohci->usb_packet.ep = &usb_device[dev_index]->ep_ctl;
}
else
{
USBEndpoint *eps = nullptr;
if (usbd.usb_packet.ep.pid == USB_TOKEN_IN)
eps = usb_device[dev_index]->ep_in;
else //if (usbd.ep.pid == USB_TOKEN_OUT)
eps = usb_device[dev_index]->ep_out;
for (int k = 0; k < USB_MAX_ENDPOINTS; k++) {
if (usbd.usb_packet.ep.type == eps[k].type
&& usbd.usb_packet.ep.nr == eps[k].nr
&& usbd.usb_packet.ep.ifnum == eps[k].ifnum
&& usbd.usb_packet.ep.pid == eps[k].pid)
{
qemu_ohci->usb_packet.ep = &eps[k];
break;
}
}
}
}
else
{
SysMessage(TEXT("USB packet has invalid device index? %d\n"), dev_index); // or just a bug
return -1;
}
}
//TODO straight copying of structs can break cross-platform/cross-compiler save states 'cause padding 'n' stuff
else if (mode == FREEZE_SAVE)
{
memset(data->data, 0, data->size);//maybe it already is...
RegisterDevice& regInst = RegisterDevice::instance();
usbd.usb_packet.dev_index = -1;
for (int i=0; i<2; i++)
{
//TODO check that current created usb device and conf.Port[n] are the same
auto index = regInst.Index(conf.Port[i]);
auto proxy = regInst.Device(index);
usbd.device[i].index = index;
if (proxy)
usbd.device[i].size = proxy->Freeze(FREEZE_SIZE, usb_device[i], nullptr);
else
usbd.device[i].size = 0;
if (qemu_ohci->usb_packet.ep && qemu_ohci->usb_packet.ep->dev == usb_device[i])
usbd.usb_packet.dev_index = i;
}
strncpy(usbd.freezeID, USBfreezeID, strlen(USBfreezeID));
usbd.t = *qemu_ohci;
usbd.usb_packet.ep = qemu_ohci->usb_packet.ep ? *qemu_ohci->usb_packet.ep : USBEndpoint{0};
usbd.t.usb_packet.iov = { };
usbd.t.usb_packet.ep = nullptr;
for(int i=0; i< qemu_ohci->num_ports; i++)
{
usbd.t.rhport[i].port.opaque = nullptr;
usbd.t.rhport[i].port.ops = nullptr;
usbd.t.rhport[i].port.dev = nullptr;
}
usbd.cycles = clocks;
usbd.remaining = remaining;
s8 *ptr = data->data + sizeof(USBfreezeData);
// Save the state of the attached devices
for (int i=0; i<2; i++)
{
auto proxy = regInst.Device(conf.Port[i]);
if (proxy && usbd.device[i].size)
{
proxy->Freeze(FREEZE_SAVE, usb_device[i], ptr);
if (usb_device[i])
usbd.device[i].dev = *usb_device[i];
memset (&usbd.device[i].dev.klass, 0, sizeof(USBDeviceClass));
}
ptr += usbd.device[i].size;
}
USBPacket *p = &qemu_ohci->usb_packet;
usbd.usb_packet.data_size = p->actual_length;
QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov;
iov_to_buf(iov->iov, iov->niov, 0, ptr, p->actual_length);
*(USBfreezeData*)data->data = usbd;
}
else if (mode == FREEZE_SIZE)
{
RegisterDevice& regInst = RegisterDevice::instance();
data->size = sizeof(USBfreezeData);
for (int i=0; i<2; i++)
{
//TODO check that current created usb device and conf.Port[n] are the same
auto proxy = regInst.Device(conf.Port[i]);
if (proxy)
data->size += proxy->Freeze(FREEZE_SIZE, usb_device[i], nullptr);
}
// PCSX2 queries size before load too, so can't use actual packet length which varies :(
data->size += 8192;// qemu_ohci->usb_packet.actual_length;
if (qemu_ohci->usb_packet.actual_length > 8192) {
fprintf(stderr, "Saving failed! USB packet is larger than 8K, try again later.\n");
return -1;
}
}
return 0;
}
void USBasync(u32 cycles)
{
remaining += cycles;
clocks += remaining;
if(qemu_ohci->eof_timer>0)
{
while(remaining>=qemu_ohci->eof_timer)
{
remaining-=qemu_ohci->eof_timer;
qemu_ohci->eof_timer=0;
ohci_frame_boundary(qemu_ohci);
/*
* Break out of the loop if bus was stopped.
* If ohci_frame_boundary hits an UE, but doesn't stop processing,
* it seems to cause a hang inside the game instead.
*/
if (!qemu_ohci->eof_timer)
break;
}
if((remaining>0)&&(qemu_ohci->eof_timer>0))
{
s64 m = qemu_ohci->eof_timer;
if(remaining < m)
m = remaining;
qemu_ohci->eof_timer -= m;
remaining -= m;
}
}
//if(qemu_ohci->eof_timer <= 0)
//{
//ohci_frame_boundary(qemu_ohci);
//}
}
int cpu_physical_memory_rw(u32 addr, u8 *buf, size_t len, int is_write)
{
//OSDebugOut(TEXT("%s addr %08X, len %d\n"), is_write ? TEXT("write") : TEXT("read "), addr, len);
// invalid address, reset and try again
if (addr+len >= 0x200000)
{
OSDebugOut(TEXT("invalid address, soft resetting ohci.\n"));
if (qemu_ohci)
ohci_soft_reset(qemu_ohci);
return 1;
}
if(is_write)
memcpy(&(ram[addr]),buf,len);
else
memcpy(buf,&(ram[addr]),len);
return 0;
}
int get_ticks_per_second()
{
return PSXCLK;
}
s64 get_clock()
{
return clocks;
}