//------------------------------------------------------------------- // xhcidemo.cpp // // This example illustrates direct hardware programming of the // USB 3.0 Extensible Host Controller on our PCIe adapter-card // when our Delcom USB Visual Indicator is attached to a port: // its blue lamp remains illuminated until -key is hit. // // to compile: $ g++ xhcidemo.cpp -o xhcidemo // to execute: $ ./xhcidemo // // NOTE: Our 'usb3.c' kernel-module needs to be pre-installed. // // programmer: ALLAN CRUSE // date begun: 09 OCT 2010 // completion: 13 OCT 2010 // revised on: 03 DEC 2010 -- to improve event-ring's display //------------------------------------------------------------------- #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 XHCIBASE 0x80000 // suitable address for io-mapping #define XHCISIZE 0x02000 // length of xHCI's register-space #define SLOTS_EN 2 // number of device-slots enabled typedef struct { unsigned long long data; unsigned int stat; unsigned int ctrl; } TRB; // Transfer Request Block typedef struct { unsigned long long addr; unsigned long long elts; } STE; // Segment Table Entry unsigned int *lp = (unsigned int *)XHCIBASE; // capabilities unsigned int *op, *rt, *db; // operationals, runtimes, doorbells unsigned int n_slots; // maximum number of device slots unsigned int n_intrs; // maximum number of interrupters unsigned int n_ports; // maxcimum number of ports char devname[] = "/dev/xhci"; // name of driver device-node unsigned long kmem_phys = 0; // physical address of memory arena unsigned char *kmem = (unsigned char *)( XHCIBASE + XHCISIZE ); unsigned int *dc1 = (unsigned int *)( kmem + 0x0000 ); unsigned int *dc2 = (unsigned int *)( kmem + 0x0800 ); unsigned long long *dcbaa = (unsigned long long *)( kmem + 0x1000 ); unsigned long dc1_phys, dc2_phys, dcbaa_phys, inctx_phys; unsigned int *ic = (unsigned int *)( kmem + 0x2000 ); TRB *cring = (TRB *)( kmem + 0x2800 ); TRB *ering = (TRB *)( kmem + 0x3000 ); STE *erstb = (STE *)( kmem + 0x3800 ); TRB *tring = (TRB *)( kmem + 0x4000 ); unsigned long cring_phys, ering_phys, erstb_phys; unsigned long tring_phys, dd_phys, delc_phys; unsigned char *dd = (unsigned char *)( kmem + 0x5000 ); unsigned long long *delcom = (unsigned long long *)( kmem + 0x6000 ); void show_operational_registers( void ); void show_port_status_registers( void ); void show_runtime_registers( void ); void show_device_control_base_address_array( unsigned long long * ); void show_input_device_context( void ); void show_event_ring_entries( void ); void show_command_ring_entries( void ); void reset_xhci_controller( void ); void my_sigint_handler( int signo ) { exit(1); } int main( int argc, char **argv ) { // open the device-file for reading and writing int xd = open( devname,O_RDWR ); if ( xd < 0 ) { perror( devname ); exit(1); } // map the XHCI registers to user-space int size = lseek( xd, 0, SEEK_END ); int prot = PROT_READ | PROT_WRITE; int flag = MAP_FIXED | MAP_SHARED; void *mm = (void *)XHCIBASE; if ( mmap( mm, size, prot, flag, xd, 0 ) == MAP_FAILED ) { perror( "mmap" ); exit(1); } // initialize our kernel memory arena memset( kmem, 0x00, size - XHCISIZE ); // initialize our pointers to XHCI registers op = (unsigned int *)( XHCIBASE + (lp[ 0x00 >> 2 ] & 0xFF) ); rt = (unsigned int *)( XHCIBASE + lp[ 0x18 >> 2 ] ); db = (unsigned int *)( XHCIBASE + lp[ 0x14 >> 2 ] ); // insure xhci controller gets reset upon exit atexit( reset_xhci_controller ); signal( SIGINT, my_sigint_handler ); // setup our kernel-memory data-structures if ( ioctl( xd, 0, &kmem_phys ) < 0 ) { perror( "ioctl" ); exit(1); } dc1_phys = kmem_phys + 0x0000; dc2_phys = kmem_phys + 0x0800; dcbaa[ 0 ] = 0; dcbaa[ 1 ] = dc1_phys; dcbaa[ 2 ] = dc2_phys; dcbaa_phys = kmem_phys + 0x1000; inctx_phys = kmem_phys + 0x2000; cring_phys = kmem_phys + 0x2800; ering_phys = kmem_phys + 0x3000; erstb_phys = kmem_phys + 0x3800; erstb[0].addr = ering_phys; erstb[0].elts = 32; tring_phys = kmem_phys + 0x4000; dd_phys = kmem_phys + 0x5000; delc_phys = kmem_phys +0x6000; // initialize our counters unsigned int hcsparams1 = lp[ 0x04 >> 2 ]; n_slots = (hcsparams1 >> 0) & 0x0FF; n_intrs = (hcsparams1 >> 8) & 0x7FF; n_ports = (hcsparams1 >>24) & 0x0FF; // show initial register-state show_operational_registers(); show_port_status_registers(); show_runtime_registers(); // show our data-structures show_device_control_base_address_array( &dcbaa[0] ); show_input_device_context(); // initialize the XHCI controller while ( op[ 0x04 >> 2 ] & (1 << 11) ); // spin until CNR==0 op[ 0x38 >> 2 ] = SLOTS_EN; // SlotsEnable = 2 op[ 0x30 >> 2 ] = dcbaa_phys; // DCBAAP register op[ 0x18 >> 2 ] = cring_phys | 1; // CRCR - low op[ 0x1C >> 2 ] = 0; // CRCR - high rt[ 0x20 >> 2 ] = (1 << 1); // IMAN IE-bit set rt[ 0x24 >> 2 ] = 0; // IMOD no throttling rt[ 0x28 >> 2 ] = 1; // ERSTSZ = 1 rt[ 0x38 >> 2 ] = ering_phys; // ERSTDP start rt[ 0x30 >> 2 ] = erstb_phys; // ERSTBA - low ebables! rt[ 0x34 >> 2 ] = 0; // ERSTBA - high op[ 0x00 >> 2 ] = op[ 0x00 ] | (1<<0); // USBCMD Run/Stop = 1 while ( op[ 0x04 >> 2 ] & (1 << 0 ) ); // USBSTS HCH==0? // setup 'Enable Slot' command cring[0].data = 0; cring[0].stat = 0; cring[0].ctrl = (9<<10)|1; // ring the doorbell db[ 0 ] = 0; usleep( 1000 ); // enable root ports for (int i = 0; i < n_ports; i++) { unsigned int index = (0x400 + 16*i) >> 2; unsigned int portsc = op[ index ]; printf( "\n port #%X: ", i+1 ); op[ index ] = portsc; // clear WC-bits if ( (portsc & (1<<0)) == 0 ) { printf( " unconnected " ); continue; } if ( (portsc & (1<<1)) != 0 ) { printf( " already enabled "); continue; } printf( " resetting... " ); op[ index ] |= (1 << 4); // set PR-bit // wait for PRC==1, PR==0, PED==1, PLS==0 do { portsc = op[ index ]; } while ( ( portsc & 0x002001F2 ) != 0x00200002 ); printf( "done " ); } printf( "\n" ); show_port_status_registers(); show_runtime_registers(); getchar(); show_event_ring_entries(); //---------------------------------------- // Next is the 'Address_Device' operation //---------------------------------------- // initialize input device context for 'Address Enable' ic[ 0 ] = 0; ic[ 1 ] = 3; // Initialize the Input Slot Context unsigned int *sc = (unsigned int *)&ic[8]; unsigned int portsc = op[ (0x400 + 2*16) >> 2 ]; printf( "--- PORTSC%X=%08X --- \n\n", 3, portsc ); unsigned int speed = (portsc >> 10)&0xF; sc[0] = (1 << 27) | (speed << 20); // context entries speed sc[1] = (3 << 16); // Initialize the Input Endpoint 0 Context unsigned int *ec = (unsigned int *)&ic[16]; ec[1] = (8 << 16) | (4 << 3) | (3 << 1); ec[2] = tring_phys | 1; show_input_device_context(); // Setup the 'Address_Device' command TRB unsigned int dev = 1; cring[1].data = inctx_phys; cring[1].stat = 0; cring[1].ctrl = (dev<<24)|(11<<10)|1; // ring the doorbell db[0] = 0; usleep( 1000 ); // setupthe Delcom Visual Indicator commands delcom[0] = 0x00040C65; // turn on 'blue' delcom[1] = 0x04000C65; // turn off 'blue' // setup the Transfer TRBs tring[0].data = 0; tring[0].stat = 0; tring[0].ctrl = (8<<10)|1; // 'No Op' command // 'Get_Device_Descriptor' control-transfer tring[1].data = 0x0012000001000680; tring[1].stat = 8; tring[1].ctrl = (3<<16)|(2<<10)|(1<<6)|1; // SETUP tring[2].data = dd_phys; tring[2].stat = 18; tring[2].ctrl = (1<<16)|(3<<10)|(1<<2)|1; // DATA tring[3].data = 0; tring[3].stat = 0; tring[3].ctrl = (0<<16)|(4<<10)|(1<<5)|1; // HANDSHAKE // 'Set_Device_Configuration' control-transfer tring[4].data = 0x0000000000010900; tring[4].stat = 8; tring[4].ctrl = (0<<16)|(2<<10)|(1<<6)|1; // SETUP tring[5].data = 0; tring[5].stat = 0; tring[5].ctrl = (1<<16)|(4<<10)|(1<<5)|1; // HANDSHAKE // 'Set_Interface_Configuration' control-transfer tring[6].data = 0x0008000000000921; tring[6].stat = 8; tring[6].ctrl = (2<<16)|(2<<10)|(1<<6)|1; // SETUP tring[7].data = delc_phys + 0; tring[7].stat = 8; tring[7].ctrl = (0<<16)|(3<<10)|(1<<2)|1; // DATA tring[8].data = 0; tring[8].stat = 0; tring[8].ctrl = (1<<16)|(4<<10)|(1<<5)|1; // HANDSHAKE // reserve space for a 'No Op' command tring[9].data = 0; tring[9].stat = 0; tring[9].ctrl = 0; // 'Set_Interface_Configuration' control-transfer tring[10].data = 0x0008000000000921; tring[10].stat = 8; tring[10].ctrl = (2<<16)|(2<<10)|(1<<6)|1; // SETUP tring[11].data = delc_phys + 8; tring[11].stat = 8; tring[11].ctrl = (0<<16)|(3<<10)|(1<<2)|1; // DATA tring[12].data = 0; tring[12].stat = 0; tring[12].ctrl = (1<<16)|(4<<10)|(1<<5)|1; // HANDSHAKE // ring the doorbell with target = 1 db[1] = 1; // ring doorbell usleep( 5000 ); getchar(); // turn off lamp tring[9].ctrl = (8<<10)|1; // 'No Op' db[1] = 1; // ring doorbell usleep( 5000 ); show_event_ring_entries(); show_command_ring_entries(); } void reset_xhci_controller( void ) { // stop the XHCI controller and reset it op[ 0x00 >> 2 ] = 0; // USBCMD Run/Stop=0 while ( (op[ 0x04 >> 2 ] & 1) == 0 ); // USBSTS HCH==1? op[ 0x00 >> 2 ] |= (1 << 1); // USBCMD HCRST=1 while ( (op[ 0x00 >> 2 ] & 2) != 0 ); // USBCMD HCRST==0? } void show_operational_registers( void ) { printf( "\n"); printf( " USBCMD=%08X ", op[ 0x00 >> 2 ] ); printf( " USBSTS=%08X ", op[ 0x04 >> 2 ] ); printf( " PAGESIZE=%08X ", op[ 0x08 >> 2 ] ); printf( " DNCTLOFF=%08X ", op[ 0x14 >> 2 ] ); printf( "\n"); printf( " CRCR=%08X%08X ", op[ 0x1C >> 2 ], op[ 0x18 >> 2 ] ); printf( " DCBAAP=%08X%08X ", op[ 0x34 >> 2 ], op[ 0x30 >> 2 ] ); printf( " CONFIG=0x%02X ", op[ 0x38 >> 2 ] ); printf( "\n" ); } void show_port_status_registers() { int i; for (i = 0; i < n_ports; i++) { unsigned int index = 0x400 + 16*i; unsigned int portsc = op[ index >> 2 ]; printf( "\n" ); printf( " PORTSC%X=%08X ", i+1, portsc ); printf( " speed=%X ", (portsc >> 10)&0xF ); printf( " powered=%X ", (portsc >> 9)&1 ); printf( " state=%X ", (portsc >> 5)&0xF ); printf( " enabled=%X ", (portsc >> 1)&1 ); printf( " connected=%X ", (portsc >> 0)&1 ); } printf( "\n" ); } void show_runtime_registers( void ) { int i; printf( "\n"); printf( " MFINDEX=%04X ", rt[ 0x00 >> 2 ] ); printf( " IMAN IMOD ERSTSZ " ); printf( " ERSTBA ERDP " ); for (i = 0; i < n_intrs; i++) { unsigned index = 0x20*(i+1); unsigned int iman, imod, erstsz; unsigned long long erstba, erdp; iman = rt[ (index + 0x00) >> 2 ]; imod = rt[ (index + 0x04) >> 2 ]; erstsz = rt[ (index + 0x08) >> 2 ]; erstba = rt[ (index + 0x10) >> 2 ]; erdp = rt[ (index + 0x18) >> 2 ]; printf( "\n intr #%X ", i ); printf( " %08X ", iman ); printf( " %08X ", imod ); printf( " %08X ", erstsz ); printf( " %016X ", erstba ); printf( " %016X ", erdp ); } printf( "\n"); printf( "\n"); } void show_device_control_base_address_array( unsigned long long *dcbaap ) { int dcbaa_entries = 1 + ((1 + SLOTS_EN) | 7); printf( " Device Context Base Address Array: " ); for (int i = 0; i < dcbaa_entries; i++) { if ( (i % 4) == 0 ) printf( "\n DC%02d: ", i ); printf( " %016llX", dcbaap[ i ] ); } printf( "\n" ); printf( "\n" ); } void show_input_device_context( void ) { printf( " Input Device Context: " ); printf( "\n" ); printf( " Input Control Context: " ); printf( " DROP=%08X ADD=%08X \n", ic[0], ic[1] ); printf( " Input Slot Context: " ); printf( " %08X %08X %08X %08X \n", ic[8], ic[9], ic[10], ic[11] ); printf( " Input Endpoint 0 Context: " ); printf( " %08X %08X %08X%08X ", ic[16], ic[17], ic[19], ic[18] ); printf( "BIDIRECTIONAL\n" ); for (int i = 2; i < 32; i++) { int endpt = i/2; int index = (i+1) * 8; int state = ic[ index ] & 7; if ( state == 0 ) continue; printf( " Input Endpoint %X Context: ", endpt ); printf( " %08X %08X", ic[ index + 0 ], ic[ index + 1 ] ); printf( " %08X%08X ", ic[ index + 3 ], ic[ index + 2 ] ); switch ( i%2 ) { case 0: printf( "OUT \n" ); break; case 1: printf( "IN \n" ); break; } } printf( "\n" ); } void show_event_ring_entries( void ) { printf( " Event Ring for Primary Interrupter: " ); for (int i = 0; i < 32; i++) { unsigned long trb_phys = ering_phys + (i * 16); unsigned int *ep = (unsigned int *)&ering[ i ]; unsigned int trbtype = (ep[3] >> 10)&0x3F; printf( "\n <0x%08lX>: ", trb_phys ); for (int j = 0; j < 4; j++) printf( "%08X ", ep[j] ); switch ( trbtype ) { case 0: break; case 32: printf( "Transfer " ); break; case 33: printf( "Command Completion " ); break; case 34: printf( "Port Status Change " ); break; default: printf( "--- ? --- " ); break; } if ( trbtype == 0 ) break; } printf( "\n\n" ); } void show_command_ring_entries( void ) { printf( " Command Ring entrires: " ); for (int i = 0; i < 32; i++) { unsigned long trb_phys = cring_phys + (i * 16); unsigned int *ep = (unsigned int *)&cring[ i ]; unsigned int trbtype = (ep[3] >> 10)&0x3F; printf( "\n <0x%08lX>: ", trb_phys ); for (int j = 0; j < 4; j++) printf( "%08X ", ep[j] ); switch ( trbtype ) { case 0: break; case 9: printf( "Enable Slot " ); break; case 11: printf( "Address Device " ); break; default: printf( "--- ? --- " ); break; } if ( trbtype == 0 ) break; } printf( "\n\n" ); }