XBOX - Programming the xpad
controller (i.e. Gamepad)
This section of xbox-programming is for those who want to develop
software for the xbox from the beginning, from the bits and bytes that make
up our wonderful software.
Now this tutorial is ment to give you an insight into how a usb
works...well the ohci-usb...and how you would code a driver..or maybe develop
further apps for the xbox. The code should compile on the xdk or
openxdk..or even the non-xdk (e.g. linux, custom xbe etc). As we'll be
coding at a very low level.
So how does the gamepad work?... is it some special new secret
way for us to connect gamepads to a console?... nop...it uses the usb
protocol...with ohci......uggg...ohci?... ohci = open host control interface.
It was developed by many of the industry companys...ms was one of them.
Its basically the underlaying system of communcating with our hub...which is our
usb :)...well basically.... trying to keep it simple here.
If you read my tutorial and discover anything new...or god help
find an error - please give me an email
(bkenwright@xbdev.net). And as you'll probably read... this tutorial
is for educational purposes. Is there a problem in knowing how your xbox
works! Its your xbox...you payed good money for it.
Sweet donations... well I had to say it... if you think my
tutorials are worthy...you may contribure to the web site... which will only
result in an improved website and more hot and spicy tutorials on all sorts of
code.
Step one....in a galaxy far far
away...
Before we do anything...we need to know some basics... And
the basics, is the PCI. The Peripheral Component Interface...for those who
have forgot ;) This tells us where all our devices memory are located.
A lot of things in the xbox are memory mapped..... graphics ouput....usb....input/output...etc
are all memory mapped. And the PCI tells us where they are...it also sets
up some basic things.
Well now....thats a quick sketch of the PCI registers and there
layout...assuming you know there base address. A nice tutorial I found on
google which you might want to read...
PCI-Tutorial Link. But don't worry... once..just wanted to show you
where I get the ohci-usb offsets from :)
Now to show you whats in our PCI registers when the xbox starts
up...I wrote a small piece of code...which simply dumps there values to a small
txt file...so you can see things like some of the DeviceID's...and memory
offsets. You might want to look around on the net for what the various
DeviceID's coinside with.
Download Code : PCI Dump |
Starting XBOX Code
PCI_HOST -> DeviceID:0x02A5 VendorID: 0x10DE
PCI_MEM -> DeviceID:0x02A6 VendorID: 0x10DE
PCI_ISA -> DeviceID:0x01B2 VendorID: 0x10DE
~_SMBUS -> DeviceID:0x01B4 VendorID: 0x10DE
PCI_USB1 -> DeviceID:0x01C2 VendorID: 0x10DE
PCI_USB2 -> DeviceID:0x01C2 VendorID: 0x10DE
PCI_AGP -> DeviceID:0x01B7 VendorID: 0x10DE
PCI_VGA -> DeviceID:0x02A0 VendorID: 0x10DE
PCI_NONE -> DeviceID:0xFFFF VendorID: 0xFFFF
USB1 - PCI Register Values
0x00 0x01C210DE
0x04 0x00B00006
0x08 0x0C0310B1
0x0C 0x00000000
0x10 0xFED00000 (This will be our vase value for usb1 in memory)
0x14 0x00000000
0x18 0x00000000
0x1C 0x00000000
0x20 0x00000000
0x24 0x00000000
0x28 0x00000000
0x2C 0x00000000
0x30 0x00000000
0x34 0x00000044
0x38 0x00000000
0x3C 0x01030101
Ending XBOX Code
|
There you go!... What a piece of code. Well I think
it is. It really tells us something about the xbox that we wouldn't
normally know :) I made the code a little longer than it really needed to
be... but you got to see how to get the VGA DeviceID...and with a bit of work
you could find its memory offsets to the graphics card registers.. But
thats for another tutorial and another time. Remember as I said
before...more or less everything in the xbox is memory mapped..its just a matter
of knowing where things are and how to use them.
One of the most important value you should remember is offset
0x10...also known as BASE0...and is 0xfed00000. Ooooo....so whats that?
Well thats the location of our ohci-usb registers....thats where the base value
is located. Yup... and there's a doc called the ohci spec doc. Which
is in pdf form which you can download, and lists what all the registers are and
what there offsets are....cool ehh? Well it will get better..heheh.
I know we haven't achieve much yet...but we will...be patient.
Here's a test for you....what do you think the PCI values change
to after we init our usb?..hmm...any ideas?...Well let me show you the a PCI
register dump of what the PCI registers should hold after we start our usb code:
PCI - Setup Register Values USB-OHCI |
USB1 - PCI Register Values
0x00 0x01C210DE
0x04 0x00B00006
0x08 0x0C0310B1
0x0C 0x00000000
0x10 0xFED00000
0x14 0x00000000
0x18 0x00000000
0x1C 0x00000000
0x20 0x00000000
0x24 0x00000000
0x28 0x00000000
0x2C 0x00000000
0x30 0x00000000
0x34 0x00000044
0x38 0x00000000
0x3C 0x01030101 |
So take a careful look....see if you can see the difference
between the initilised registers and the non-initilised ones above. Well
after 5-10 minutes of lucking you'll tell me...'there the same!'..hehe... and so
they are...hehe All we needed from the PCI was the base value. Of
course the PCI does tell us things like the interrupt IRQ and the line number
etc... but all we need to set up a basic Gamepad demo is the OHCI-USB base
memory address...which for usb1 is 0xfed00000.
OHCI - The regisers? What are
they?
Well no more messing around with that PCI stuff.....but its
always useful to know I'd say :) But for us...well we know what we need to
know now...so on we go with our mission...and let for force...I mean code be
with us......
Its got to be worth giving you the link to the ohci
specificaiton doc here...as it I always find it useful at times to look at it...
its a big doc...160 odd pages... but still..its got an appendix which outlines
each register and what it is...and what values are in them..and why the value
means etc..... Here is the link:
OHCI
specification
Here is a quick sketch of the layout of the various ohci
registers in memory...remember base address will be 0xfed00000 for usb1...just
incase you've forgot :)
But us being programmers...and of course us loving C....we can
put the ohci registers in a structure....then set up a structure pointer and
point it to the base of our bunch of registers :). Now I chose a popular
structure...based on some linux code on the web...why?... well believe me its
better to stick to the same thing.... things would be so much easer if things
matched :)
So using this principle...lets dump the values that we have a
boot out to a file and see what they look like...
OHCI
Dump Code: |
#include
<stdio.h> // fopen, fclose, sprintf
typedef
unsigned int
__u32;
/**************************************************************************/
/* */
/* Here is our structure for the OHCI controller registers...which */
/* we'll memory map to the location that we want :) */
/* */
/**************************************************************************/
#define
MAX_ROOT_PORTS 15
struct
ohci_regs
{
/* control and status registers */
__u32 revision;
__u32 control;
__u32 cmdstatus;
__u32 intrstatus;
__u32 intrenable;
__u32 intrdisable;
/* memory pointers */
__u32 hcca;
__u32 ed_periodcurrent;
__u32 ed_controlhead;
__u32 ed_controlcurrent;
__u32 ed_bulkhead;
__u32 ed_bulkcurrent;
__u32 donehead;
/* frame counters */
__u32 fminterval;
__u32 fmremaining;
__u32 fmnumber;
__u32 periodicstart;
__u32 lsthresh;
/* Root hub ports */
struct ohci_roothub_regs
{
__u32 a;
__u32 b;
__u32 status;
__u32 portstatus[MAX_ROOT_PORTS];
} roothub;
};
typedef
struct ohci_regs ohci_regs_t;
/**************************************************************************/
/* */
/* Forward declaration of our function that will dump the ohci reg */
/* to file....its declared at the bottom of the file. */
/* */
/**************************************************************************/
void
dump_ohci_regs(ohci_regs_t * ohci);
/**************************************************************************/
/* */
/* Program Entry Point */
/* */
/**************************************************************************/
void
main()
{
// Turn off interupts ...so our xbox kernel
doesn't do anything
// while where checking things out! Its only for
now..we
// can turn the xbox interrupts back in easily
using 'sti'
// STI and CLI can be used to enable/disable
interrupts
__asm
{
cli
}
// Lets declare a pointer of our structure...then
we'll do it so it
// points to the start of our ohci-usb1 hub
registers.
ohci_regs_t * ohci = (ohci_regs_t*)0xfed00000; //
? volatile
dump_ohci_regs( ohci );
}//
End main()
/**************************************************************************/
/* */
/* Simple but useful - these couple of functions dump our ohci-usb */
/* register values to a file.... you can use functions like this to */
/* monitor prgress of your ohci-driver development...while you work */
/* towards fixing faults */
/* */
/**************************************************************************/
char
buf[200];
void
dbg(char* str)
{
FILE *fp = fopen("d:\\output.txt", "a+");
fprintf(fp, "%s", str);
fclose(fp);
}//
End dbg(..)
void
dump_ohci_regs(ohci_regs_t * ohci)
{
dbg("\nOHCI
Reg Values:\n\n");
sprintf( buf, " revision 0x%08x\n",ohci->revision);
dbg(buf);
sprintf( buf, " control 0x%08x\n",ohci->control);
dbg(buf);
sprintf( buf, " cmdstatus 0x%08x\n",ohci->cmdstatus);
dbg(buf);
sprintf( buf, " intrstatus 0x%08x\n",ohci->intrstatus);
dbg(buf);
sprintf( buf, " intrenable 0x%08x\n",ohci->intrenable);
dbg(buf);
sprintf( buf, " intrdisable 0x%08x\n",ohci->intrdisable);
dbg(buf);
sprintf( buf, " ed_periodcurrent 0x%08x\n",ohci->ed_periodcurrent);
dbg(buf);
sprintf( buf, " ed_controlhead 0x%08x\n",ohci->ed_controlhead);
dbg(buf);
sprintf( buf, " ed_controlcurrent 0x%08x\n",ohci->ed_controlcurrent);
dbg(buf);
sprintf( buf, " ed_bulkhead 0x%08x\n",ohci->ed_bulkhead);
dbg(buf);
sprintf( buf, " ed_bulkcurrent 0x%08x\n",ohci->ed_bulkcurrent);
dbg(buf);
sprintf( buf, " donehead 0x%08x\n",ohci->donehead);
dbg(buf);
sprintf( buf, " fminterval 0x%08x\n",ohci->fminterval);
dbg(buf);
sprintf( buf, " fmremaining 0x%08x\n",ohci->fmremaining);
dbg(buf);
sprintf( buf, " periodicstart 0x%08x\n",ohci->periodicstart);
dbg(buf);
sprintf( buf, " lsthresh 0x%08x\n",ohci->lsthresh);
dbg(buf);
sprintf( buf, " ohci_roothub_regs.a 0x%08x\n",ohci->roothub.a);
dbg(buf);
sprintf( buf, " ohci_roothub_regs.b 0x%08x\n",ohci->roothub.b);
dbg(buf);
sprintf( buf, " ohci_roothub_regs.status 0x%08x\n",ohci->roothub.status);
dbg(buf);
sprintf( buf, " ohci_roothub_regs.portstatus[0] 0x%08x\n",ohci->roothub.portstatus[0]); dbg(buf);
sprintf( buf, " ohci_roothub_regs.portstatus[1] 0x%08x\n",ohci->roothub.portstatus[1]); dbg(buf);
sprintf( buf, " ohci_roothub_regs.portstatus[2] 0x%08x\n",ohci->roothub.portstatus[2]); dbg(buf);
sprintf( buf, " ohci_roothub_regs.portstatus[3] 0x%08x\n",ohci->roothub.portstatus[3]); dbg(buf);
}
// End dump_ohci_regs(..) |
Well its not much of a piece of code....but if you run it.....it
will create some really useful information... It shows us what our OHCI
registers have in them. I'll show you the output file now, and explain
which values you should take notice of.
Output File: |
OHCI
Reg Values:
revision 0x00000010
control 0x00000602
cmdstatus 0x00000004
intrstatus 0x00000004
intrenable 0x00000040
intrdisable 0x00000040
ed_periodcurrent 0x00000000
ed_controlhead 0x03f58820
ed_controlcurrent 0x00000000
ed_bulkhead 0x00000000
ed_bulkcurrent 0x00000000
donehead 0x00000000
fminterval 0xa7782edf
fmremaining 0x8000079b
periodicstart 0x00002a2f
lsthresh 0x00000620
ohci_roothub_regs.a 0x01001204
ohci_roothub_regs.b 0x00000000
ohci_roothub_regs.status 0x00000000
ohci_roothub_regs.portstatus[0] 0x00000100
ohci_roothub_regs.portstatus[1] 0x00000100
ohci_roothub_regs.portstatus[2] 0x00000100
ohci_roothub_regs.portstatus[3] 0x00000100 |
Hmmmm...just looks like a load of hex values doesn't it.... or
does it? Well first things first...that first value...its got our revision
in...which is revision?...yup..did I hear you...1.0...k...now notice that the
'portstatus[0]'...[1]..[2]... etc all have the same value...which if you look it
up..just means there's power there...but no device is identified.
How did I get that from all those 0's and 1's...well don't
forget that its a hex value...32 bit hex value...and remember...that means we
have 32 of those 1's and 0's. If you look at 0x00000100....this tells us
that bit 8 is a 1...all the other bits are 0. You can always check that on
your calculator if you want ..heheh
Get out the OHCI spec doc....the pdf one :)...and if you go
along to the back of it....it lists all the operational registers...the
location..and what each bit stands for. So bit 8 is PPS...which stands for
PortPowerStatus...and a 1 indicates that power is on. Hmmm.
Another interesting thing is bit 0....which is CCS (CurrentConnectStatus)....if
there's a 1 in there it means a device is connected. But of course we've
not initialised our ohci-usb registers yet...so there still in there default
stage and won't react to connected devices.
And for a final piece of info...if you notice the cmdstatus
register..its has a value which if you look up in the ohci spec doc...tells us
the device is in suspended mode!.
Of course we haven't initialized the ohci-usb device yet...so we
didn't expect much to be in there....but we'll go about setting it up so its
operational...! Get it ticking as you might say...and have it telling us
something we want.
Wonder what the working registers look like?....or more
correctly...what the correctly set registers would look like after we've gone
through the stage of setting up?... hmm..well I thought it might be good to see
what to look for...so here they are:
OHCI Operational Register Values |
OHCI
Reg Values:
revision 0x00000010
control 0x000006be
cmdstatus 0x00000004
intrstatus 0x00000004
intrenable 0x80000063
intrdisable 0x80000063
ed_periodcurrent 0x00000000
ed_controlhead 0x03fdf3d0
ed_controlcurrent 0x00000000
ed_bulkhead 0x00000000
ed_bulkcurrent 0x00000000
donehead 0x00000000
fminterval 0xa7782edf
fmremaining 0x80000d32
periodicstart 0x00002a2f
lsthresh 0x00000620
ohci_roothub_regs.a 0x01001204
ohci_roothub_regs.b 0x00000000
ohci_roothub_regs.status 0x00000000
ohci_roothub_regs.portstatus[0] 0x00000103
ohci_roothub_regs.portstatus[1] 0x00000100
ohci_roothub_regs.portstatus[2] 0x00000100
ohci_roothub_regs.portstatus[3] 0x00000100 |
The biggest thing to note..is the portstatus[0] register... that
3 on the end has a big meaning. It says that the last two bits are set to
1's. Which are the CCS (bit 0) and PES (bit 1) values...which stand for
CurrentConnectionStatus and PortEnabledStatus. Which comes down to..our
hub is connected and its enabled.
{ Next stage - Seting up the ohci...getting it operational!}
{ Allocating memory for the hcca memory area 256k aligned}
{ Sending some simple control msg's to get some info from the
hub}
Test Code |
Well to start things off, I've put together some
basic ohci-usb code. It still has some problems, them being timing or
alignment....not sure at this time. As its for setting the ohci-usb
address, and getting the device descriptors.
The code is available for download - and if you get any further results,
I'd be glad for some feedback...help with it.
DownloadCode
|
Bugs! & Problems!
Well I simplified the above TestCode...but we have
problems...for some reason I can't get the Control EndPoint Descriptor to go
into CurrentControlED register....it seems to always go to 0xff02ff00...or
0...but never the memory location that is ED!...hmmm.... Well here is the code
is someone wants to take a looksy....its stripped down to the very minimum:
-
Download Code -
_______________________________________________________________
Step two....solutions come in time
Many many nights and days later....a miracle! a miracle!
Well there I was taking a break from the coding of the ohci-usb stuff, as
sometimes you need to rest your mind. And well if you'll have tried the
code above, you'll see that we hit a wall. We didn't seem to be getting
anything back from our Hub :(
But then...late...and I mean very late...I was going through a
ton of raw asm/binary...and I came across some useful xbox kernel api's....as
show:
extern
"C" __u32 __stdcall
MmAllocateContiguousMemory(__u32 a);
extern
"C" __u32 __stdcall
MmLockUnlockBufferPages(__u32 MemoryAddress, __u32 NumberOfBytes, __u32 a);
extern
"C" __u32 __stdcall
MmGetPhysicalAddress(__u32 BaseAddress);
|
Now I was sitting there staring at the screen...and I
realised...I was making a terrible assumption....very silly of me. I was
assuming that all my memory that I was using was where it was suppose to be.
I'd been working in dos earlier...and in dos it is...memory is what it is...but
in protected mode on the xbox...all is not what you see. Even if you read
the xbox linux information on the net, on how the new kernel is loaded into
memory, those api's are used...as you need to have the 'real' memory location.!
So first thing first, I did some tests...just to make
sure...always make sure :) I tried allocating some memory and using 'MmGetPhysicalAddress'
on it...but this crashed it...after examining the asm I got the idea from, I
noticed that it called 'MmLockUnlockBufferPages'
first...so I decided to try that with the memory first...and yup...I got a
different values for the allocated pointer and returned value from 'MmGetPhysicalAddress'
.
The following morning I modified my earlier code so that the
ohci registers all used the real 'physical' memory locations this time!
And yes..."WAahhhooooo"...we had results! We actually got part of a
descriptor back! It all seemed okay....so bit by bit I added in the other
parts so that I got all the descriptors for the hub.
Code:
DownloadWholeCode |
.....
int
FindDev(ohci_t * ohci,
int
Port)
{
__u32 TDA;
int Speed=0;
s_USB_Devicedescriptor DD;
s_USB_Devicedescriptor * pDD = ⅅ
memset(pDD, 0, sizeof(DD) );
s_Transferdescriptor * TD;
__u32 GetDescr[2] = { 0x01000680, 0x00080000 };
__u32 WG = (__u32)GetDescr;
MmLockUnlockBufferPages( WG, 0x8, 0);
__u32 DDA = (__u32)pDD;
MmLockUnlockBufferPages( DDA, 0x32, 0);
TD
= (s_Transferdescriptor*) (((__u32*)ED)+20); //
Same as saying TD = EDA+80 ;)
TDA
= EDA + 80;
__u32 realTDA = MmGetPhysicalAddress( (__u32)TDA );
TD[0].Format = 0xE20050C0; // Get
DeviceDescriptor
TD[0].Buffer = MmGetPhysicalAddress(WG);
TD[0].NextTD = realTDA + 16;
TD[0].BufferEnd = MmGetPhysicalAddress(WG+7);
TD[1].Format = 0xE31050C1; // Receive
first 8 bytes of DeviceDescriptor
TD[1].Buffer = MmGetPhysicalAddress(DDA);
TD[1].NextTD = realTDA + 32;
TD[1].BufferEnd = MmGetPhysicalAddress(DDA + 7);
TD[2].Format = 0xE20050C2; // Queue END
TD[2].Buffer = 0;
TD[2].NextTD = 0;
TD[2].BufferEnd = 0;
// Power on + Enable Ports
ohci->regs->roothub.portstatus[ Port*4 ] = 0x100;
Sleep(2);
if( (ohci->regs->roothub.portstatus[ Port*4]
& 1) == 0 )
return 0;
// No device
if( ohci->regs->roothub.portstatus[ Port*4]
& 0x200) // lowspeed device?
Speed = 1;
else
Speed = 2;
if( ohci->regs->roothub.portstatus[ Port*4 ]
& 0x10000 ) // Port Power changed?
{
ohci->regs->roothub.portstatus[ Port*4 ] = 0x10000;
// Port power Ack
}
// Port Reset
// We will try and do this 4 times
ohci->regs->roothub.portstatus[ Port*4 ] = 0x10;
Sleep(40);
for(int i=0;
i<4; i++)
{
if( (ohci->regs->roothub.portstatus[
Port*4 ] & 0x10) == 0 )
break;
sprintf(buf, "\tport: %d, reset failed %d times\n", Port, i);
dbg(buf);
Sleep(100);
}
ohci->regs->roothub.portstatus[ Port*4 ] = 0x100000;
if( (ohci->regs->roothub.portstatus[ Port*4
] & 7) != 3 ) // Port disabled?
{
ohci->regs->roothub.portstatus[ Port*4 ] = 2;
}
// Configure Endpointdescriptor
if(Speed==2)
ED[1].Format &= 0xFFFFDFFF;
else
ED[1].Format |= 0x2000;
// determine MPS
ED[1].Headptr = realTDA;
ED[1].Tailptr = realTDA + 32;
ED[1].Format &= 0xffffff00;
// set CLF
ohci->regs->cmdstatus |= 2; //
CommandStatus
ohci->regs->control = 0x90; // set CLE
__u32 tt = ohci->regs->intrstatus; // clear all
Interruptflags
ohci->regs->intrstatus = tt;
DebugFile( ohci );
// wait for execution
dbg("waiting for execution\n");
do
{
ohci->regs->intrstatus = 0x4; // SOF
}while(
(ohci->regs->intrstatus & 2)== 0 );
Sleep(10);
// Errors?
ohci_hcca *hcca = (ohci_hcca*)ohci->hcca;
// HCCA
hcca->done_head &= 0xfffffffe;
// DoneHead in HCCA
if( (hcca->done_head>>28)==0 )
{
ED[1].Format &= 0xF800FFFF;
ED[1].Format |= (((__u32)DD.MaxPacketSize) << 16);
found++;
}
else
return 0;
sprintf(buf, "\nDescriptor.Length: 0x%x\n", DD.Length
); dbg(buf);
sprintf(buf, "Descriptor.DescriptorType: 0x%02X\n", DD.DescriptorType
); dbg(buf);
sprintf(buf, "Descriptor.USB: 0x%04X\n", DD.USB
); dbg(buf);
sprintf(buf, "Descriptor.DeviceClass: 0x%04X\n", DD.DeviceClass
); dbg(buf);
sprintf(buf, "Descriptor.DeviceSubClass: 0x%04X\n", DD.DeviceSubClass
); dbg(buf);
sprintf(buf, "Descriptor.DeviceProtocol: 0x%04X\n", DD.DeviceProtocol
); dbg(buf);
sprintf(buf, "Descriptor.MaxPacketSize: 0x%04X\n", DD.MaxPacketSize
); dbg(buf);
sprintf(buf, "Descriptor.Vendor: 0x%04X\n", DD.Vendor
); dbg(buf);
sprintf(buf, "Descriptor.ProductID: 0x%04X\n", DD.ProductID
); dbg(buf);
sprintf(buf, "Descriptor.Manufacturer: 0x%04X\n", DD.Manufacturer
); dbg(buf);
sprintf(buf, "Descriptor.ProductIndex: 0x%04X\n", DD.ProductIndex
); dbg(buf);
sprintf(buf, "Descriptor.SerialNumber: 0x%04X\n", DD.SerialNumber
); dbg(buf);
sprintf(buf, "Descriptor.ConfigNumber: 0x%04X\n", DD.ConfigNumber
); dbg(buf);
return (Speed);
}//
End FindDev(..)
.... |
The above function is the first part that enabled me to get the
first details of the HUB....below is the text file of what you should get if you
call that function. Notice that we have the Descriptor Length, Type etc.
We usually call the descriptor first to get its length and that all is okay...we
use a similar set of functions next to get the full descriptor and all the other
descriptors...Device, Config, Interface Descriptors as you'll soon see.
Output File For Above Function |
revision 0x00000010
control 0x00000690
cmdstatus 0x00000000
intrstatus 0x00000046
intrenable 0xc000006b
intrdisable 0xc000006b
ed_periodcurrent 0x00000000
ed_controlhead 0x001c4110
ed_controlcurrent 0x00000000
ed_bulkhead 0x001c4100
ed_bulkcurrent 0x00000000
donehead 0x00000000
fminterval 0x37782edf
fmremaining 0x00000829
periodicstart 0x000004af
lsthresh 0x00000628
ohci_roothub_regs.a 0x01000204
ohci_roothub_regs.b 0x00000002
ohci_roothub_regs.status 0x00000000
ohci_roothub_regs.portstatus[0] 0x00010103
ohci_roothub_regs.portstatus[1] 0x00010100
ohci_roothub_regs.portstatus[2] 0x00010100
ohci_roothub_regs.portstatus[3] 0x00010100
waiting for execution
Descriptor.Length: 0x12
Descriptor.DescriptorType: 0x01
Descriptor.USB: 0x0110
Descriptor.DeviceClass: 0x0009
Descriptor.DeviceSubClass: 0x0000
Descriptor.DeviceProtocol: 0x0000
Descriptor.MaxPacketSize: 0x0008
Descriptor.Vendor: 0x0000
Descriptor.ProductID: 0x0000
Descriptor.Manufacturer: 0x0000
Descriptor.ProductIndex: 0x0000
Descriptor.SerialNumber: 0x0000
Descriptor.ConfigNumber: 0x0000 |
Here is a dump of the output file and the source code for the
Program:
-DownloadSourceCode-
Output: |
revision 0x00000010
control 0x00000690
cmdstatus 0x00000000
intrstatus 0x00000046
intrenable 0xc000006b
intrdisable 0xc000006b
ed_periodcurrent 0x00000000
ed_controlhead 0x001c4110
ed_controlcurrent 0x00000000
ed_bulkhead 0x001c4100
ed_bulkcurrent 0x00000000
donehead 0x00000000
fminterval 0x37782edf
fmremaining 0x00000829
periodicstart 0x000004af
lsthresh 0x00000628
ohci_roothub_regs.a 0x01000204
ohci_roothub_regs.b 0x00000002
ohci_roothub_regs.status 0x00000000
ohci_roothub_regs.portstatus[0] 0x00010103
ohci_roothub_regs.portstatus[1] 0x00010100
ohci_roothub_regs.portstatus[2] 0x00010100
ohci_roothub_regs.portstatus[3] 0x00010100
waiting for execution
Descriptor.Length: 0x12
Descriptor.DescriptorType: 0x01
Descriptor.USB: 0x0110
Descriptor.DeviceClass: 0x0009
Descriptor.DeviceSubClass: 0x0000
Descriptor.DeviceProtocol: 0x0000
Descriptor.MaxPacketSize: 0x0008
Descriptor.Vendor: 0x0000
Descriptor.ProductID: 0x0000
Descriptor.Manufacturer: 0x0000
Descriptor.ProductIndex: 0x0000
Descriptor.SerialNumber: 0x0000
Descriptor.ConfigNumber: 0x0000
fullspeed-device found at Port 0
revision 0x00000010
control 0x00000690
cmdstatus 0x00000000
intrstatus 0x00000046
intrenable 0xc000006b
intrdisable 0xc000006b
ed_periodcurrent 0x00000000
ed_controlhead 0x001c4110
ed_controlcurrent 0x00000000
ed_bulkhead 0x001c4100
ed_bulkcurrent 0x00000000
donehead 0x00000000
fminterval 0x37782edf
fmremaining 0x00000540
periodicstart 0x000004af
lsthresh 0x00000628
ohci_roothub_regs.a 0x01000204
ohci_roothub_regs.b 0x00000002
ohci_roothub_regs.status 0x00000000
ohci_roothub_regs.portstatus[0] 0x00010103
ohci_roothub_regs.portstatus[1] 0x00010100
ohci_roothub_regs.portstatus[2] 0x00010100
ohci_roothub_regs.portstatus[3] 0x00010100
waiting for execution
good to go
waiting for execution
*Descriptor*
Descriptor.Length: 0x12
Descriptor.DescriptorType: 0x01
Descriptor.USB: 0x0110
Descriptor.DeviceClass: 0x0009
Descriptor.DeviceSubClass: 0x0000
Descriptor.DeviceProtocol: 0x0000
Descriptor.MaxPacketSize: 0x0008
Descriptor.Vendor: 0x0451
Descriptor.ProductID: 0x2046
Descriptor.Device: 0x0125
Descriptor.Manufacturer: 0x00
Descriptor.ProductIndex: 0x00
Descriptor.SerialNumber: 0x00
Descriptor.ConfigNumber: 0x01
ConfigNumber:1
waiting for execution
waiting for execution
+Configuration Descriptor+
ConfDesc.Length: 0x09
ConfDescDescriptorType: 0x02
ConfDesc.TotalLength: 0x0019
ConfDesc.NumberofInterfaces: 0x01
ConfDesc.ConfigValue: 0x01
ConfDesc.Configuration: 0x00
ConfDesc.Attributes: 0xE0
ConfDesc.MaxPower: 0x00
waiting for execution
+Configuration Descriptor+
ConfDesc.Length: 0x09
ConfDescDescriptorType: 0x02
ConfDesc.TotalLength: 0x0019
ConfDesc.NumberofInterfaces: 0x01
ConfDesc.ConfigValue: 0x01
ConfDesc.Configuration: 0x00
ConfDesc.Attributes: 0xE0
ConfDesc.MaxPower: 0x00
#Interface Descriptor#
InterDesc.Length: 0x09
InterDesc.DescriptorType: 0x04
InterDesc.Interfacenumber: 0x00
InterDesc.AlternateSetting: 0x00
InterDesc.NumberofEndpoints: 0x01
InterDesc.InterfaceClass: 0x09
InterDesc.InterfaceSubClass: 0x00
InterDesc.InterfaceProtocol: 0x00
InterDesc.InterfaceIndex: 0x00
#EndPointDescriptor Descriptor#
EndPointDes.Length: 0x07
EndPointDes.DescriptorType: 0x05
EndPointDes.EndpointAddress: 0x81
EndPointDes.Attributes: 0x03
EndPointDes.MaxPacketSize: 0x01
EndPointDes.Interval: 0xFF
*NO* device found at Port 1
*NO* device found at Port 2
*NO* device found at Port 3 |
This is a big step....from here we should be able to get the
gamepad descriptors and then...once we have that! Poll for device
changes..new gamepads being plugged in/unplugged...buttons being pressed
etc.....which is what we hope to do next!
Step three....we found the gamepad?
Wahoo....Well it just shows that if you stick with it, that the
evil xbox will do our bidding, and give us what we want to see! After many
hours of code hacking and crashing...reseting the xbox etc....I got these few
wonderful lines of txt:
--gamepad_0---
*Descriptor*
Descriptor.Length: 0x12
Descriptor.DescriptorType: 0x01
Descriptor.USB: 0x0110
Descriptor.DeviceClass: 0x0000
Descriptor.DeviceSubClass: 0x0000
Descriptor.DeviceProtocol: 0x0000
Descriptor.MaxPacketSize: 0x0040
Descriptor.Vendor: 0x045E
Descriptor.ProductID: 0x0202
Descriptor.Device: 0x0100
Descriptor.Manufacturer: 0x00
Descriptor.ProductIndex: 0x00
Descriptor.SerialNumber: 0x00
Descriptor.ConfigNumber: 0x01 |
What on earth is that? Well its our gamepad
descriptor....not the hub...the gamepad :) As with a lot of effort I
managed to set the gamepad usb address. Most of the code is just bullying
the hub into giving us information - so I put this code in hub.cpp/.h.
Again its a rough version of the code at this point...a lot of
redundant functions and the code could deal with tidying up a bit. But
where so close! Very very close. We have our gamepad...its
there...its so close we can feel it.... its just a matter of hopefully a small
piece of code to make it do something! Make it rumble! Shake with
fear at our power...mhahaha...it will be our slave..hehe
We can now detect how many usb gamepads are plugged into our
xbox....I've made the assumption that 1 gamepad is plugged in, and we'll use
that test gamepad as our guinipig - where we'll try and send and receive some
small bulk messages.
DownloadCode |
Now I'm not going to paste the whole code yet...not until
I've tidied it up and got ride of so much redundant code...but you can take
a looksy and see what I've done now...maybe you might figure out how the
bulk msg can be send with a basic rumble data?
DownloadCode
|
Step Four: Bulk Message! Lets see you
rumble Gamepad
Its not easy! But we now need to send a bulk message to
our gamepad so that it does somethign...its okay having all this information
about hubs and gamepad id's etc...but we want results! So our next big
mission is to make our gamepad vibrate...mhahahah..... I know we can do it
if we put our minds to it.
Its all a matter of sending a bulk msg with our ohci registers
to the correct address. We've got our device descriptors, which don't
change - with this information at hand we can rull the xbox gamepad!
I've implemented TWO versions of the code....as the usb gamepad
code will work on both the xdk and the openxdk without any changes...its that
nice. It works...fully...we can send and get information from the USB!
I've done it so it dumps the contents the the data we get back from the gamepad...the
first one it finds on the screen. So as you press buttons you can see
it!...woowWWW. Also if you press the 'A' button hard...it will start the
gamepad rummbling....wahoo...coolll
~Code~
Download OpenXDK GamePad Code |
Download
XDK GamePad Code |
Includes the full OpenXDK library's as 2 of the files have
been changed mm.cpp/mm.h which where uncommented so we could use the
GetPhscialMemory(..) location call. |
Has a single file dx.cpp which uses the xdk lib's to display
gamepad information on the screen so we can see that its all working. |
Now there was of couse a few bumps on the road to success!
And I can't say how great to see a great number of linux ohci/usb examples codes
on the net. It wasnt' easy though - as my code doesn't rely on interrupts,
instead it polls the ohci regitsers for data. We could improve this now by
adding interrupt feedback, but for the xbox our game loop is running at 50-60
fps, so we don't mind polling our gamepad at this stage.
I've stuck all that sweet input code in a folder....xinput....and
all we have to do now is include the "#include "xinput/xinput.h" and where ready
to go. For the openxdk, its probably better if we build a library and just
add it...e.g. xinput.lib...and a xinput.h file :) But we can do that
later! We have a gamepad working...Wahoooo
Data is read from the gamepad into a structure which is defined
at the top of pad.h...and it looks like this:
Code Snippet: pad.h |
...
struct
stXPAD
// Packed to 1 byte alignment.
{
char reserved1;
unsigned char
structsize;
char pad;
/* 1 up 2 down 4 left 8 right */
char reserved2;
unsigned char
keys[6]; /* A B X Y Black White */
unsigned char
trig_left;
unsigned char
trig_right;
short stick_left_x;
short stick_left_y;
short stick_right_x;
short stick_right_y;
char padding[0x40];
};
... |
You don't need that padding on the end....but I put that there
just in-case I added something to the stXPAD structure later on. The
structure is pretty self explanatory...but we will of course add some binary
defines, so we can more easily test for inputs....but its easily done from here.
Remember now, if you run up the code...press the 'A' button to
start the rumble effect so that you can really be sure of our great gamepads
power! Our next goal...is to tidy up the internal workings...so we can
check for additional devices...web cam's...multiple usb devices...and most of
all for connection and reconnection of usb things... ;)
If anyone gets any further with this, please feel free to give
me feedback anytime: bkenwright@xbdev.net
_______________________________________________________________
References / Resources
PCI
Programmable
Interrupt Controller Programming
XBOX Linux PCI Overview
PCI
Configuration Headers/Tutorial
xbox.sparcy.net
XBOX USB
Iniside
the XBOX Controller
OHCI
Step-by-step
OHCI setup
OHCI
Official Specification Document
Misc
OpenXDK Home
Page
Msg Board Posting
|