//------------------------------------------------------------------- // ohcitone.cpp // // This experimental program shows us the basic steps needed to // sound a square-wave tone lasting 1-second on Logitech's Z205 // Stereo Laptop Speakers via direct hardware-level programming // of our platform's Universal Serial Bus OHCI host controller, // thereby illustrating the use of 'Isochronous' USB transfers. // // compile using: $ g++ ohcitone.cpp -o ohcitone // execute using: $ ./ohcitone // // NOTE: Requires installing our 'ohci.c' device-driver module. // // programmer: ALLAN CRUSE // date begun: 11 OCT 2011 // completion: 19 OCT 2011 // revised on: 31 OCT 2011 -- to poll on HcInterruptStatus.WBD //------------------------------------------------------------------- #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 VENDOR_ID 0x046D // Logitech, Inc. #define DEVICE_ID 0x0A19 // Z205 Stereo Laptop Speaker #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; unsigned short tdOffset[8]; } 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 { short left, right; } PCM; // stereo S16 format 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 ); 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); PCM *bf = (PCM *)(KMEM_BASE + 5*PAGE_SIZE); unsigned int *lp = (unsigned int *)OHCI_BASE; unsigned int hcca_phys, ed_phys, td_phys, rb_phys, dd_phys, bf_phys; unsigned int HcControl, HcInterruptEnable; unsigned int HcHCCA, HcControlHeadED, HcControlCurrentED; unsigned int hub, dev = 128; void restore_OHCI_registers( void ) { lp[ 0x0C >> 2 ] = 0x0000007F; // clear WC-bits lp[ 0x10 >> 2 ] = HcInterruptEnable; // restore lp[ 0x04 >> 2 ] &= ~(0xF << 2); // list processing "off" lp[ 0x18 >> 2 ] = HcHCCA; // restore lp[ 0x20 >> 2 ] = HcControlHeadED; // restore lp[ 0x24 >> 2 ] = HcControlCurrentED; // restore lp[ 0x04 >> 2 ] = HcControl; // restore } void my_sigint_handler( int signo ) { exit(1); } int main( int argc, char **argv ) { // make sure prior OHCI register-values will get restored // when this program terminates, including the case where // the program 'hangs' and is terminated with 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 ]; HcControlCurrentED = lp[ 0x24 >> 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; bf_phys = kmem_phys + 5*PAGE_SIZE; // setup an uninitialized Transfer Descriptor memset( td, 0x00, sizeTD ); // 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; // disable OHCI controller interrupts lp[ 0x14 >> 2 ] = 0x8000007F; // HcInterruptDisable lp[ 0x0C >> 2 ] = 0x8000007F; // HcInterruptStatus // activate our Control List lp[ 0x04 >> 2 ] &= ~(1 << 4); // HcControl.CLE "off" lp[ 0x20 >> 2 ] = ed_phys; // modify HcControlHeadED lp[ 0x24 >> 2 ] = 0x00000000; // clear HcControlCurrentED lp[ 0x04 >> 2 ] |= (1 << 4); // HcControl.CLE "on" lp[ 0x08 >> 2 ] |= (1 << 1); // HcCommandStatus.CLF // 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; // erase any prior data from the descriptor buffer memset( &dd[0], 0x00, sizeDD ); // 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 = 0xF0000000 | (7 << 21); // SETUP, DI=7 td[0].tdBuffer = rb_phys + 0; // RB #0 td[0].tdNextTD = td_phys + 1*sizeTD; td[0].tdBufEnd = rb_phys + 7; td[1].tdStatus = 0xF0100000 | (7 << 21); // IN, DI=7 td[1].tdBuffer = dd_phys + 0; td[1].tdNextTD = td_phys + 2*sizeTD; td[1].tdBufEnd = dd_phys + 7; td[2].tdStatus = 0xF0100000 | (7 << 21); // IN, DI=7 td[2].tdBuffer = dd_phys + 8; td[2].tdNextTD = td_phys + 3*sizeTD; td[2].tdBufEnd = dd_phys + 15; td[3].tdStatus = 0xF0100000 | (7 << 21); // IN, DI=7 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 = 0x00000000; td[5].tdBuffer = 0x00000000; td[5].tdNextTD = 0x00000000; td[5].tdBufEnd = 0x00000000; // clear the ED's 'sKip' bit ed[0].edControl &= ~(1 << 14); // wait until the linked-list of TDs 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 peripheral's device-descriptor returned if ( ( dd[0].bDescriptorType == 1 ) &&( dd[0].idVendor == VENDOR_ID ) &&( dd[0].idProduct == DEVICE_ID )) break; } // stop searching when the peripheral was detected if ( dev < 128 ) break; else restore_OHCI_registers(); } // report results of our search for the attached peripheral 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); } //------------------------------------------------ // Execute the SET_CONFIGURATION control-transfer //------------------------------------------------ // build the Request-Block for 'Set_Configuration' rb[1].bmReqType = 0x00; // host-to-device rb[1].bRequest = 0x09; // Set_Configuration rb[1].wValue = 0x0001; // configuration=1 rb[1].wIndex = 0x0000; // zero index rb[1].wLength = 0x0000; // zero length // setup revised Endpoint Descriptor with 'sKip' bit set ed[0].edControl = 0x00084000 + dev; // MPS=8, FS, K ed[0].edTailPtr = td_phys + 7*sizeTD; ed[0].edHeadPtr = td_phys + 5*sizeTD; ed[0].edNextED = ed_phys; // setup a fresh linked-list of Transfer Descriptors td[5].tdStatus = 0xF0000000 | (7 << 21); // SETUP, DATA0, DI=7 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 = 0xF0100000 | (0 << 21); // IN, DATA1, DI=0 td[6].tdBuffer = 0x00000000; td[6].tdNextTD = td_phys + 7*sizeTD; td[6].tdBufEnd = 0x00000000; td[7].tdStatus = 0x00000000; td[7].tdBuffer = 0x00000000; td[7].tdNextTD = 0x00000000; td[7].tdBufEnd = 0x00000000; lp[ 0x0C >> 2 ] = 0x0000007F; // clear HcInterruptStatus // clear the Endpoint Descriptor's 'sKip' bit ed[0].edControl &= ~(1 << 14); // wait for the 'WritebackDoneHead' interrupt while ( (lp[ 0x0C >> 2 ] & (1 << 1)) == 0 ); // report the outcome of this control-transfer 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" control-transfer 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 revised Endpoint Descriptor with 'sKip' bit set 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; // setup a fresh linked-list of Transfer Descriptors td[7].tdStatus = 0xF0000000 | (7 << 21); // SETUP, DATA0, DI=7 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 = 0xF0100000 | (0 << 21); // IN, DATA1, DI=0 td[8].tdBuffer = 0x00000000; td[8].tdNextTD = td_phys + 9*sizeTD; td[8].tdBufEnd = 0x00000000; td[9].tdStatus = 0x00000000; td[9].tdBuffer = 0x00000000; td[9].tdNextTD = 0x00000000; td[9].tdBufEnd = 0x00000000; lp[ 0x0C >> 2 ] = 0x0000007F; // clear HcInterruptStatus // clear the Endpoint Descriptor's sKip bit ed[0].edControl &= ~(1 << 14); // wait for the 'WritebackDoneHead' interrupt while ( (lp[ 0x0C >> 2 ] & (1 << 1)) == 0 ); // report the outcome of this control-transfer 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 the PCM data-stream for isochronous transfers //===================================================== // Explanation: The Z205's default Sampling-Frequency is // 32-kHz, and each Isochronous Transfer-Descriptor will // transfer 8 data-packets, each describing 32 pulses of // signed 16-bit stereo data (or 32x4=128 bytes/packet). // So here we initialize our data-buffer with the square // wave data for the 8 successive transfers described by // one Isochronous Transfer-Descriptor. The sequence of // transfers will be repeated 64 times, giving 1024=64x8 // packets altogether, one packet each millisecond, thus // approximately a 1-second tone-duration altogether. for (int i = 0; i < 64; i++) { bf[ i + 0x00 ].left = 30000; bf[ i + 0x00 ].right = 30000; bf[ i + 0x40 ].left = -30000; bf[ i + 0x40 ].right = -30000; bf[ i + 0x80 ].left = 30000; bf[ i + 0x80 ].right = 30000; bf[ i + 0xC0 ].left = -30000; bf[ i + 0xC0 ].right = -30000; } // setup an Isochronous Endpoint Descriptor int endpt = 2; // isochronous OUT endpoint-number int mps = 128; // maximum isochronous packet size int dir = 1; // OUT direction int control = (1 << 15) | (dir << 11) | (endpt << 7) | dev; control |= (mps << 16); ed[1].edControl = control | (1 << 14); // sKip-bit ed[1].edTailPtr = td_phys + 11 * sizeTD; ed[1].edHeadPtr = td_phys + 10 * sizeTD; ed[1].edNextED = 0x00000000; // setup a pair of 'dummy' Transfer-Descriptors memset( &td[10], 0x00, 2 * sizeTD ); // 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[ 0x18 >> 2 ] = hcca_phys; // HcHCCA lp[ 0x04 >> 2 ] |= (3 << 2); // HcControl.PLE and IE // loop to sound the square-wave tone for one second for (int i = 0; i < 128; i++) { // set the ED's 'sKip' bit to avoid a race condition ed[1].edControl = control | (1 << 14); // sKip-bit // refresh the TD's fields td[10].tdBuffer = bf_phys; td[10].tdBufEnd = bf_phys + 0x3FF; td[10].tdStatus = (1 + lp[0x3C >> 2]) & 0xFFFF; td[10].tdStatus |= 0xF7000000; td[10].tdNextTD = 0x00000000; for (int i = 0; i < 8; i++) td[10].tdOffset[i] = 0xE000 + i * 0x80; // refresh the ED's HeadPtr, and clear 'sKip' bit ed[1].edHeadPtr = td_phys + 10 * sizeTD; ed[1].edControl &= ~(1 << 14); // clear sKip bit // wait for writeback of the TD's status while ( (td[10].tdStatus >> 28) == 0xF ); } // 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 revised Endpoint Descriptor with 'sKip' bit set 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; // setup a fresh linked-list of Transfer Descriptors td[7].tdStatus = 0xF0000000 | (7 << 21); // SETUP, DATA0, DI=7 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 = 0xF0100000 | (0 << 21); // IN, DATA1, DI=0 td[8].tdBuffer = 0x00000000; td[8].tdNextTD = td_phys + 9*sizeTD; td[8].tdBufEnd = 0x00000000; td[9].tdStatus = 0x00000000; td[9].tdBuffer = 0x00000000; td[9].tdNextTD = 0x00000000; td[9].tdBufEnd = 0x00000000; lp[ 0x0C >> 2 ] = 0x0000007F; // clear HcInterruptStatus // clear the Endpoint Descriptor's sKip bit ed[0].edControl &= ~(1 << 14); // wait for the 'WritebackDoneHead' interrupt while ( (lp[ 0x0C >> 2 ] & (1 << 1)) == 0 ); // report the outcome of this control-transfer 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[3].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 <= 9; 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" ); } for (int i = 10; i <= 11; 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( " " ); for (int j = 4; j < 8; 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" ); }