//------------------------------------------------------------------- // anomaly1.cpp // // This program demonstrates a deviation of the OHCI controller // on our Sonnet add-in adapters from the OpenHCI Specification // (version 1.0a dated 9/14/99) which states in Section 4.3.3.1 // on page 33: // "In no case does the Host Controller set the Halted bit in // the ED for an Isochronous TD." // We are retaining this experimental code as our confirmation. // // The attached Full-Speed USB peripheral was our Logitech Z205 // Stereo Laptop Speaker. // // compile using: $ g++ anomaly1.cpp -o anomaly1 // execute using: $ ./anomaly1 // // NOTE: Requires installing our 'ohci.c' device-driver module. // // ----------- // // programmer: ALLAN CRUSE // written on: 18 OCT 2011 //------------------------------------------------------------------- #include // for printf(), perror() #include // for open() #include // for exit() #include // for lseek(), usleep() #include // for memset() #include // for signal() #include // for mmap() #include // for ioctl() #define OHCI_BASE 0x70000 // suitable address for register-map #define PAGE_SIZE 0x01000 // memory-granularity on x86 systems #define KMEM_BASE 0x71000 // address for a kernel memory arena #define VENDOR_ID 0x046D // Logitech, Inc. #define DEVICE_ID 0x0A19 // Z205 Stereo Laptop Speaker typedef struct { unsigned int edControl; unsigned int edTailPtr; unsigned int edHeadPtr; unsigned int edNextED; } OHCI_ED; // OHCI EndPoint Descriptor typedef struct { unsigned int tdStatus; unsigned int tdBuffer; unsigned int tdNextTD; unsigned int tdBufEnd; } OHCI_TD; // OHCI Transfer 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 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 MajorCmd; unsigned char MinorCmd; unsigned char DataLSB; unsigned char DataMSB; unsigned char DataHID[4]; unsigned char DataExt[8]; } HID_CP; // HID Command Packet typedef struct { short left, right; } PCM; char devname[] = "/dev/ohci-"; const int sizeED = sizeof( OHCI_ED ); const int sizeTD = sizeof( OHCI_TD ); const int sizeRB = sizeof( USB_RB ); const int sizeDD = sizeof( USB_DD ); const int sizeCP = sizeof( HID_CP ); unsigned long kmem_phys, kmem_size; unsigned int *hcca = (unsigned int *)(KMEM_BASE + 0*PAGE_SIZE); OHCI_ED *ed = (OHCI_ED *)(KMEM_BASE + 1*PAGE_SIZE); OHCI_TD *td = (OHCI_TD *)(KMEM_BASE + 2*PAGE_SIZE); USB_RB *rb = (USB_RB *)(KMEM_BASE + 3*PAGE_SIZE); USB_DD *dd = (USB_DD *)(KMEM_BASE + 4*PAGE_SIZE); HID_CP *cp = (HID_CP *)(KMEM_BASE + 5*PAGE_SIZE); PCM *bf = (PCM *)(KMEM_BASE + 6*PAGE_SIZE); unsigned int *lp = (unsigned int *)OHCI_BASE; unsigned int ed_phys, td_phys, rb_phys, dd_phys, cp_phys; unsigned int hcca_phys, bf_phys; unsigned int hub, dev = 128; unsigned int HcControl, HcInterruptEnable, HcHCCA, HcControlHeadED; void restore_OHCI_registers( void ) { lp[ 0x0C >> 2 ] = 0x0000003F; // cleare WC-bits lp[ 0x10 >> 2 ] = HcInterruptEnable; lp[ 0x04 >> 2 ] &= ~(0xF << 2); // list processing "off" lp[ 0x18 >> 2 ] = HcHCCA; // restore lp[ 0x20 >> 2 ] = HcControlHeadED; // restore lp[ 0x04 >> 2 ] = HcControl; // restore } void my_sigint_handler( int signo ) { exit(1); } int main( int argc, char **argv ) { atexit( restore_OHCI_registers ); signal( SIGINT, my_sigint_handler ); // to discover the Logitech peripheral's USB device-address // we iterate over our system's two OHCI Host Controllers for (hub = 0; hub < 2; hub++) { // setup the controller's device-filename sprintf( devname + 9, "%c", hub + '1' ); printf( "\n Opening \'%s\' \n", devname ); // open this device-file for reading and writing int fd = open( devname, O_RDWR ); if ( fd < 0 ) { perror( devname ); exit(1); } // map the controller's registers and kernel buffer int size = lseek( fd, 0, SEEK_END ); int prot = PROT_READ | PROT_WRITE; int flag = MAP_FIXED | MAP_SHARED; void *mm = (void*)OHCI_BASE; if ( mmap( mm, size, prot, flag, fd, 0 ) == MAP_FAILED ) { perror( "mmap" ); exit(1); } // get the physical address of the kernel memory buffer if ( ioctl( fd, 0, &kmem_phys ) < 0 ) { perror( "ioctl" ); exit(1); } kmem_size = size; printf( " physical address-range of kernel-memory:" ); printf( " 0x%08lX-0x%08lX \n", kmem_phys, kmem_phys + size ); // show the OHCI Host Controller registers printf( "\n OHCI Host Controller #%u Registers: ", hub+1 ); for (int i = 0; i < 24; i++) { if ( (i % 8) == 0 ) printf( "\n 0x%02X: ", i*4 ); printf( "%08X ", lp[ i ] ); } printf( "\n" ); // save some initial register-values to be restored later HcControl = lp[ 0x04 >> 2 ]; HcInterruptEnable = lp[ 0x10 >> 2 ]; HcHCCA = lp[ 0x18 >> 2 ]; HcControlHeadED = lp[ 0x20 >> 2 ]; // check that controller's functional state is 'OPERATIONAL' unsigned int HCFS = (HcControl >> 6) & 3; printf( "\n HcControl=%08X HCFS=%u ", HcControl, HCFS ); printf( " HC Functional State is " ); switch ( HCFS ) { case 0: printf( "\'RESET\' \n" ); break; case 1: printf( "\'RESUME\' \n" ); break; case 2: printf( "\'OPERATIONAL\' \n" ); break; case 3: printf( "\'SUSPEND\' \n" ); break; } unsigned int HcRhDescriptorA = lp[ 0x48 >> 2 ]; unsigned int NDP = HcRhDescriptorA & 0xFF; printf( " Root Hub has %u ports: \n", NDP ); int n_ls = 0; int index = 0x54; for (int i = 1; i <= NDP; i++) { unsigned int index = (0x50 >> 2) + i; unsigned int HcRhPortStatus = lp[ index ]; unsigned int CCS = (HcRhPortStatus >> 0)&1; unsigned int PES = (HcRhPortStatus >> 1)&1; unsigned int PPS = (HcRhPortStatus >> 8)&1; unsigned int LSDA = (HcRhPortStatus >> 9)&1; printf( " HcRhPortStatus[%u]=", i ); printf( "%08X ", HcRhPortStatus ); printf( " low-speed=%X ", LSDA ); printf( " powered=%X ", PPS ); printf( " enabled=%X ", PES ); printf( " connected=%X ", CCS ); printf( "\n" ); unsigned int mask; mask = (CCS<<0)|(PES<<1)|(PPS<<8)|(LSDA<<9); if ( (HcRhPortStatus & mask) == mask ) ++n_ls; } printf( "\n" ); if (( HCFS != 2 )||( n_ls == 0 )) continue; // setup physical addresses of our data structures hcca_phys = kmem_phys + 0*PAGE_SIZE; ed_phys = kmem_phys + 1*PAGE_SIZE; td_phys = kmem_phys + 2*PAGE_SIZE; rb_phys = kmem_phys + 3*PAGE_SIZE; dd_phys = kmem_phys + 4*PAGE_SIZE; cp_phys = kmem_phys + 5*PAGE_SIZE; bf_phys = kmem_phys + 6*PAGE_SIZE; // erase any prior data from the descriptor buffer memset( &dd[0], 0x00, sizeDD ); // build the 'Get_Device_Descriptor' Request-Block rb[0].bmReqType = 0x80; rb[0].bRequest = 0x06; rb[0].wValue = 0x0100; rb[0].wIndex = 0x0000; rb[0].wLength = 0x0012; // setup an inactive Endpoint Descriptor ed[0].edControl = 0x00080000; // MPS=8, Full-Speed ed[0].edTailPtr = td_phys; ed[0].edHeadPtr = td_phys; ed[0].edNextED = ed_phys; // setup an uninitialized Transfer Descriptor memset( td, 0x00, sizeTD ); // disable OHCI controller interupts lp[ 0x14 >> 2 ] = 0x8000007F; // InterruptDisable lp[ 0x0C >> 2 ] = 0x8000007F; // InterruptStatus // activate our Control List lp[ 0x04 >> 2 ] &= ~(1 << 4); // HcControl.CLE "off" lp[ 0x20 >> 2 ] = ed_phys; // modify HcControlHeadED lp[ 0x04 >> 2 ] |= (1 << 4); // HcControl.CLE "on" lp[ 0x08 >> 2 ] |= (1 << 1); // set HcCommandStatus.CLF // loop to discover the peripheral's USB device-address for (dev = 0; dev < 128; dev++) { // set out Endpoint Descriptor's 'sKip bit' ed[0].edControl = 0x00084000 + dev; // MPS=8, FS, K ed[0].edTailPtr = td_phys + 5*sizeTD; ed[0].edHeadPtr = td_phys + 0*sizeTD; ed[0].edNextED = ed_phys; // setup linked-list of TDs for a Control Transfer td[0].tdStatus = 0xF2E00000; // SETUP, DATA0 td[0].tdBuffer = rb_phys + 0; // RB #0 td[0].tdNextTD = td_phys + 1*sizeTD; td[0].tdBufEnd = rb_phys + 7; td[1].tdStatus = 0xF3F00000; // IN, DATA1 td[1].tdBuffer = dd_phys + 0; td[1].tdNextTD = td_phys + 2*sizeTD; td[1].tdBufEnd = dd_phys + 7; td[2].tdStatus = 0xF2F00000; // IN, DATA0 td[2].tdBuffer = dd_phys + 8; td[2].tdNextTD = td_phys + 3*sizeTD; td[2].tdBufEnd = dd_phys + 15; td[3].tdStatus = 0xF3F00000; // IN, DATA1 td[3].tdBuffer = dd_phys + 16; td[3].tdNextTD = td_phys + 4*sizeTD; td[3].tdBufEnd = dd_phys + 17; td[4].tdStatus = 0xF3E80000; // OUT, DATA1 td[4].tdBuffer = 0x00000000; td[4].tdNextTD = td_phys + 5*sizeTD; td[4].tdBufEnd = 0x00000000; td[5].tdStatus = 0xF0000000; td[5].tdBuffer = 0x00000000; td[5].tdNextTD = 0x00000000; td[5].tdBufEnd = 0x00000000; // attach this list of TDs to our queue ed[0].edControl = 0x00080000 + dev; // MPS=8, FS // wait until the list is processed while ( td[4].tdStatus >> 28 ) // Completion Code if ( ed[0].edHeadPtr & 1 ) break; // Halted? // check for 'Endpoint Halted' condition if ( ed[0].edHeadPtr & (1<<0) ) continue; // check for Delcom device-descriptor returned if ( ( dd[0].bDescriptorType == 1 ) &&( dd[0].idVendor == VENDOR_ID ) &&( dd[0].idProduct == DEVICE_ID )) break; } // deactivate our Control List lp[ 0x04 >> 2 ] &= ~(1 << 4); // HcControl.CLE "off" // stop searching when the Delcom device is detected if ( dev < 128 ) break; } // report results of our search for an attached Logitech device if ( dev < 128 ) { printf( "\n Logitech Z205 Speaker " ); printf( "attached to OHCI Controller #%u ", hub+1 ); printf( "with USB device-address %u \n\n", dev ); } else { printf( "\n The Logitech Z205 Stereo Laptop Speaker " ); printf( "peripheral-device was not detected. \n\n" ); exit(1); } // build the 'Set_Device_Configuration' Request-Block rb[1].bmReqType = 0x00; rb[1].bRequest = 0x09; rb[1].wValue = 0x0001; rb[1].wIndex = 0x0000; rb[1].wLength = 0x0000; // set out Endpoint Descriptor's 'sKip bit' ed[0].edControl = 0x00084000 + dev; // MPS=8, FS, K ed[0].edTailPtr = td_phys + 5*sizeTD; ed[0].edHeadPtr = td_phys + 0*sizeTD; ed[0].edNextED = ed_phys; // setup a fresh linked-list of Transfer Descriptors td[5].tdStatus = 0xF2E00000; // SETUP, DATA0 td[5].tdBuffer = rb_phys + 1*sizeRB; td[5].tdNextTD = td_phys + 6*sizeTD; td[5].tdBufEnd = rb_phys + 1*sizeRB + 7; td[6].tdStatus = 0xF3F00000; // IN, DATA1 td[6].tdBuffer = 0x00000000; td[6].tdNextTD = td_phys + 7*sizeTD; td[6].tdBufEnd = 0x00000000; td[7].tdStatus = 0xFFFFFFFF; td[7].tdBuffer = 0x00000000; td[7].tdNextTD = 0x00000000; td[7].tdBufEnd = 0x00000000; // setup out Endpoint Descriptor with these TDs attached ed[0].edControl = 0x00080000 + dev; ed[0].edTailPtr = td_phys + 7*sizeTD; ed[0].edHeadPtr = td_phys + 5*sizeTD; ed[0].edNextED = ed_phys; // reactivate our Control List on the OHCI controller unsigned int HcControl = lp[ 0x04 >> 2 ]; lp[ 0x04 >> 2 ] &= ~(1 << 4); // HcControl.CLE "off" lp[ 0x24 >> 2 ] = ed_phys; // HcControlCurrentED lp[ 0x04 >> 2 ] |= (1 << 4); // HcControl.CLE "on" lp[ 0x08 >> 2 ] |= (1 << 1); // set HcCommandStatus.CLF // await completion of this linked-list processing while ( td[6].tdStatus >> 28 ) // Completion Code if ( ed[0].edHeadPtr & 1 ) break; // Halted? if ( td[6].tdStatus >> 28 ) { printf( " SET_CONFIGURATION failed \n\n" ); exit(1); } printf( " Executed SET_CONFIGURATION control-transfer:" ); printf( " configuration=%d \n\n", rb[1].wValue ); //-------------------------------------------- // Execute the SET_INTERFACE control-transfer //-------------------------------------------- // build the Request-Block for a "Set_Interface_Configuration" rb[2].bmReqType = 0x01; // host-to-device rb[2].bRequest = 0x0B; // SET_INTERFACE rb[2].wValue = 0x0001; // interface=1 rb[2].wIndex = 0x0001; // index=1 rb[2].wLength = 0x0000; // zero-length // setup the next linked-list of TDs td[7].tdStatus = 0xF2E00000; // SETUP, DATA0 td[7].tdBuffer = rb_phys + 2*sizeRB; td[7].tdNextTD = td_phys + 8*sizeTD; td[7].tdBufEnd = rb_phys + 2*sizeRB + 7; td[8].tdStatus = 0xF3F00000; // IN, DATA1 td[8].tdBuffer = 0x00000000; td[8].tdNextTD = td_phys + 9*sizeTD; td[8].tdBufEnd = 0x00000000; td[9].tdStatus = 0xF0000000; td[9].tdBuffer = 0x00000000; td[9].tdNextTD = 0x00000000; td[9].tdBufEnd = 0x00000000; // adjust the Endpoint Descriptor ed[0].edControl = ( 0x00080000 + dev ) | (1 << 14); ed[0].edTailPtr = td_phys + 9*sizeTD; ed[0].edHeadPtr = td_phys + 7*sizeTD; ed[0].edNextED = ed_phys; // clear the Endpoint Descriptor's sKip bit ed[0].edControl &= ~(1 << 14); // await completion of this list processing while ( td[8].tdStatus >> 28 ) // Completion Code if ( ed[0].edHeadPtr & 1 ) break; // Halted? // set Endpoint-Descriptor's 'sKip' bit, to avoid a 'race-condition' ed[0].edControl |= (1<<14); // K=1 if ( td[8].tdStatus >> 28 ) { printf( " SET_INTERFACE failed \n\n" ); exit(1); } printf( " Executed SET_INTERFACE control-transfer:" ); printf( " interface=%d \n\n", rb[2].wValue ); //=================================================== // setup a data-stream for the isochronous transfers // that we can inspect using our Beagle 480 analyzer //=================================================== char *cp = (char *)bf; // base of the buffer-area char val = 0x00; // a starting memory-image for (int i = 0; i < 8; i++) // number of data-regions { val += 0x11; // increment memory-image memset( cp, val, 128 ); // and store 128 copies cp += 128; // advance buffer-pointer } // setup the Endpoint Descriptor and its Isochronous TD int endpt = 2; // isochronous OUT endpoint-number int mps = 192; // maximum packet size int dir = 1; // OUT direction int control = (1 << 15) | (dir << 11) | (endpt << 7) | dev; control |= (mps << 16); // setup an Isochronous Transfer Descriptor td[10].tdStatus = 0xF7000000; td[10].tdBuffer = bf_phys; td[10].tdNextTD = 0x00000000; //td_phys + 12 * sizeTD; td[10].tdBufEnd = bf_phys + 127; td[11].tdStatus = 0xE080E000;; td[11].tdBuffer = 0xE180E100;; td[11].tdNextTD = 0xE280E200;; td[11].tdBufEnd = 0xE380E300;; // setup a 'dummy' successor TD memset( &td[12], 0x00, 32 ); td[12].tdStatus = 0xF0000000; // initialize the 'Starting Frame' field of the TD td[10].tdStatus |= ( 2 + lp[ 0x3C >> 2 ] ) & 0xFFFF; // setup an Isochronous Endpoint Descriptor ed[1].edControl = control | (1 << 14); // sKip-bit ed[1].edTailPtr = td_phys + 12 * sizeTD; ed[1].edHeadPtr = td_phys + 10 * sizeTD; ed[1].edNextED = 0x00000000; //ed_phys + 2 * sizeED; // initialize the Host Controller Communications Area (HCCA) for (int i = 0; i < 32; i++) hcca[i] = ed_phys + 1*sizeED; // activate the Periodic Schedule lp[ 0x1C >> 2 ] = 0; // HcPeriodCurrentED lp[ 0x18 >> 2 ] = hcca_phys; // HcHCCA lp[ 0x04 >> 2 ] |= (3 << 2); // HcControl.PLE and IE // clear the Endpoint-Descriptor's sKip-bit ed[1].edControl &= ~(1 << 14); // wait for status-writeback of the Isochronous TD while( (td[10].tdStatus >> 28) == 0xF ); // NOTE: OHCI Specification says "In no case does the HC // set the Halted bit in the ED for an isochronous RD." // (page 33); However, we observed it being set. // disable the periodic list-processing lp[ 0x04 >> 2 ] &= ~(3 << 2); // HcControl.PLE and IE // clear the HccaInterruptTable memset( &hcca[0], 0x00, 32 * 4 ); //==================================================== //-------------------------------------------- // Execute the SET_INTERFACE control-transfer //-------------------------------------------- // build the Request-Block for a "Set_Interface_Configuration" rb[3].bmReqType = 0x01; // host-to-device rb[3].bRequest = 0x0B; // SET_INTERFACE rb[3].wValue = 0x0000; // interface=0 rb[3].wIndex = 0x0001; // index=1 rb[3].wLength = 0x0000; // zero-length // setup the next linked-list of TDs td[7].tdStatus = 0xF2E00000; // SETUP, DATA0 td[7].tdBuffer = rb_phys + 3*sizeRB; td[7].tdNextTD = td_phys + 8*sizeTD; td[7].tdBufEnd = rb_phys + 3*sizeRB + 7; td[8].tdStatus = 0xF3F00000; // IN, DATA1 td[8].tdBuffer = 0x00000000; td[8].tdNextTD = td_phys + 9*sizeTD; td[8].tdBufEnd = 0x00000000; td[9].tdStatus = 0xF0000000; td[9].tdBuffer = 0x00000000; td[9].tdNextTD = 0x00000000; td[9].tdBufEnd = 0x00000000; // adjust the Endpoint Descriptor ed[0].edControl = ( 0x00080000 + dev ) | (1 << 14); // sKip ed[0].edTailPtr = td_phys + 9*sizeTD; ed[0].edHeadPtr = td_phys + 7*sizeTD; ed[0].edNextED = ed_phys; // clear the Endpoint Descriptor's sKip bit ed[0].edControl &= ~(1 << 14); // await completion of this list processing while ( td[8].tdStatus >> 28 ) // Completion Code if ( ed[0].edHeadPtr & 1 ) break; // Halted? // set Endpoint-Descriptor's 'sKip' bit, to avoid a 'race-condition' ed[0].edControl |= (1<<14); // K=1 if ( td[8].tdStatus >> 28 ) { printf( " SET_INTERFACE failed \n\n" ); exit(1); } printf( " Executed SET_INTERFACE control-transfer:" ); printf( " interface=%d \n\n", rb[2].wValue ); //===================================== // exhibit the sequence of TDs and EDs //===================================== // clear pending interrupt status bits unsigned int HcInterruptStatus = lp[ 0x0C >> 2 ]; lp[ 0x0C >> 2 ] = HcInterruptStatus; // display the Transfer and Endpoint descriptors printf( " OHCI Transfer Descriptors: \n"); for (int i = 0; i < 14; i++) { unsigned int *entry = (unsigned int*)&td[i]; printf( " TD%X ", i ); printf( "<0x%08X>: ", td_phys + i * sizeTD ); for (int j = 0; j < 4; j++) printf( "%08X ", entry[j] ); printf( "\n" ); } printf( "\n" ); printf( " OHCI Endpoint Descriptors: \n"); for (int i = 0; i < 2; i++) { unsigned int *entry = (unsigned int*)&ed[i]; printf( " ED%u ", i ); printf( "<0x%08X>: ", ed_phys + i * sizeED ); for (int j = 0; j < 4; j++) printf( "%08X ", entry[j] ); printf( "\n" ); } printf( "\n" ); }