//------------------------------------------------------------------- // dev2desc.cpp // // Here is an initial experiment with programming of the Intel // USB 2.0 Enhanced Host Controller Interface within the Linux // environment of an x86 PC computing platform. Specifically, // a 'Get_Device_Descriptor' control transfer for a High-Speed // device is requested by the Host Controller and, if provided // by the attached device, its parameter-values are displayed. // (A 'default' device-address of 2 will be overridden in case // a command-line argument is supplied by the user.) To guide // our understanding of the EHCI's behavior, the EHCI register // values and the TD and QH data-structures will be displayed. // // to compile: $ g++ dev2desc.cpp -o dev2desc // to execute: $ ./dev2desc // // NOTE: Requires installing our 'ehci.c' device-driver module. // // programmer: ALLAN CRUSE // written on: 16 MAR 2010 // correction: 29 MAR 2010 -- to remove STALL on handshake PID //------------------------------------------------------------------- #include // for printf(), perror() #include // for open() #include // for exit() #include // for lseek() #include // for memset() #include // for signal() #include // for mmap() #include // for ioctl() #define EHCI_BASE 0x80000 // suitable address for i/o mapping #define PAGE_SIZE 0x1000 // memory-granularity on x86 system typedef struct { unsigned char bLength; unsigned char bDescriptorType; unsigned short bcdUSB; unsigned char bDeviceClass; unsigned char bDeviceSubClass; unsigned char bDeviceProtocol; unsigned char bMaxPacketSize0; unsigned short idVendor; unsigned short idProduct; unsigned short bcdDevice; unsigned char iManufacturer; unsigned char iProduct; unsigned char iSerialNumber; unsigned char bNumConfigurations; } USB_DD; // USB Device Descriptor typedef struct { unsigned char bmReqType; unsigned char bRequest; unsigned short wValue; unsigned short wIndex; unsigned short wLength; } USB_RB; // USB Request Block typedef struct { unsigned int qhHorizontalLink; unsigned int qhEndPtCharacteristics; unsigned int qhEndPtCapabilities; unsigned int qhCurrentTDPtr; unsigned int qhNextTDPtr; unsigned int qhAltNextTDPtr; unsigned char qhStatus; unsigned char qhAction; unsigned short qhTotalBytes; unsigned int qhBufferPtr[5]; unsigned int qhExtBufferPtr[5]; unsigned int qhPaddng[15]; } EHCI_QH64; typedef struct { unsigned int tdNextTDPtr; unsigned int tdAltNextTDPtr; unsigned char tdStatus; unsigned char tdAction; unsigned short tdTotalBytes; unsigned int tdBufferPointer[5]; unsigned int tdExtBufferPointer[5]; unsigned int tdPadding[3]; } EHCI_TD64; void display_TD64( int index ); void display_QH64( int index ); char devname[] = "/dev/ehci"; unsigned long kmem_phys; EHCI_QH64 *qh = (EHCI_QH64*)(EHCI_BASE + PAGE_SIZE * 1); EHCI_TD64 *td = (EHCI_TD64*)(EHCI_BASE + PAGE_SIZE * 2); USB_RB *rb = (USB_RB*)(EHCI_BASE + PAGE_SIZE * 3); USB_DD *dd = (USB_DD*)(EHCI_BASE + PAGE_SIZE * 4); unsigned int *lp = (unsigned int*)EHCI_BASE; unsigned int usb2cmd, usb2sts, usb2intr, asynclistaddr; unsigned int qh_phys, td_phys, rb_phys, bf_phys; unsigned int dev = 2; // default value for device-address void my_sigint_handler( int signo ) { exit(1); } void display_EHCI_registers( void ) { printf( "\n" ); printf( " USB2CMD=%08X ", lp[ 0x20 >> 2 ] ); printf( " USB2STS=%08X ", lp[ 0x24 >> 2 ] ); printf( " USB2INTR=%08X ", lp[ 0x28 >> 2 ] ); printf( " ASYNCLISTADDR=%08X ", lp[ 0x38 >> 2 ] ); printf( "\n" ); } void restore_asynclist_address( void ) { lp[ 0x20 >> 2 ] &= ~(1<<5); // disable asynchronous schedule while ( (lp[ 0x24 >> 2 ] & (1<<15)) != 0 ); // wait till done lp[ 0x38 >> 2 ] = asynclistaddr; // restore initial value usb2sts = lp[ 0x24 >> 2 ]; // read current status lp[ 0x24 >> 2 ] = usb2sts; // reset write-to-clears lp[ 0x28 >> 2 ] = usb2intr; // restore initial value lp[ 0x20 >> 2 ] = usb2cmd; // restore initial value display_EHCI_registers(); // show register-values } int main( int argc, char **argv ) { // open the device-file for reading and writing int fd = open( devname, O_RDWR ); if ( fd < 0 ) { perror( devname ); exit(1); } int size = lseek( fd, 0, SEEK_END ); printf( "\nOpened the device-file \'%s\' ", devname ); printf( "(size = 0x%0X bytes) \n", size ); // initialize our physical region addresses if ( ioctl( fd, 0, &kmem_phys ) < 0 ) { perror( "ioctl" ); exit(1); } qh_phys = kmem_phys + PAGE_SIZE * 0; td_phys = kmem_phys + PAGE_SIZE * 1; rb_phys = kmem_phys + PAGE_SIZE * 2; bf_phys = kmem_phys + PAGE_SIZE * 3; printf( "Kernel memory region begins at " ); printf( "physical address 0x%08lX \n", kmem_phys ); // let user override our 'default' device-address if ( argc > 1 ) dev = atoi( argv[1] ); printf( "USB Device-Address = %u \n", dev ); if ( dev > 127 ) { printf( "Invalid address\n" ); exit(1); } // map EHCI registers and kernel-memory region into userspace int prot = PROT_READ | PROT_WRITE; int flag = MAP_FIXED | MAP_SHARED; void *mm = (void*)EHCI_BASE; if ( mmap( mm, size, prot, flag, fd, 0 ) == MAP_FAILED ) { perror( "mmap" ); exit(1); } // save some initial register-values to be restored later usb2cmd = lp[ 0x20 >> 2 ]; usb2sts = lp[ 0x24 >> 2 ]; usb2intr = lp[ 0x28 >> 2 ]; asynclistaddr = lp[ 0x38 >> 2 ]; display_EHCI_registers(); getchar(); // insure that asynclistaddr will be restored upon exit atexit( restore_asynclist_address ); signal( SIGINT, my_sigint_handler ); // setup td[0] as an 'inactive' Transaction Descriptor memset( &td[0], 0x00, sizeof( EHCI_TD64 ) ); td[0].tdNextTDPtr = 0x00000001; td[0].tdAltNextTDPtr = 0x00000001; td[0].tdStatus = 0x40; // halted td[0].tdAction = 0x00; td[0].tdTotalBytes = 0x0000; // setup qh[0] as an 'empty' Queue-Head structure memset( &qh[0], 0x00, sizeof( EHCI_QH64 ) ); qh[0].qhHorizontalLink = qh_phys | (1<<1); // self qh[0].qhEndPtCharacteristics = 0x00008000; // H=1 qh[0].qhEndPtCapabilities = 0x00000000; qh[0].qhCurrentTDPtr = 0x00000000; qh[0].qhNextTDPtr = 0x00000001; qh[0].qhAltNextTDPtr = td_phys | (0<<1); qh[0].qhStatus = 0x40; qh[0].qhAction = 0x00; qh[0].qhTotalBytes = 0x0000; // exhibit these two initial structures display_QH64( 0 ); display_TD64( 0 ); getchar(); // activate our 'empty' asynchronous schedule lp[ 0x20 >> 2 ] &= ~(1<<5); // USB2CMD while ( ( lp[ 0x24 >> 2 ] & (1<<15) ) != 0 ); // USB2STS lp[ 0x38 >> 2 ] = qh_phys | (1<<1); // ASYNCLISTADDR lp[ 0x20 >> 2 ] |= (1<<5); // USB2CMD while ( ( lp[ 0x24 >> 2 ] & (1<<15) ) == 0 ); // USB2STS display_EHCI_registers(); getchar(); // construct structures for 'Get_Device_Descriptor' rb[0].bmReqType = 0x80; rb[0].bRequest = 0x06; rb[0].wValue = 0x0100; rb[0].wIndex = 0x0000; rb[0].wLength = 0x0012; memset( &td[1], 0x00, sizeof( EHCI_TD64 ) ); td[1].tdNextTDPtr = td_phys + 2*sizeof( EHCI_TD64 ); td[1].tdAltNextTDPtr = 0x00000001; td[1].tdStatus = 0x80; td[1].tdAction = 0x0E; td[1].tdTotalBytes = 0x0008 | (0<<15); // DATA0 td[1].tdBufferPointer[0] = rb_phys; memset( &td[2], 0x00, sizeof( EHCI_TD64 ) ); td[2].tdNextTDPtr = td_phys + 3*sizeof( EHCI_TD64 ); td[2].tdAltNextTDPtr = 0x00000001; td[2].tdStatus = 0x80; td[2].tdAction = 0x0D; td[2].tdTotalBytes = 0x0012 | (1<<15); // DATA1 td[2].tdBufferPointer[0] = bf_phys; memset( &td[3], 0x00, sizeof( EHCI_TD64 ) ); td[3].tdNextTDPtr = 0x00000001; td[3].tdAltNextTDPtr = 0x00000001; td[3].tdStatus = 0x80; td[3].tdAction = 0x0C; td[3].tdTotalBytes = 0x0000 | (1<<15); // DATA1 memset( &qh[1], 0x00, sizeof( EHCI_QH64 ) ); qh[1].qhHorizontalLink = qh_phys | (1<<1); // qh#0 qh[1].qhEndPtCharacteristics = 0x40406000 | dev; // H=0, DTC=1, HS, dev qh[1].qhEndPtCapabilities = 0x40000000; qh[1].qhCurrentTDPtr = 0x00000000; qh[1].qhNextTDPtr = td_phys + 1*sizeof( EHCI_TD64 ); qh[1].qhAltNextTDPtr = td_phys | (0<<1); qh[1].qhStatus = 0x00; qh[1].qhAction = 0x00; qh[1].qhTotalBytes = 0x0000; // exhibit this new Queue-Head and its linked-list of TDs display_QH64( 1 ); display_TD64( 1 ); display_TD64( 2 ); display_TD64( 3 ); getchar(); // overwrite any previous data in the destination buffer memset( dd, 0xFF, sizeof( USB_DD ) ); // finish the insertion of QH#1 into our circular list qh[0].qhHorizontalLink = qh_phys + 1*sizeof( EHCI_QH64 ) | (1<<1); // allow time for the EHCI controller to traverse its schedule usleep( 5000 ); // delay for 5 milliseconds // show the resulting changes to our QH and TD data-structures display_QH64( 1 ); display_TD64( 1 ); display_TD64( 2 ); display_TD64( 3 ); getchar(); // exit if a valid USB Device Descriptor was not returned if ( dd->bDescriptorType != 1 ) { printf( "Device Descriptor not obtained\n" ); exit(1); } // otherwise show that Device Descriptor's parameters printf( "\n" ); printf( "Parameters from the USB Device Descriptor " ); printf( "at physical address 0x%08X: \n", bf_phys ); printf( " DeviceClass=%02X ", dd->bDeviceClass ); printf( " DeviceSubClass=%02X ", dd->bDeviceSubClass ); printf( " DeviceProtocol=%02X ", dd->bDeviceProtocol ); printf( " MaxPacketSize0=%u ", dd->bMaxPacketSize0 ); printf( "\n" ); printf( " VendorID=%04X ", dd->idVendor ); printf( " ProductID=%04X ", dd->idProduct ); printf( " ReleaseID=%X.%X ", dd->bcdDevice>>8, dd->bcdDevice&0xFF ); printf( " iMfg=%u ", dd->iManufacturer ); printf( " iProd=%u ", dd->iProduct ); printf( " iS/N=%u ", dd->iSerialNumber ); printf( "\n" ); printf( " NumConfigurations=%u ", dd->bNumConfigurations ); printf( " USBspec=%X.%X ", dd->bcdUSB>>8, dd->bcdUSB&0xFF ); printf( "\n\n" ); } void display_QH64( int index ) { unsigned int qh_addr = qh_phys + index * sizeof( EHCI_QH64 ); unsigned int *ep = (unsigned int*)&qh[ index ]; printf( "\n Queue-Head Descriptor #%d ", index ); printf( "at physical address 0x%08X:", qh_addr ); for (int i = 0; i < 17; i++) { if ( i == 0 ) printf( "\n" ); if (( i == 7 )||( i == 12 )) printf( "\n " ); printf( " %08X", ep[i] ); } printf( "\n\n" ); } void display_TD64( int index ) { unsigned int td_addr = td_phys + index * sizeof( EHCI_TD64 ); unsigned int *ep = (unsigned int*)&td[ index ]; printf( "\n Transaction Descriptor #%d ", index ); printf( "at physical address 0x%08X:", td_addr ); for (int i = 0; i < 13; i++) { if ( i == 0 ) printf( "\n " ); if (( i == 3 )||( i == 8 )) printf( "\n " ); printf( " %08X", ep[i] ); } printf( "\n" ); } // References: // Enhanced Host Controller Interface Specification, Intel (2002) // I/O Controller Hub 9 (ICH9) Family Datasheet, Intel (2008)