Jan Kratochvil, who has been recently working on other Xbox360 stuff, would probably be interested too. Added him to CC.
On Tue, 29 May 2007, Brian Magnuson wrote: > Hi, > > This patch implements a driver for the "Microsoft Xbox 360 Wireless Receiver > for Windows" that was recently released. It seems to mostly "just work" for > the basics at this point and I'd like to get some feedback on it since this is > my first attempt at driver development. > > It borrows heavily from xpad.c since it's basically the same controller with > new wireless magic. The difference being that the receiever can host multiple > controllers through the same device and the driver manages them coming and > going. In particular I moved the input device creation/deletion out of the > probe function and into workqueues kicked off by usb interrupts. That would > probably be a good place for prospective reviewers to start finding issues. :) > > Thanks for looking. > > -Brian > > Please CC me on replies since I'm not subscribed. Thanks. > > >From 61e650894787215e5939ad73d100d3fb3ab3ff17 Mon Sep 17 00:00:00 2001 > From: Brian Magnuson <[EMAIL PROTECTED](none)> > Date: Mon, 28 May 2007 23:10:18 -0400 > Subject: [PATCH] New xboxrcvr driver > > > Signed-off-by: Brian Magnuson <[EMAIL PROTECTED]> > --- > drivers/input/joystick/xboxrcvr.c | 463 > +++++++++++++++++++++++++++++++++++++ > drivers/input/joystick/xboxrcvr.h | 72 ++++++ > 2 files changed, 535 insertions(+), 0 deletions(-) > > diff --git a/drivers/input/joystick/xboxrcvr.c > b/drivers/input/joystick/xboxrcvr.c > new file mode 100644 > index 0000000..e132726 > --- /dev/null > +++ b/drivers/input/joystick/xboxrcvr.c > @@ -0,0 +1,463 @@ > +/* > + * Xbox wireless receiver input device driver for Linux - v0.0.1 > + * > + * Copyright (c) 2007 Brian Magnuson <[EMAIL PROTECTED]> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * 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 > + * > + * This driver is based on: > + * - information from http://euc.jp/periphs/xbox-controller.en.html > + * - the iForce driver drivers/char/joystick/iforce.c > + * - the skeleton-driver drivers/usb/usb-skeleton.c > + * - the xpad driver drivers/usb/input/xpad.c > + * > + * Thanks to: > + * - ITO Takayuki for providing essential xpad information on his website > + * - Vojtech Pavlik - iforce driver / input subsystem > + * - Greg Kroah-Hartman - usb-skeleton driver > + * - The xpad.c developers on which this driver is based in large part > + * Vojtech Pavlik <[EMAIL PROTECTED]>, > + * Oliver Schwartz <[EMAIL PROTECTED]>, > + * Thomas Pedley <[EMAIL PROTECTED]>, > + * Steven Toth <[EMAIL PROTECTED]>, > + * Franz Lehner <[EMAIL PROTECTED]>, > + * Ivan Hawkes <[EMAIL PROTECTED]> > + * Edgar Hucek <[EMAIL PROTECTED]> > + * Niklas Lundberg <[EMAIL PROTECTED]> > + * > + * TODO: > + * - Der blinken-lights remain blinking. The xpad method of turning it off > + * doesn't appear to work. > + * - Rumble is also not functional > + * - There's always the headset. :) Probably belongs in a different driver. > + * > + */ > +#include <linux/kernel.h> > +#include <linux/init.h> > +#include <linux/slab.h> > +#include <linux/module.h> > +#include <linux/smp_lock.h> > +#include <linux/usb.h> > +#include <linux/version.h> > +#include <linux/usb/input.h> > +#include <linux/usbdevice_fs.h> > +#include <linux/timer.h> > +#include <asm/uaccess.h> > + > +#include "xboxrcvr.h" > + > +static unsigned long debug = 0; > +module_param(debug, ulong, 0644); > +MODULE_PARM_DESC(debug, "debug level"); > + > +static const struct xboxrcvr_device xboxrcvr_device[] = { > + { 0x045e, 0x0719, "Xbox 360 Wireless Receiver for Windows"}, /* Ha! */ > + { 0x0000, 0x0000, "nothing detected - FAIL"} > +}; > + > +static struct usb_device_id xboxrcvr_table [] = { > + { USB_DEVICE(0x045e, 0x0719) }, > + { } > +}; > + > +static const signed short xboxrcvr_btn[] = { > + BTN_A, BTN_B, BTN_X, BTN_Y, /* analogue buttons */ > + BTN_START, BTN_BACK, BTN_THUMBL, BTN_THUMBR, /* start/back/sticks */ > + BTN_0, BTN_1, BTN_2, BTN_3, /* d-pad as buttons */ > + BTN_TL, BTN_TR, /* Button LB/RB */ > + BTN_MODE, /* The big X */ > + -1 /* terminating entry */ > +}; > + > +static const signed short xboxrcvr_abs[] = { > + ABS_X, ABS_Y, /* left stick */ > + ABS_RX, ABS_RY, /* right stick */ > + ABS_Z, ABS_RZ, /* triggers left/right */ > + -1 /* terminating entry */ > +}; > + > +MODULE_DEVICE_TABLE(usb, xboxrcvr_table); > + > +/** > + * xboxrcvr_process_packet > + * > + * Completes a request by converting the data into events > + * for the input subsystem. > + * > + * The report descriptor was taken from ITO Takayukis website: > + * http://euc.jp/periphs/xbox-controller.en.html > + */ > +static void xboxrcvr_process_packet(struct usb_xboxrcvr *xboxrcvr, unsigned > char *sdata) > +{ > + struct input_dev *dev = xboxrcvr->dev; > + int i; > + int last; > + > + /* Four byte shift over so we can just use the code from xpad.c */ > + unsigned char *data = &sdata[4]; > + > + if(debug) { > + printk(KERN_INFO "xboxrcvr: id: %d, data:", xboxrcvr->id); > + /* Just the 4 bytes of header info or everything */ > + if(debug == 1) > + last = 4; > + else > + last = 29; > + for(i = 0; i < last; i++) { > + printk("0x%02x ", sdata[i]); > + } > + printk("\n"); > + return; > + } > + > + /* The 1st four bytes appear to be some kind of status word */ > + > + /* Presence change - Create/Delete an input device */ > + /* Well, we're getting something. Create a device */ > + if(!xboxrcvr->gamepad_present) { > + xboxrcvr->gamepad_present = 1; > + schedule_work(&xboxrcvr->idev_build); > + return; > + } > + > + if(!xboxrcvr->gamepad_registered) > + return; > + > + if(sdata[0] & 0x8) { > + if(!(sdata[1] & 0x80)) { > + /* xboxrcvr->gamepad_present cleared in teardown */ > + xboxrcvr->gamepad_registered = 0; > + schedule_work(&xboxrcvr->idev_teardown); > + } > + > + /* Doesn't do anything yet. Thanks for noticing */ > + if(sdata[1] & 0x40) { > + xboxrcvr->headset_present = 1; > + } else { > + xboxrcvr->headset_present = 0; > + } > + } > + > + /* Valid pad data */ > + if(!(sdata[1] & 0x1) || !xboxrcvr->open) > + return; > + > + /* digital pad (button mode) bits (3 2 1 0) (right left down up) */ > + input_report_key(dev, BTN_0, (data[2] & 0x01)); > + input_report_key(dev, BTN_1, (data[2] & 0x08) >> 3); > + input_report_key(dev, BTN_2, (data[2] & 0x02) >> 1); > + input_report_key(dev, BTN_3, (data[2] & 0x04) >> 2); > + > + /* start and back buttons */ > + input_report_key(dev, BTN_START, (data[2] & 0x10) >> 4); > + input_report_key(dev, BTN_BACK, (data[2] & 0x20) >> 5); > + > + /* stick press left/right */ > + input_report_key(dev, BTN_THUMBL, (data[2] & 0x40) >> 6); > + input_report_key(dev, BTN_THUMBR, data[2] >> 7); > + > + /* buttons A, B, X, Y digital mode */ > + input_report_key(dev, BTN_A, (data[3] & 0x10) >> 4); > + input_report_key(dev, BTN_B, (data[3] & 0x20) >> 5); > + input_report_key(dev, BTN_X, (data[3] & 0x80) >> 7); > + input_report_key(dev, BTN_Y, (data[3] & 0x40) >> 6); > + input_report_key(dev, BTN_TL, data[3] & 0x01); > + input_report_key(dev, BTN_TR, (data[3] & 0x02) >> 1); > + input_report_key(dev, BTN_MODE, (data[3] & 0x04) >> 2); > + > + /* left stick */ > + input_report_abs(dev, ABS_X, (__s16)(((__s16)data[7] << 8) | > (__s16)data[6])); > + input_report_abs(dev, ABS_Y, (__s16)(((__s16)data[9] << 8) | > (__s16)data[8])); > + > + /* right stick */ > + input_report_abs(dev, ABS_RX, (__s16)(((__s16)data[13] << 8) | > (__s16)data[12])); > + input_report_abs(dev, ABS_RY, (__s16)(((__s16)data[11] << 8) | > (__s16)data[10])); > + > + /* triggers left/right */ > + input_report_abs(dev, ABS_Z, data[4]); > + input_report_abs(dev, ABS_RZ, data[5]); > + > + input_sync(dev); > +} > + > +/** > + * xboxrcvr_open > + * > + * Called when a an application opens the device. > + */ > +static int xboxrcvr_open(struct input_dev *dev) > +{ > + struct usb_xboxrcvr *xboxrcvr = dev->private; > + > + if(debug) > + info("opening device"); > + xboxrcvr->open = 1; > + > + return 0; > +} > + > +/** > + * xboxrcvr_close > + * > + * Called when an application closes the device. > + */ > +static void xboxrcvr_close(struct input_dev *dev) > +{ > + struct usb_xboxrcvr *xboxrcvr = dev->private; > + > + if(debug) > + info("closing device"); > + xboxrcvr->open = 0; > +} > + > +/** > + * xboxrcvr_irq_in > + * > + * Completion handler for interrupt in transfers (user input). > + * Just calls xboxrcvr_process_packet which does then emit input events. > + */ > +static void xboxrcvr_irq_in(struct urb *urb) > +{ > + struct usb_xboxrcvr *xboxrcvr = urb->context; > + int retval; > + > + switch (urb->status) { > + case 0: > + /* success */ > + break; > + case -ECONNRESET: > + case -ENOENT: > + case -ESHUTDOWN: > + /* this urb is terminated, clean up */ > + dbg("%s - urb shutting down with status: %d", > + __FUNCTION__, urb->status); > + return; > + default: > + dbg("%s - nonzero urb status received: %d", > + __FUNCTION__, urb->status); > + goto exit; > + } > + > + xboxrcvr_process_packet(xboxrcvr, xboxrcvr->idata); > + > +exit: > + retval = usb_submit_urb(urb, GFP_ATOMIC); > + if (retval) > + err("%s - usb_submit_urb failed with result %d", > + __FUNCTION__, retval); > +} > + > +/* > + * Create an input device when a controller shows up > +*/ > +void idev_build_func(struct work_struct *work) > +{ > + struct usb_xboxrcvr *xboxrcvr = container_of(work, struct usb_xboxrcvr, > + idev_build); > + > + struct input_dev *input_dev; > + char path[65]; > + int i; > + > + input_dev = input_allocate_device(); > + if(!input_dev) { > + info("Could not allocate input device"); > + return; > + } > + > + > + /* FIXME: Yuck. Need to make sure this doesn't get truncated */ > + usb_make_path(xboxrcvr->udev, path, sizeof(xboxrcvr->phys)); > + snprintf(xboxrcvr->phys, sizeof(xboxrcvr->phys), "%s/input%d", path, > xboxrcvr->id); > + snprintf(xboxrcvr->uniq, sizeof(xboxrcvr->uniq), "xpad%d", xboxrcvr->id > >> 1); > + > + input_dev->name = xboxrcvr_device[0].name; > + input_dev->phys = xboxrcvr->phys; > + input_dev->uniq = xboxrcvr->uniq; > + input_dev->cdev.dev = &(xboxrcvr->intf->dev); > + input_dev->private = xboxrcvr; > + input_dev->open = xboxrcvr_open; > + input_dev->close = xboxrcvr_close; > + usb_to_input_id(xboxrcvr->udev, &input_dev->id); > + > + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); > + > + for (i = 0; xboxrcvr_btn[i] >= 0; ++i) > + set_bit(xboxrcvr_btn[i], input_dev->keybit); > + > + for (i = 0; xboxrcvr_abs[i] >= 0; ++i) { > + > + signed short t = xboxrcvr_abs[i]; > + > + set_bit(t, input_dev->absbit); > + > + switch (t) { > + case ABS_X: > + case ABS_Y: > + case ABS_RX: > + case ABS_RY: /* the two sticks */ > + input_set_abs_params(input_dev, t, > + -32768, 32767, 16, 12000); > + break; > + case ABS_Z: /* left trigger */ > + case ABS_RZ: /* right trigger */ > + input_set_abs_params(input_dev, t, > + 0, 255, 0, 0); > + break; > + } > + } > + > + xboxrcvr->dev = input_dev; > + input_register_device(xboxrcvr->dev); > + xboxrcvr->gamepad_registered = 1; > +} > + > +void xboxrcvr_unregister_idev(struct input_dev **input_dev) > +{ > + if(*input_dev) > + input_unregister_device(*input_dev); > + *input_dev = NULL; > +} > + > +void idev_teardown_func(struct work_struct *work) > +{ > + struct usb_xboxrcvr *xboxrcvr = container_of(work, struct usb_xboxrcvr, > + idev_teardown); > + xboxrcvr_unregister_idev(&xboxrcvr->dev); > + xboxrcvr->gamepad_present = 0; > +} > + > +/** > + * xboxrcvr_probe > + * > + * Called upon device detection to find a suitable driver. > + * Must return NULL when no xboxrcvr is found, else setup everything. > + */ > +static int xboxrcvr_probe(struct usb_interface *intf, > + const struct usb_device_id *id) > +{ > + struct usb_device *udev = interface_to_usbdev(intf); > + struct usb_xboxrcvr *xboxrcvr; > + > + struct usb_endpoint_descriptor *ep_irq_in; > + int status; > + > + /* Odd numbered interfaces appear to be audio/microphone */ > + if(intf->cur_altsetting->desc.bInterfaceNumber % 2) > + return -ENODEV; > + > + xboxrcvr = kzalloc(sizeof(struct usb_xboxrcvr), GFP_KERNEL); > + if (!xboxrcvr) > + goto fail1; > + > + xboxrcvr->id = intf->cur_altsetting->desc.bInterfaceNumber; > + xboxrcvr->idata = usb_buffer_alloc(udev, XBOXRCVR_PKT_LEN, > + GFP_ATOMIC, &xboxrcvr->idata_dma); > + xboxrcvr->udev = udev; > + > + if (!xboxrcvr->idata) > + goto fail1; > + > + /* setup input interrupt pipe (button and axis state) */ > + xboxrcvr->irq_in = usb_alloc_urb(0, GFP_KERNEL); > + if (!xboxrcvr->irq_in) > + goto fail2; > + > + ep_irq_in = &intf->cur_altsetting->endpoint[0].desc; > + usb_fill_int_urb(xboxrcvr->irq_in, udev, > + usb_rcvintpipe(udev, ep_irq_in->bEndpointAddress), > + xboxrcvr->idata, XBOXRCVR_PKT_LEN, xboxrcvr_irq_in, > + xboxrcvr, ep_irq_in->bInterval); > + xboxrcvr->irq_in->transfer_dma = xboxrcvr->idata_dma; > + xboxrcvr->irq_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; > + > + usb_set_intfdata(intf, xboxrcvr); > + > + xboxrcvr->irq_in->dev = xboxrcvr->udev; > + if ((status = usb_submit_urb(xboxrcvr->irq_in, GFP_KERNEL))) { > + err("submit int urb failed: %d", status); > + goto fail2; > + } > + > + xboxrcvr->intf = intf; > + INIT_WORK(&xboxrcvr->idev_build, idev_build_func); > + INIT_WORK(&xboxrcvr->idev_teardown, idev_teardown_func); > + > + return 0; > + > +fail2: usb_buffer_free(udev, XBOXRCVR_PKT_LEN, xboxrcvr->idata, > + xboxrcvr->idata_dma); > +fail1: return -ENOMEM; > +} > + > +/** > + * xboxrcvr_disconnect > + * > + * Called upon device disconnect to dispose of the structures and > + * close the USB connections. > + */ > +static void xboxrcvr_disconnect(struct usb_interface *intf) > +{ > + struct usb_xboxrcvr *xboxrcvr = usb_get_intfdata(intf); > + > + usb_set_intfdata(intf, NULL); > + if (xboxrcvr) { > + usb_kill_urb(xboxrcvr->irq_in); > + usb_free_urb(xboxrcvr->irq_in); > + > + xboxrcvr_unregister_idev(&xboxrcvr->dev); > + > + usb_buffer_free(interface_to_usbdev(intf), XBOXRCVR_PKT_LEN, > + xboxrcvr->idata, xboxrcvr->idata_dma); > + > + kfree(xboxrcvr); > + } > + > +} > + > +/******************* Linux driver framework specific stuff ************/ > + > +static struct usb_driver xboxrcvr_driver = { > + .name = "xboxrcvr", > + .probe = xboxrcvr_probe, > + .disconnect = xboxrcvr_disconnect, > + .id_table = xboxrcvr_table, > +}; > + > +/** > + * driver init entry point > + */ > +static int __init usb_xboxrcvr_init(void) > +{ > + int result = usb_register(&xboxrcvr_driver); > + if (result == 0) > + info(DRIVER_DESC " " DRIVER_VERSION); > + return result; > +} > + > +/** > + * driver exit entry point > + */ > +static void __exit usb_xboxrcvr_exit(void) > +{ > + usb_deregister(&xboxrcvr_driver); > +} > + > +module_init(usb_xboxrcvr_init); > +module_exit(usb_xboxrcvr_exit); > + > +MODULE_AUTHOR(DRIVER_AUTHOR); > +MODULE_DESCRIPTION(DRIVER_DESC); > +MODULE_LICENSE("GPL"); > + > diff --git a/drivers/input/joystick/xboxrcvr.h > b/drivers/input/joystick/xboxrcvr.h > new file mode 100644 > index 0000000..685dbff > --- /dev/null > +++ b/drivers/input/joystick/xboxrcvr.h > @@ -0,0 +1,72 @@ > +/* > + * Xbox wireless receiver input device driver for Linux - v0.0.1 > + * > + * Copyright (c) 2007 Brian Magnuson <[EMAIL PROTECTED]> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * 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 > + * > + */ > + > +#ifndef __XBOXRCVR_h > +#define __XBOXRCVR_h > + > +/************************* driver internals ***************************/ > +#ifdef __KERNEL__ > + > +#include <linux/input.h> > +#include <linux/circ_buf.h> > + > +/****************** driver description and version ********************/ > +#define DRIVER_VERSION "v0.0.1" > +#define DRIVER_AUTHOR "Brian Magnuson" > +#define DRIVER_DESC "Driver for wireless xbox360 controllers" > + > +#define XBOXRCVR_PKT_LEN 32 > + > +/************************* the device struct **************************/ > +struct usb_xboxrcvr { > + struct input_dev *dev; /* input device interface */ > + struct usb_device *udev; /* usb device */ > + > + struct urb *irq_in; /* urb for int. in report */ > + > + int id; > + int gamepad_present; > + int gamepad_registered; > + int headset_present; > + > + struct usb_interface *intf; > + > + struct work_struct idev_build; > + struct work_struct idev_teardown; > + > + unsigned char *idata; /* input data */ > + dma_addr_t idata_dma; > + > + int open; /* open to the input layer */ > + > + char phys[65]; /* physical input dev path */ > + char uniq[5]; /* unique identifier */ > +}; > + > +/* for the list of know devices */ > +struct xboxrcvr_device { > + u16 idVendor; > + u16 idProduct; > + char *name; > +}; > + > +#endif /* __KERNEL__ */ > + > +#endif /* __XBOXRCVR_h */ > -- Jiri Kosina