Using the Palm Pre as Xbox 360 storage

I have a dream where I plug my Palm Pre into my Xbox 360 and it is recognized as a USB Mass Storage device.  I then save all my games and downloaded content to it.  Later, I fire up a PDK app that loads x360 (A FUSE filesystem driver for the 360) and I edit my saved games, adding more health, more gold, etc. What would it take for a USB newb to turn this into a reality?  I’m not sure, but I am willing to try.

Spoofing Vendor/Product ID

My first thought was that the 360 was blocking Vendor/Product IDs, much like Apple’s iTunes does.  So my first step was to spoof the USB Vendor/Product ID.

First, I found a USB stick that worked with the 360, I popped it into my linux box, ran lsusb -v and copied down the details.

Bus 002 Device 002: ID 1307:0163 Transcend Information, Inc. 512MB/1GB Flash Drive
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x1307 Transcend Information, Inc.
  idProduct          0x0163 512MB/1GB Flash Drive
  bcdDevice            1.00
  iManufacturer           1
  iProduct                2
  iSerial                 3
  bNumConfigurations      1

I then did the same for my Palm Pre

Bus 002 Device 003: ID 0830:0101 Palm, Inc.
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x0830 Palm, Inc.
  idProduct          0x0101
  bcdDevice            2.16
  iManufacturer           1 Palm Inc.
  iProduct                2 Pre
  iSerial                 3 a5470bf024bd51193d15a9614fe7d302960c955f
  bNumConfigurations      1

Unfortunately, the only way of changing the Vendor/Product ID is to recompile the kernel.  If we grep through a default config file for the Pre (look in /boot on the device), we’ll find:

CONFIG_USB_ROCKHOPPER=y
CONFIG_USB_ROCKHOPPER_MANUFACTURER_STRING=Palm Inc.
CONFIG_USB_ROCKHOPPER_PRODUCT_STRING=Pre
CONFIG_USB_ROCKHOPPER_VID=0x830
CONFIG_USB_ROCKHOPPER_PID_DEV_1=0x100
CONFIG_USB_ROCKHOPPER_PID_DEV_2=0x101
CONFIG_USB_ROCKHOPPER_PID_DEBUG=0x8002
CONFIG_USB_ROCKHOPPER_PID_PASSTHRU=0x8003
CONFIG_USB_ROCKHOPPER_PID_RETAIL=0x8004

For the curious, here is the secret decoder table:

parameter function
USB_ROCKHOPPER_VID The USB Vendor ID
USB_ROCKHOPPER_PID_DEV_1 The USB Product ID for RNDIS Ethernet + Passthru
USB_ROCKHOPPER_PID_DEV_2 The USB Product ID for RNDIS Ethernet + Mass-Storage + Novacom.
USB_ROCKHOPPER_PID_DEBUG The USB Product ID for Mass-Storage + Novacom
USB_ROCKHOPPER_PID_PASSTHRU The USB Product ID for Passthru
USB_ROCKHOPPER_PID_RETAIL The USB Product ID for Mass-Storage only

Grep through the source tree for these config variables and you’ll find all the goodies in drivers/usb/gadget/rockhopper.c.  We don’t actually have to modify this source file, but long term, the elegant solution would be compiling this as a module and adding some of these config strings as arguments.

Results of Spoofing

After compiling the kernel and loading it on the Pre, the device now looked like this:

Bus 002 Device 004: ID 1307:0163 Transcend Information, Inc. 512MB/1GB Flash Drive
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x1307 Transcend Information, Inc.
  idProduct          0x0163 512MB/1GB Flash Drive
  bcdDevice            3.16
  iManufacturer           1
  iProduct                2
  iSerial                 3

But, the 360 didn’t seem to care and still couldn’t see the device :( Ok, time to regroup and educate.  USB in a Nutshell became my new best friend.

USB Configuration and Interface Descriptors

As I read about the USB spec, a couple things started nagging at me, particularly bNumInterfaces and Endpoint Descriptors.

USB devices can have multiple Interfaces defined.  On the Palm Pre, you can use it in Mass Storage mode, usb networking mode or Novacom mode (for debugging).  Each of these modes has an Interface Descriptor where bNumInterfaces is the total number of these Interface Descriptors.

Each Interface Descriptor contains 1 or more Endpoint Descriptors.  From an embedded background, I’m imagining these to be data pins, where each pin is configured either for input or output (source/sink).

I started taking a survey of USB devices that worked with my 360 and those that didn’t work and found the following.

Device bNumInterfaces # of Endpoint Descriptors for Mass Storage Works with Xbox 360
Transcend flash drive 1 3: Bulk In, Bulk Out, Interrupt In yes
Kingston flash drive 1 2: Bulk In, Bulk Out no
Generic flash drive 1 3: Bulk In, Bulk Out, Interrupt In yes
Palm Pre 2 (In mass storage mode, 4 otherwise) 2: Bulk In, Bulk Out no

I’m not surprised that all of my flash drives have only a single interface descriptor.  The two questions I need to answer are:

  1. Does the Xbox 360 care if multiple interface descriptors exist?
  2. Does the Xbox 360 require an Interrupt Endpoint to work properly?

Well, question 1 can easily be avoided.  The reason I’m seeing 2 interfaces is because I am in developer mode (USB_ROCKHOPPER_PID_DEBUG).  A quick konami code later and I’m out of developer mode and down to a single interface descriptor in mass storage mode.

Question 2 will require a hack of the Pre mass storage driver.

Hacking the Palm Pre Mass Storage Driver

Conveniently, Palm provides all of their kernel modifications as a patch.  Grep’ing through this master patch, it was quite easy to spot the interface descriptors in f_mass_storage.c.

When I started this little project, I knew nothing about USB other than how to plug it in.  I’m a bit further now, but to be clear, the intention of this hack is purely cosmetic.

The simple goal is:

  • Add an interrupt Endpoint for USB_ROCKHOPPER_PID_RETAIL

After a bit of hacking, I came up with the following patch.

--- f_mass_storage.c.orig       2010-09-01 08:27:17.223869104 -0700
+++ f_mass_storage.c    2010-09-01 20:22:28.093870603 -0700
@@ -339,6 +339,7 @@

        struct usb_ep           *bulk_in;
        struct usb_ep           *bulk_out;
+       struct usb_ep           *intr_in;

        struct fsg_buffhd       *next_buffhd_to_fill;
        struct fsg_buffhd       *next_buffhd_to_drain;
@@ -450,6 +451,8 @@
                name = bulk-in;
        else if (ep == fsg-bulk_out)
                name = bulk-out;
+       else if (ep == fsg-intr_in)
+               name = intr-in;
        else
                name = ep-name;
        FSG_DBG(fsg, %s set haltn, name);
@@ -501,7 +504,7 @@
        .bLength =              sizeof intf_desc,
        .bDescriptorType =      USB_DT_INTERFACE,

-       .bNumEndpoints =        2,              /* Adjusted during fsg_bind() */
+       .bNumEndpoints =        3,              /* Adjusted during fsg_bind() */
        .bInterfaceClass =      USB_CLASS_MASS_STORAGE,
        .bInterfaceSubClass =   US_SC_SCSI,
        .bInterfaceProtocol =   US_PR_BULK,
@@ -530,13 +533,23 @@
        /* wMaxPacketSize set by autoconfiguration */
 };

+static struct usb_endpoint_descriptor
+fs_intr_in_desc = {
+       .bLength =              USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType =      USB_DT_ENDPOINT,
+
+       .bEndpointAddress =     USB_DIR_IN,
+       .bmAttributes =         USB_ENDPOINT_XFER_INT,
+};
+
 static struct usb_descriptor_header *fs_function[] = {
        (struct usb_descriptor_header *) intf_desc,
+       (struct usb_descriptor_header *) fs_intr_in_desc,
        (struct usb_descriptor_header *) fs_bulk_in_desc,
        (struct usb_descriptor_header *) fs_bulk_out_desc,
        NULL,
 };

 static struct usb_endpoint_descriptor
@@ -560,9 +573,20 @@
        .bInterval =            1,      /* NAK every 1 uframe */
 };

+static struct usb_endpoint_descriptor
+hs_intr_in_desc = {
+       .bLength =              USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType =      USB_DT_ENDPOINT,
+
+       /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */
+       .bmAttributes =         USB_ENDPOINT_XFER_INT,
+       .wMaxPacketSize =        __constant_cpu_to_le16(64),
+       .bInterval =            8,
+};

 static struct usb_descriptor_header *hs_function[] = {
        (struct usb_descriptor_header *) intf_desc,
+       (struct usb_descriptor_header *) hs_intr_in_desc,
        (struct usb_descriptor_header *) hs_bulk_in_desc,
        (struct usb_descriptor_header *) hs_bulk_out_desc,
        NULL,

Every time you hack a USB endpoint, a USB programmer dies :/  Sorry!

Mass Storage Driver Hack Results

After recompiling and loading the mass storage driver, the endpoints look like this:

Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           3
      bInterfaceClass         8 Mass Storage
      bInterfaceSubClass      6 SCSI
      bInterfaceProtocol     80 Bulk (Zip)
      iInterface              0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x00  EP 0 OUT
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               8
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               1

The interrupt endpoint is there but the fact that it is enumerated as EP0 is a problem, EP0 is reserved and not supposed to have a descriptor!   Clearly I am missing something here!  I took several passes at this, trying to get the interrupt endpoint recognized as 082 / EP2, which is used in other device configurations as an interrupt endpoint.  So far none have sticked.

As I build up my understanding of the composite and gadget usb frameworks, I found a good article on the composite usb framework which unfortunately shows that most folks writing USB mass storage drivers in linux leave the endpoint descriptor definitions alone.  The endpoint details happen behind the scenes and get implemented as part of the framework rather than being defined by your driver.  This project is getting put on the back burner for now until I have some quality time to devote to understanding the frameworks involved.

Continued in Part 2.

Published 01 September 2010
blog comments powered by Disqus ~