//------------------------------------------------------------------- // ohciblue.cpp // // This experimental program shows us the basic steps needed to // turn on and off the blue-colored lamp in a Delcom USB Visual // Signal Indicator device by direct hardware-level programming // of a USB Open Host Controller Interface (OHCI), provided our // 'ohci.c' Linux device-driver kernel module was preinstalled. // // The display of various diagnostic messages which aided us in // development and debugging have been retained for educational // purposes, and to help clarify ambiguities in OHCI documents. // (Hopefully also to encourage and aid added experimentation.) // // compile using: $ g++ ohciblue.cpp -o ohciblue // execute using: $ ./ohciblue // // NOTE: Requires installing our 'ohci.c' device-driver module. // // ----------- // // HARDWARE USED DURING TESTING // // Model-Number: 904005-SB USB HID Visual Signal Indicator RGB // Black Case w/Switch&Buzzer 2M-Cable (Delcom Products, Inc.) // Generation II: VID=0x0FC5 PID=0xB080 TID=2 (Family Type ID) // w/ Sonnet Allegro USB 2.0 5-port PCI-e add-in Adapter Card. // // DELCOM PROGRAMMING REFERENCES // // USB Visual Indicator Development Manual, Delcom Engineering // Document Ver USBVIDM 1.2 (2004), plus Application Note 220: // "Migrating to Delcom USB HID Chip Set (Generation 2) from a // Delcom USB custom driver Chip Set (Generation 1)", (2008). // // [Available online at ] // // ----------- // // programmer: ALLAN CRUSE // date begun: 21 JUN 2010 // completion: 23 JUN 2010 // revised on: 01 JUL 2010 -- to stop 'bad entry' log-messages // revised on: 15 AUG 2011 -- to fix 'pointer-register' writes //------------------------------------------------------------------- #include // for printf(), perror() #include // for open() #include // for exit() #include // for lseek(), usleep() #include // for memset() #include // for mmap() #include // for ioctl() #define VENDOR_ID 0x0FC5 // Delcom Products, Incorporated #define DEVICE_ID 0xB080 // Visual Signal Indicator 904005-SB #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 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 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); unsigned int *lp = (unsigned int *)OHCI_BASE; unsigned int hcca_phys, ed_phys, td_phys, rb_phys, dd_phys, cp_phys; unsigned int hub, dev = 128; int main( int argc, char **argv ) { unsigned int HcControl, HcHCCA, HcControlHeadED; // to discover the Delcom 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 the values in certain OHCI registers we will modify HcControl = lp[ 0x04 >> 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; // initialize our Host Controller Communications Area memset( hcca, 0x00, 0x100 ); // with null pointers // 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 = 0x00082000; // MPS=8, Low-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 ); // activate our Control List lp[ 0x04 >> 2 ] &= ~(1 << 2); // clear HcCommandStatus.CPE lp[ 0x18 >> 2 ] = hcca_phys; // setup our own HCCA lp[ 0x04 >> 2 ] |= (1 << 2); // set HcCommandStatus.CPE lp[ 0x04 >> 2 ] &= ~(1 << 4); // clear HcControl.CLE lp[ 0x20 >> 2 ] = ed_phys; // pointer to our EDs lp[ 0x04 >> 2 ] |= (1 << 4); // set HcControl.CLE lp[ 0x08 >> 2 ] |= (1 << 1); // set HcCommandStatus.CLF // iterate over the set of legal USB device-address values // seeking replies to a 'Get_Descriptor' control-transfer for (dev = 0; dev < 128; dev++) { // set our Endpoint Descriptor's 'sKip bit' ed[0].edControl = 0x00086000 + dev; // MPS=8, LS, 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 = 0x00082000 + dev; // MPS=8, LS // 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; } // restore some former OHCI register-values lp[ 0x04 >> 2 ] &= ~(1 << 2); // clear HcControl.PLE lp[ 0x18 >> 2 ] = HcHCCA; lp[ 0x04 >> 2 ] &= ~(1 << 4); // clear HcControl.CLE lp[ 0x20 >> 2 ] = HcControlHeadED; lp[ 0x04 >> 2 ] = HcControl; // restore HcControl // stop searching when the Delcom device is detected if ( dev < 128 ) break; } // report results of our search for an attached Delcom device if ( dev < 128 ) { printf( "\n Delcom peripheral connected " ); printf( "to OHCI Controller #%u ", hub+1 ); printf( "with USB device-address %u \n\n", dev ); } else { printf( "\n The Delcom USB Visual Signal Indicator " ); 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 our Endpoint Descriptor's 'sKip bit' ed[0].edControl = 0x00086000 + dev; // MPS=8, LS, 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 our Endpoint Descriptor with these TDs attached ed[0].edControl = 0x00082000 + 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 HcControl = lp[ 0x04 >> 2 ]; HcHCCA = lp[ 0x18 >> 2 ]; HcControlHeadED = lp[ 0x20 >> 2 ]; lp[ 0x04 >> 2 ] &= ~(1 << 2); // clear HcControl.PLE lp[ 0x18 >> 2 ] = hcca_phys; // HcHCCA lp[ 0x04 >> 2 ] |= (1 << 2); // set HcControl.PLE lp[ 0x04 >> 2 ] &= ~(1 << 4); // clear HcControl.CLE lp[ 0x20 >> 2 ] = ed_phys; // HcControlHeadED lp[ 0x04 >> 2 ] |= (1 << 4); // set HcControl.CLE 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? //------------------------------------------------------- // Implement the Delcom Visual Signal Indicator commands //------------------------------------------------------- // build the Request-Block for a "Set_Interface_Configuration" rb[2].bmReqType = 0x21; rb[2].bRequest = 0x09; rb[2].wValue = 0x0000; rb[2].wIndex = 0x0000; rb[2].wLength = 0x0008; // build the HID Command-Packet for blue lamp 'on' memset( &cp[0], 0, sizeCP ); cp[0].MajorCmd = 101; cp[0].MinorCmd = 12; cp[0].DataLSB = 4; cp[0].DataMSB = 0; // build the HID Command-Packet for blue lamp 'off' memset( &cp[1], 0, sizeCP ); cp[1].MajorCmd = 101; cp[1].MinorCmd = 12; cp[1].DataLSB = 0; cp[1].DataMSB = 4; // 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 = 0xF3E80000; // OUT, DATA1 td[8].tdBuffer = cp_phys + 0*sizeCP; td[8].tdNextTD = td_phys + 9*sizeTD; td[8].tdBufEnd = cp_phys + 0*sizeCP + 7; td[9].tdStatus = 0xF3F00000; // IN, DATA1 td[9].tdBuffer = 0x00000000; td[9].tdNextTD = td_phys + 10*sizeTD; td[9].tdBufEnd = 0x00000000; td[10].tdStatus = 0xF0000000; td[10].tdBuffer = 0x00000000; td[10].tdNextTD = 0x00000000; td[10].tdBufEnd = 0x00000000; // adjust the Endpoint Descriptor ed[0].edControl = 0x00082000 + dev; ed[0].edTailPtr = td_phys + 10*sizeTD; ed[0].edHeadPtr = td_phys + 7*sizeTD; ed[0].edNextED = ed_phys; // await completion of this list processing while ( td[9].tdStatus >> 28 ) // Completion Code if ( ed[0].edHeadPtr & 1 ) break; // Halted? printf( " OK, now hit to extinguish the blue lamp... \n " ); getchar(); // set Endpoint-Descriptor's 'sKip' bit, to avoid a 'race-condition' ed[0].edControl |= (1<<14); // K=1 usleep( 1000 ); // one-frame delay // setup yet another linked-list of TDs td[10].tdStatus = 0xF2E00000; // SETUP, DATA0 td[10].tdBuffer = rb_phys + 2*sizeRB; td[10].tdNextTD = td_phys + 11*sizeTD; td[10].tdBufEnd = rb_phys + 2*sizeRB + 7; td[11].tdStatus = 0xF3E80000; // OUT, DATA1 td[11].tdBuffer = cp_phys + 1*sizeCP; td[11].tdNextTD = td_phys + 12*sizeTD; td[11].tdBufEnd = cp_phys + 1*sizeCP + 7; td[12].tdStatus = 0xF3F00000; // IN, DATA1 td[12].tdBuffer = 0x00000000; td[12].tdNextTD = td_phys + 13*sizeTD; td[12].tdBufEnd = 0x00000000; td[13].tdStatus = 0xF0000000; td[13].tdBuffer = 0x00000000; td[13].tdNextTD = 0x00000000; td[13].tdBufEnd = 0x00000000; // adjust the Endpoint Descriptor ed[0].edControl = 0x00082000 + dev; ed[0].edTailPtr = td_phys + 13*sizeTD; ed[0].edHeadPtr = td_phys + 10*sizeTD; ed[0].edNextED = ed_phys; // await completion of this list processing while ( td[12].tdStatus >> 28 ) // Completion Code if ( ed[0].edHeadPtr & 1 ) break; // Halted? // restore former OHCI register-values lp[ 0x04 >> 2 ] &= ~(1 << 2); // clear HcControl.PLE lp[ 0x18 >> 2 ] = HcHCCA; lp[ 0x04 >> 2 ] &= ~(1 << 4); // clear HcControl.CLE lp[ 0x20 >> 2 ] = HcControlHeadED; // restore original HCCA and Control List lp[ 0x04 >> 2 ] = HcControl; // clear HcControl.CLE // display the Transfer and Endpoint descriptors printf( " OHCI Transfer Descriptors: \n"); for (int i = 0; i <= 13; 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 < 1; 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" ); } //---------------------------------------------------------------------- // NOTE: We wanted our code to coexist peacefully with the Linux kernel // and its device-drivers, but we discovered that executing our initial // version of this demo resulted in unwanted 'bad entry' error-messages // being written to the kernel log-file, due to the Linux OHCI driver's // intermittent background processing of the controller's 'Done Queue', // based on a pointer-value which the OHCI controller automatically puts // into its current HCCA (Host Controller Communications Area). In this // revised version, by temporarily switching to our own 'private' HCCA, // which Linux doesn't see, any 'Done Queue' anomalies produced in our // code simply will go unnoticed by Linux's driver, and so the annoying // 'bad entry' logfile messages will not get generated. :-) --ABC //----------------------------------------------------------------------