diff --git a/hw/xbox/xid-sdl.c b/hw/xbox/xid-sdl.c
new file mode 100644
index 0000000000..8fba2e2427
--- /dev/null
+++ b/hw/xbox/xid-sdl.c
@@ -0,0 +1,527 @@
+/*
+ * QEMU USB XID Devices
+ *
+ * Copyright (c) 2013 espes
+ * Copyright (c) 2017 Jannik Vogel
+ * Copyright (c) 2018 Matt Borgerson
+ *
+ * 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 or
+ * (at your option) version 3 of the License.
+ *
+ * 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, see .
+ */
+
+#include "qemu/osdep.h"
+#include "hw/hw.h"
+#include "ui/console.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "ui/input.h"
+
+#include
+
+//#define FORCE_FEEDBACK
+#define UPDATE
+
+//#define DEBUG_XID
+#ifdef DEBUG_XID
+#define DPRINTF printf
+#else
+#define DPRINTF(...)
+#endif
+
+/*
+ * http://xbox-linux.cvs.sourceforge.net/viewvc/xbox-linux/kernel-2.6/drivers/usb/input/xpad.c
+ * http://euc.jp/periphs/xbox-controller.en.html
+ * http://euc.jp/periphs/xbox-pad-desc.txt
+ */
+
+#define USB_CLASS_XID 0x58
+#define USB_DT_XID 0x42
+
+#define HID_GET_REPORT 0x01
+#define HID_SET_REPORT 0x09
+#define XID_GET_CAPABILITIES 0x01
+
+#define TYPE_USB_XID_SDL "usb-xbox-gamepad-sdl"
+#define USB_XID(obj) OBJECT_CHECK(USBXIDState, (obj), TYPE_USB_XID_SDL)
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT,
+ STR_SERIALNUMBER,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "QEMU",
+ [STR_PRODUCT] = "Microsoft Gamepad",
+ [STR_SERIALNUMBER] = "1",
+};
+
+typedef struct XIDDesc {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint16_t bcdXid;
+ uint8_t bType;
+ uint8_t bSubType;
+ uint8_t bMaxInputReportSize;
+ uint8_t bMaxOutputReportSize;
+ uint16_t wAlternateProductIds[4];
+} QEMU_PACKED XIDDesc;
+
+typedef struct XIDGamepadReport {
+ uint8_t bReportId;
+ uint8_t bLength;
+ uint16_t wButtons;
+ uint8_t bAnalogButtons[8];
+ int16_t sThumbLX;
+ int16_t sThumbLY;
+ int16_t sThumbRX;
+ int16_t sThumbRY;
+} QEMU_PACKED XIDGamepadReport;
+
+typedef struct XIDGamepadOutputReport {
+ uint8_t report_id; //FIXME: is this correct?
+ uint8_t length;
+ uint16_t left_actuator_strength;
+ uint16_t right_actuator_strength;
+} QEMU_PACKED XIDGamepadOutputReport;
+
+typedef struct USBXIDState {
+ USBDevice dev;
+ USBEndpoint *intr;
+ const XIDDesc *xid_desc;
+ XIDGamepadReport in_state;
+ XIDGamepadOutputReport out_state;
+
+ uint8_t device_index;
+ SDL_GameController *sdl_gamepad;
+#ifdef FORCE_FEEDBACK
+ SDL_Haptic *sdl_haptic;
+ int sdl_haptic_effect_id;
+#endif
+} USBXIDState;
+
+static const USBDescIface desc_iface_xbox_gamepad = {
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_XID,
+ .bInterfaceSubClass = 0x42,
+ .bInterfaceProtocol = 0x00,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | 0x02,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 0x20,
+ .bInterval = 4,
+ },
+ {
+ .bEndpointAddress = USB_DIR_OUT | 0x02,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = 0x20,
+ .bInterval = 4,
+ },
+ },
+};
+
+static const USBDescDevice desc_device_xbox_gamepad = {
+ .bcdUSB = 0x0110,
+ .bMaxPacketSize0 = 0x40,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CFG_ATT_ONE,
+ .bMaxPower = 50,
+ .nif = 1,
+ .ifs = &desc_iface_xbox_gamepad,
+ },
+ },
+};
+
+static const USBDesc desc_xbox_gamepad = {
+ .id = {
+ .idVendor = 0x045e,
+ .idProduct = 0x0202,
+ .bcdDevice = 0x0100,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .full = &desc_device_xbox_gamepad,
+ .str = desc_strings,
+};
+
+static const XIDDesc desc_xid_xbox_gamepad = {
+ .bLength = 0x10,
+ .bDescriptorType = USB_DT_XID,
+ .bcdXid = 1,
+ .bType = 1,
+ .bSubType = 1,
+ .bMaxInputReportSize = 0x20,
+ .bMaxOutputReportSize = 0x6,
+ .wAlternateProductIds = {-1, -1, -1, -1},
+};
+
+#define GAMEPAD_A 0
+#define GAMEPAD_B 1
+#define GAMEPAD_X 2
+#define GAMEPAD_Y 3
+#define GAMEPAD_BLACK 4
+#define GAMEPAD_WHITE 5
+#define GAMEPAD_LEFT_TRIGGER 6
+#define GAMEPAD_RIGHT_TRIGGER 7
+
+#define GAMEPAD_DPAD_UP 8
+#define GAMEPAD_DPAD_DOWN 9
+#define GAMEPAD_DPAD_LEFT 10
+#define GAMEPAD_DPAD_RIGHT 11
+#define GAMEPAD_START 12
+#define GAMEPAD_BACK 13
+#define GAMEPAD_LEFT_THUMB 14
+#define GAMEPAD_RIGHT_THUMB 15
+
+#define BUTTON_MASK(button) (1 << ((button) - GAMEPAD_DPAD_UP))
+
+static void update_output(USBXIDState *s) {
+#ifdef FORCE_FEEDBACK
+ if (s->sdl_haptic == NULL) { return; }
+
+ SDL_HapticLeftRight effect = {
+ .type = SDL_HAPTIC_LEFTRIGHT,
+ .length = SDL_HAPTIC_INFINITY,
+ /* FIXME: Might be left/right inverted */
+ .large_magnitude = s->out_state.right_actuator_strength,
+ .small_magnitude = s->out_state.left_actuator_strength
+ };
+
+ if (s->sdl_haptic_effect_id == -1) {
+ int effect_id = SDL_HapticNewEffect(s->sdl_haptic,
+ (SDL_HapticEffect*)&effect);
+ if (effect_id == -1) {
+ fprintf(stderr, "Failed to upload haptic effect!\n");
+ SDL_HapticClose(s->sdl_haptic);
+ s->sdl_haptic = NULL;
+ return;
+ }
+ SDL_HapticRunEffect(s->sdl_haptic, effect_id, 1);
+
+ s->sdl_haptic_effect_id = effect_id;
+
+ } else {
+ SDL_HapticUpdateEffect(s->sdl_haptic, s->sdl_haptic_effect_id,
+ (SDL_HapticEffect*)&effect);
+ }
+#endif
+}
+
+static void update_input(USBXIDState *s)
+{
+ int i, state;
+
+#ifdef UPDATE
+ SDL_GameControllerUpdate();
+#endif
+
+ /* Buttons */
+ const int button_map_analog[6][2] = {
+ { GAMEPAD_A, SDL_CONTROLLER_BUTTON_A },
+ { GAMEPAD_B, SDL_CONTROLLER_BUTTON_B },
+ { GAMEPAD_X, SDL_CONTROLLER_BUTTON_X },
+ { GAMEPAD_Y, SDL_CONTROLLER_BUTTON_Y },
+ { GAMEPAD_BLACK, SDL_CONTROLLER_BUTTON_LEFTSHOULDER },
+ { GAMEPAD_WHITE, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER },
+ };
+
+ const int button_map_binary[8][2] = {
+ { GAMEPAD_BACK, SDL_CONTROLLER_BUTTON_BACK },
+ { GAMEPAD_START, SDL_CONTROLLER_BUTTON_START },
+ { GAMEPAD_LEFT_THUMB, SDL_CONTROLLER_BUTTON_LEFTSTICK },
+ { GAMEPAD_RIGHT_THUMB, SDL_CONTROLLER_BUTTON_RIGHTSTICK },
+ { GAMEPAD_DPAD_UP, SDL_CONTROLLER_BUTTON_DPAD_UP },
+ { GAMEPAD_DPAD_DOWN, SDL_CONTROLLER_BUTTON_DPAD_DOWN },
+ { GAMEPAD_DPAD_LEFT, SDL_CONTROLLER_BUTTON_DPAD_LEFT },
+ { GAMEPAD_DPAD_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_RIGHT },
+ };
+
+ for (i = 0; i < 6; i++) {
+ state = SDL_GameControllerGetButton(s->sdl_gamepad,
+ button_map_analog[i][1]);
+ s->in_state.bAnalogButtons[button_map_analog[i][0]] = state ? 0xff : 0;
+ }
+
+ s->in_state.wButtons = 0;
+ for (i = 0; i < 8; i++) {
+ state = SDL_GameControllerGetButton(s->sdl_gamepad,
+ button_map_binary[i][1]);
+ if (state) {
+ s->in_state.wButtons |= BUTTON_MASK(button_map_binary[i][0]);
+ }
+ }
+
+ /* Triggers */
+ state = SDL_GameControllerGetAxis(s->sdl_gamepad,
+ SDL_CONTROLLER_AXIS_TRIGGERLEFT);
+ s->in_state.bAnalogButtons[GAMEPAD_LEFT_TRIGGER] = state >> 8;
+
+ state = SDL_GameControllerGetAxis(s->sdl_gamepad,
+ SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
+ s->in_state.bAnalogButtons[GAMEPAD_RIGHT_TRIGGER] = state >> 8;
+
+ /* Analog sticks */
+ s->in_state.sThumbLX = SDL_GameControllerGetAxis(s->sdl_gamepad,
+ SDL_CONTROLLER_AXIS_LEFTX);
+
+ s->in_state.sThumbLY = -SDL_GameControllerGetAxis(s->sdl_gamepad,
+ SDL_CONTROLLER_AXIS_LEFTY) - 1;
+
+ s->in_state.sThumbRX = SDL_GameControllerGetAxis(s->sdl_gamepad,
+ SDL_CONTROLLER_AXIS_RIGHTX);
+
+ s->in_state.sThumbRY = -SDL_GameControllerGetAxis(s->sdl_gamepad,
+ SDL_CONTROLLER_AXIS_RIGHTY) - 1;
+}
+
+static void usb_xid_handle_reset(USBDevice *dev)
+{
+ DPRINTF("xid reset\n");
+}
+
+static void usb_xid_handle_control(USBDevice *dev, USBPacket *p,
+ int request, int value, int index, int length, uint8_t *data)
+{
+ USBXIDState *s = (USBXIDState *)dev;
+
+ DPRINTF("xid handle_control 0x%x 0x%x\n", request, value);
+
+ int ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0) {
+ DPRINTF("xid handled by usb_desc_handle_control: %d\n", ret);
+ return;
+ }
+
+ switch (request) {
+ /* HID requests */
+ case ClassInterfaceRequest | HID_GET_REPORT:
+ DPRINTF("xid GET_REPORT 0x%x\n", value);
+ if (value == 0x100) { /* input */
+ update_input(s);
+ assert(s->in_state.bLength <= length);
+// s->in_state.bReportId++; /* FIXME: I'm not sure if bReportId is just a counter */
+ memcpy(data, &s->in_state, s->in_state.bLength);
+ p->actual_length = s->in_state.bLength;
+ } else {
+ assert(false);
+ }
+ break;
+ case ClassInterfaceOutRequest | HID_SET_REPORT:
+ DPRINTF("xid SET_REPORT 0x%x\n", value);
+ if (value == 0x200) { /* output */
+ /* Read length, then the entire packet */
+ memcpy(&s->out_state, data, sizeof(s->out_state));
+ assert(s->out_state.length == sizeof(s->out_state));
+ assert(s->out_state.length <= length);
+ //FIXME: Check actuator endianess
+ DPRINTF("Set rumble power to 0x%x, 0x%x\n",
+ s->out_state.left_actuator_strength,
+ s->out_state.right_actuator_strength);
+ update_output(s);
+ p->actual_length = s->out_state.length;
+ } else {
+ assert(false);
+ }
+ break;
+ /* XID requests */
+ case VendorInterfaceRequest | USB_REQ_GET_DESCRIPTOR:
+ DPRINTF("xid GET_DESCRIPTOR 0x%x\n", value);
+ if (value == 0x4200) {
+ assert(s->xid_desc->bLength <= length);
+ memcpy(data, s->xid_desc, s->xid_desc->bLength);
+ p->actual_length = s->xid_desc->bLength;
+ } else {
+ assert(false);
+ }
+ break;
+ case VendorInterfaceRequest | XID_GET_CAPABILITIES:
+ DPRINTF("xid XID_GET_CAPABILITIES 0x%x\n", value);
+ /* FIXME: ! */
+ p->status = USB_RET_STALL;
+ //assert(false);
+ break;
+ case ((USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_DEVICE)<<8)
+ | USB_REQ_GET_DESCRIPTOR:
+ /* FIXME: ! */
+ DPRINTF("xid unknown xpad request 0x%x: value = 0x%x\n",
+ request, value);
+ memset(data, 0x00, length);
+ //FIXME: Intended for the hub: usbd_get_hub_descriptor, UT_READ_CLASS?!
+ p->status = USB_RET_STALL;
+ //assert(false);
+ break;
+ case ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8)
+ | USB_REQ_CLEAR_FEATURE:
+ /* FIXME: ! */
+ DPRINTF("xid unknown xpad request 0x%x: value = 0x%x\n",
+ request, value);
+ memset(data, 0x00, length);
+ p->status = USB_RET_STALL;
+ break;
+ default:
+ DPRINTF("xid USB stalled on request 0x%x value 0x%x\n", request, value);
+ p->status = USB_RET_STALL;
+ assert(false);
+ break;
+ }
+}
+
+static void usb_xid_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBXIDState *s = DO_UPCAST(USBXIDState, dev, dev);
+
+ DPRINTF("xid handle_data 0x%x %d 0x%zx\n", p->pid, p->ep->nr, p->iov.size);
+
+ switch (p->pid) {
+ case USB_TOKEN_IN:
+ if (p->ep->nr == 2) {
+ update_input(s);
+ usb_packet_copy(p, &s->in_state, s->in_state.bLength);
+ } else {
+ assert(false);
+ }
+ break;
+ case USB_TOKEN_OUT:
+ p->status = USB_RET_STALL;
+ break;
+ default:
+ p->status = USB_RET_STALL;
+ assert(false);
+ break;
+ }
+}
+
+#if 0
+static void usb_xid_handle_destroy(USBDevice *dev)
+{
+ USBXIDState *s = DO_UPCAST(USBXIDState, dev, dev);
+ DPRINTF("xid handle_destroy\n");
+#ifdef FORCE_FEEDBACK
+ if (s->sdl_haptic) {
+ SDL_HapticClose(s->sdl_haptic);
+ }
+#endif
+ SDL_JoystickClose(s->sdl_gamepad);
+}
+#endif
+
+static void usb_xbox_gamepad_unrealize(USBDevice *dev, Error **errp)
+{
+}
+
+static void usb_xid_class_initfn(ObjectClass *klass, void *data)
+{
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->handle_reset = usb_xid_handle_reset;
+ uc->handle_control = usb_xid_handle_control;
+ uc->handle_data = usb_xid_handle_data;
+ // uc->handle_destroy = usb_xid_handle_destroy;
+ uc->handle_attach = usb_desc_attach;
+}
+
+static void usb_xbox_gamepad_realize(USBDevice *dev, Error **errp)
+{
+ USBXIDState *s = USB_XID(dev);
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+ s->intr = usb_ep_get(dev, USB_TOKEN_IN, 2);
+
+ s->in_state.bLength = sizeof(s->in_state);
+ s->out_state.length = sizeof(s->out_state);
+ s->xid_desc = &desc_xid_xbox_gamepad;
+
+ /* FIXME: Make sure SDL was init before */
+ if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER)) {
+ fprintf(stderr, "SDL failed to initialize joystick subsystem\n");
+ exit(1);
+ }
+
+ s->sdl_gamepad = SDL_GameControllerOpen(s->device_index);
+ if (s->sdl_gamepad == NULL) {
+ /* FIXME: More appropriate qemu error handling */
+ fprintf(stderr, "Couldn't open joystick %d\n", s->device_index);
+ exit(1);
+ }
+
+ const char* name = SDL_GameControllerName(s->sdl_gamepad);
+ printf("Found game controller %d (%s)\n", s->device_index, name);
+
+#ifndef UPDATE
+ /* We could update the joystick in the usb event handlers, but that would
+ * probably pause emulation until data is ready + we'd also hammer SDL with
+ * SDL_JoystickUpdate calls if the games are programmed poorly.
+ */
+ SDL_JoystickEventState(SDL_ENABLE);
+#endif
+
+#ifdef FORCE_FEEDBACK
+ s->sdl_haptic = SDL_HapticOpenFromJoystick(s->sdl_gamepad);
+ if (s->sdl_haptic == NULL) {
+ fprintf(stderr, "Joystick doesn't support haptic\n");
+ } else {
+ if ((SDL_HapticQuery(s->sdl_haptic) & SDL_HAPTIC_LEFTRIGHT) == 0) {
+ fprintf(stderr, "Joystick doesn't support necessary haptic effect\n");
+ SDL_HapticClose(s->sdl_haptic);
+ s->sdl_haptic = NULL;
+ }
+ }
+#endif
+}
+
+static Property xid_sdl_properties[] = {
+ DEFINE_PROP_UINT8("index", USBXIDState, device_index, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_usb_xbox = {
+ .name = TYPE_USB_XID_SDL,
+ .unmigratable = 1,
+};
+
+static void usb_xbox_gamepad_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->product_desc = "Microsoft Xbox Controller";
+ uc->usb_desc = &desc_xbox_gamepad;
+ uc->realize = usb_xbox_gamepad_realize;
+ uc->unrealize = usb_xbox_gamepad_unrealize;
+ usb_xid_class_initfn(klass, data);
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->vmsd = &vmstate_usb_xbox;
+ dc->props = xid_sdl_properties;
+ dc->desc = "Microsoft Xbox Controller";
+}
+
+static const TypeInfo usb_xbox_gamepad_info = {
+ .name = TYPE_USB_XID_SDL,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBXIDState),
+ .class_init = usb_xbox_gamepad_class_initfn,
+};
+
+static void usb_xid_register_types(void)
+{
+ type_register_static(&usb_xbox_gamepad_info);
+}
+
+type_init(usb_xid_register_types)