//------------------------------------------------------------------- // tryuhci.cpp // // This is our initial experiment with programming of the Intel // UHCI controller from user-space (using functions implemented // by our 'uhci.c' device-driver for gaining access to I/O-port // and kernel-memory resources). For one of the installed UHCI // controllers, and for a specified USB Device-address, we will // attempt execution a 'Get_Device_Descriptor' control-transfer // and (if it succeeds) display that descriptor's field-values. // The setup used here assumes a 'Low-Speed' device is attached // (such as a USB mouse or USB keyboard) requiring use of three // 'IN' transfers of at most 8 bytes each to obtain the full 18 // bytes that comprise a 'device-descriptor'. We encourage the // student to experiment with this code, by replacing the self- // referencing horizontal-link used here in Queue-Head #1 by an // 'invalid' link (bit #0 set to 1), and by changing the VF-bit // to zero to specify a 'breadth first' schedule-traversal (and // observe the resulting effects which get displayed onscreen). // We mention that command-line arguments can be supplied which // override our 'default' device-address and UHCI hub-number; a // pseudo-file '/proc/uhci' shows hubs with no device attached. // // to compile: $ g++ tryuhci.cpp -o tryuhci // to execute: $ ./tryuhci // // NOTE: This program relies on installing our 'uhci.c' module. // // programmer: ALLAN CRUSE // date begun: 20 MAR 2010 // completion: 24 MAR 2010 //------------------------------------------------------------------- #include // for printf(), perror() #include // for open() #include // for exit() #include // for lseek() #include // for signal() #include // for memset() #include // for mmap() #include // for ioctl() #include // for inw(), outw() #define KMEM_BASE 0x70000 // suitable address for memory-map #define PAGE_SIZE 4096 // memory-granularity on x86 system #define QH (1<<1) // descriptor-flag for 'queue-head' #define VF (1<<2) // descriptor-flag for 'depth-first' 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 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 int tdNextTD; unsigned int tdStatus; unsigned int tdAction; unsigned int tdBuffer; } UHCI_TD; // UHCI Tranfer Descriptor typedef struct { unsigned int qhHorzLink; unsigned int qhVertLink; unsigned int qhPadding0; unsigned int qhPadding1; } UHCI_QH; // UHCI Queue Head void display_TD( int index ); void display_QH( int index ); char devname[] = "/dev/uhci"; unsigned long hub_count; unsigned long port_addr; unsigned long kmem_phys; unsigned int *framelist = (unsigned int*)(KMEM_BASE); UHCI_QH *qh = (UHCI_QH *)(KMEM_BASE + PAGE_SIZE * 1); UHCI_TD *td = (UHCI_TD *)(KMEM_BASE + PAGE_SIZE * 2); USB_RB *rb = (USB_RB *)(KMEM_BASE + PAGE_SIZE * 4); USB_DD *dd = (USB_DD *)(KMEM_BASE + PAGE_SIZE * 5); unsigned int qh_phys, td_phys, rb_phys, dd_phys, frbaseaddr; unsigned short usbcmd, usbsts, usbintr, frnum, portsc0, portsc1; unsigned int io, dev = 2, hub=1; void my_sigint_handler( int signo ) { exit(1); } void display_UHCI_registers( void ) { printf( "\n" ); printf( " USBCMD=%04X ", inw( io + 0 ) ); printf( " USBSTS=%04X ", inw( io + 2 ) ); printf( " USBINTR=%04X ", inw( io + 4 ) ); printf( " FRNUM=%04X ", inw( io + 6 ) ); printf( " FRBASEADDR=%08X ", inl( io + 8 ) ); printf( "\n" ); printf( " PORTSC0=%04X ", inw( io + 16 ) ); printf( " PORTSC1=%04X ", inw( io + 18 ) ); printf( "\n" ); } void restore_framelist_address( void ) { outw( inw( io + 0 ) & ~(1 << 0), io + 0 ); // USBCMD R/S=0 while (( inw( io + 2 ) & (1 << 5)) == 0 ); // USBSTS HCHalted outw( frnum, io + 6 ); // FRNUM outl( frbaseaddr, io + 8 ); // FRBASEADDR usbsts = inw( io + 2 ); // read current status outw( usbsts, io + 2 ); // reset write-to-clear outw( usbintr, io + 4 ); // restore initial value outw( usbcmd, io + 0 ); // restore initial value display_UHCI_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 * 1; td_phys = kmem_phys + PAGE_SIZE * 2; rb_phys = kmem_phys + PAGE_SIZE * 4; dd_phys = kmem_phys + PAGE_SIZE * 5; printf( "Kernel memory region begins at " ); printf( "physical address 0x%08lX \n", kmem_phys ); // let the 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); } // get the number of UHCI controllers if ( ioctl( fd, 1, &hub_count ) < 0 ) { perror( "ioctl" ); exit(1); } // let the user override our 'default' UHCI hub-number if ( argc > 2 ) hub = atoi( argv[ 2 ] ); printf( "UHCI controller number = %u \n", hub ); if (( hub < 1 )||( hub > hub_count )) { printf( "Invalid hub\n" ); exit(1); } // get this controller's i/o-port address port_addr = hub; if ( ioctl( fd, 1, &port_addr ) < 0 ) { perror( "ioctl" ); exit(1); } printf( "I/O-Port Base Address = 0x%04lX \n", port_addr ); // map the kernel memory into userspace int prot = PROT_READ | PROT_WRITE; int flag = MAP_FIXED | MAP_SHARED; void *mm = (void*)KMEM_BASE; if ( mmap( mm, size, prot, flag, fd, 0 ) == MAP_FAILED ) { perror( "mmap" ); exit(1); } // save some initial register-values to be restored later io = port_addr; usbcmd = inw( io + 0 ); usbsts = inw( io + 2 ); usbintr = inw( io + 4 ); frnum = inw( io + 6 ); frbaseaddr = inl( io + 8 ); portsc0 = inw( io + 16 ); portsc1 = inw( io + 18 ); // let user see the initial UHCI register-values display_UHCI_registers(); // insure that framelist will be restored upon exit atexit( restore_framelist_address ); signal( SIGINT, my_sigint_handler ); // setup td[0] as an 'inactive' transfer descriptor td[0].tdNextTD = 0x00000001; td[0].tdStatus = 0x00000000; td[0].tdAction = 0xFFE07F69; td[0].tdBuffer = 0x00000000; // setup qh[0] as an 'empty' Queue-Head descriptor qh[0].qhHorzLink = 0x00000001; qh[0].qhVertLink = td_phys; qh[0].qhPadding0 = 0xFFFFFFFF; qh[0].qhPadding1 = 0xFFFFFFFF; // display these two initial data-structures display_TD( 0 ); display_QH( 0 ); getchar(); // initialize the framelist for (int i = 0; i < 1024; i++) framelist[i] = qh_phys | QH; // activate our UHCI schedule while ( inw( io + 6 ) ); // FRNUM == 0 ? outw( inw( io + 0 ) & ~(1<<0), io + 0 ); // USBCMD R/S = 0 while ( (inw( io + 2 ) & (1 << 5)) == 0 ); // USBSTS HCHalted frnum = inw( io + 6 ); // FRNUM outl( qh_phys | (1<<1), io + 8 ); // FRBASEADDR outw( 0x0000, io + 6 ); // FRNUM outw( inw( io + 0 ) | (1<<0), io + 0 ); // USBCMD R/S = 1 while ( (inw( io + 2 ) & (1 << 5)) != 0 ); // USBSTS HCHalted // let user see the modified UHCI register-values display_UHCI_registers(); getchar(); // overwrite any previous data in the destination buffer memset( dd, 0xFF, sizeof( USB_DD ) ); // construct data-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; td[1].tdNextTD = td_phys + 2 * sizeof( UHCI_TD ) | VF; td[1].tdStatus = 0x1C800000; // Low-Speed, Active td[1].tdAction = 0x00E0002D | (dev<<8); // SETUP, MaxLen=8 td[1].tdBuffer = rb_phys; // Request Block #0 td[2].tdNextTD = td_phys + 3 * sizeof( UHCI_TD ) | VF; td[2].tdStatus = 0x1C800000; // Low-Speed, Active td[2].tdAction = 0x00E80069 | (dev<<8); // IN, MaxLen=8 td[2].tdBuffer = dd_phys + 8*0; // Buffer #0 + 8*0 td[3].tdNextTD = td_phys + 4 * sizeof( UHCI_TD ) | VF; td[3].tdStatus = 0x1C800000; // Low-Speed, Active td[3].tdAction = 0x00E00069 | (dev<<8); // IN, MaxLen=8 td[3].tdBuffer = dd_phys + 8*1; // Buffer #0 + 8*1 td[4].tdNextTD = td_phys + 5 * sizeof( UHCI_TD ) | VF; td[4].tdStatus = 0x1C800000; // Low-Speed, Active td[4].tdAction = 0x00E80069 | (dev<<8); // IN, MaxLen=8 td[4].tdBuffer = dd_phys + 8*2; // Buffer #0 + 8*2 td[5].tdNextTD = 0x00000001; td[5].tdStatus = 0x1C800000; // Low-Speed, Active td[5].tdAction = 0xFFE800E1 | (dev<<8); // OUT, MaxLen=0 td[5].tdBuffer = 0x00000000; // Buffer = NULL // this Queue-Head's horizontal-link points to itself qh[1].qhHorzLink = qh_phys + 1 * sizeof( UHCI_QH ) | QH; qh[1].qhVertLink = td_phys + 1 * sizeof( UHCI_TD ) | VF; qh[1].qhPadding0 = 0xFFFFFFFF; qh[1].qhPadding1 = 0xFFFFFFFF; // show these descriptors BEFORE they are executed printf( "\e[H\e[J\n" ); // home the cursor and clear the screen for (int i = 1; i < 6; i++) display_TD( i ); display_QH( 1 ); printf( "Attaching Queue-Head... " ); fflush( stdout ); sleep( 1 ); while ( inw( io + 6 ) ); printf( "now.\n" ); // add this new Queue-Head to our schedule unsigned int elemptr = qh_phys + 1 * sizeof( qh_phys ) | QH; framelist[ 0 ] = elemptr; again: // check TDs' status-bytes for completions or stalls int count = 0; for (int i = 1; i < 6; i++) if ( ( td[i].tdStatus & 0x00FF0000 ) == 0 ) ++count; // show these descriptors WHILE they are executing printf( "\e[H\n" ); for (int i = 1; i < 6; i++) display_TD( i ); display_QH( 1 ); if ( count < 5 ) goto again; else printf( "\nDone\n" ); 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( "Parameters from the USB Device Descriptor " ); printf( "at physical address 0x%08X: \n", dd_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" ); getchar(); } void display_TD( int index ) { unsigned int td_addr = td_phys + index * sizeof( UHCI_TD ); unsigned int *ep = (unsigned int*)&td[ index ]; printf( "\n Transfer Descriptor #%d ", index ); printf( "at physical address 0x%08X: ", td_addr ); for (int i = 0; i < 4; i++) { if ( i == 0 ) printf( "\n " ); printf( " %08X ", ep[i] ); } printf( "\n" ); } void display_QH( int index ) { unsigned int qh_addr = qh_phys + index * sizeof( UHCI_QH ); 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 < 2; i++) { if ( i == 0 ) printf( "\n " ); printf( " %08X ", ep[i] ); } printf( "\n\n" ); }