//------------------------------------------------------------------- // uhcitone.cpp // // This Linux application program shows us what basic steps are // needed in order to sound a square-wave tone lasting 1-second // on Logitech's Z205 Stereo Laptop Speakers by direct hardware // programming of our platform's UHCI host controllers, thereby // illustrating the use of 'Isochronous' USB transactions. // // NOTE: This program relies upon our 'uhci'c' device-driver. // // REFERENCES: // "Universal Host Controller Interface (UHCI) Design Guide," // Intel Corporation (March 1996). // // "USB Class Definition for Audio Devices," Release 1.0 (1998). // // programmer: ALLAN CRUSE // date begun: 17 SEP 2011 // completion: 20 OCT 2011 //------------------------------------------------------------------- #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() #include // for inw(), outw() #define VENDOR_ID 0x046D // Logitech, Inc. #define DEVICE_ID 0x0A19 // Z205 Stereo Speaker #define KMEM_BASE 0x70000 // suitable address for memory-map #define PAGE_SIZE 0x01000 // 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 int qhHorzLink; unsigned int qhVertLink; unsigned int qhPadding0; unsigned int qhPadding1; } UHCI_QH; // UHCI Queue Head typedef struct { unsigned int tdNextTD; unsigned int tdStatus; unsigned int tdAction; unsigned int tdBuffer; } UHCI_TD; // UHCI 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/uhci"; unsigned long hub_count; unsigned long port_addr; unsigned long kmem_phys; const int sizeQH = sizeof( UHCI_QH ); const int sizeTD = sizeof( UHCI_TD ); const int sizeRB = sizeof( USB_RB ); const int sizeDD = sizeof( USB_DD ); 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); PCM *bf = (PCM *)(KMEM_BASE + PAGE_SIZE * 6); unsigned int qh_phys, td_phys, rb_phys, dd_phys, bf_phys; unsigned short usbcmd, usbsts, usbintr, frnum; unsigned int frbaseaddr, io, dev, hub = 1; void restore_UHCI_registers( void ) { outw( inw( io + 0 ) & ~(1 << 0), io + 0 ); // clear R/S bit while (( inw( io + 2 ) & (1 << 5 )) == 0 ); // await H == 1 outw( frnum, io + 6 ); // restore FRNUM outl( frbaseaddr, io + 8 ); // restore FRBASEADDR usbsts = inw( io + 2 ); // read current status outw( usbsts, io + 2 ); // and reset WC-bits outw( usbintr, io + 4 ); // restore initial value outw( usbcmd, io + 0 ); // restore initial value } void my_sigint_handler( int signo ) { exit(1); } void display_UHCI_registers( void ) { printf( "\n" ); printf( "UHCI controller #%d ", hub+1 ); 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" ); } int main( int argc, char **argv ) { // open the UHCI 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; bf_phys = kmem_phys + PAGE_SIZE * 6; printf( "Kernel memory region begins at " ); printf( "physical address 0x%08lX \n", kmem_phys ); // map the driver's 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); } // get the number of UHCI controllers if ( ioctl( fd, 1, &hub_count ) < 0 ) { perror( "ioctl" ); exit(1); } printf( "\nNumber of UHCI controllers = %ld \n", hub_count ); // show the controllers' IO-port addresses for (int i = 0; i < hub_count; i++) { port_addr = i+1; printf( " controller #%d: ", port_addr ); if ( ioctl( fd, 1, &port_addr ) < 0 ) { perror( "ioctl" ); exit(1); } printf( "I/O-Port=0x%04X \n", port_addr ); } //-------------------------------------------------- // next we have to search for the peripheral device //-------------------------------------------------- for (hub = 0; hub < hub_count; hub++) { port_addr = hub + 1; if ( ioctl( fd, 1, &port_addr ) < 0 ) { perror( "ioctl" ); exit(1); } // overwrite any previous data in the destination buffer memset( &dd[0], 0xFF, sizeDD ); // 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 ); // make sure prior UHCI register-values will get restored // when this program terminates, including the case where // the program 'hangs' and is terminated with atexit( restore_UHCI_registers ); signal( SIGINT, my_sigint_handler ); // let the user see the initial UHCI register-values display_UHCI_registers(); // skip if host controller entered Global Suspend Mode if ( usbcmd & (1 << 3 ) ) continue; // EGSM=1 // 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; // initialize the framelist for (int i = 0; i < 1024; i++) framelist[i] = qh_phys | QH; // activate our UHCI schedule 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 frbaseaddr = inl( io + 8 ); // FRBASEADDR outw( 0x0000, io + 4 ); // USBINTR outl( kmem_phys, 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 the user see the modified UHCI register-values display_UHCI_registers(); // build the 'Get_Device_Descriptor' Request-Block rb[0].bmReqType = 0x80; // device-to-host rb[0].bRequest = 0x06; // Get_Descriptor rb[0].wValue = 0x0100; // device-descriptor rb[0].wIndex = 0x0000; // zero index rb[0].wLength = 0x0012; // length=18 for ( dev = 0; dev < 128; dev++) { // overwrite any previous data in the destination buffer memset( &dd[0], 0xFF, sizeDD ); td[1].tdNextTD = td_phys + 2 * sizeTD | VF; td[1].tdStatus = 0x18800000; // Full-Speed, Active td[1].tdAction = 0x00E0002D | (dev<<8); // SETUP, MaxLen=8 td[1].tdBuffer = rb_phys; // Requst Block #0 td[2].tdNextTD = td_phys + 3 * sizeTD | VF; td[2].tdStatus = 0x18800000; // Full-Speed, Active td[2].tdAction = 0x00E80069 | (dev<<8); // IN, MaxLen=8, DT=1 td[2].tdBuffer = dd_phys + 0*8; // Requst Block #0 td[3].tdNextTD = td_phys + 4 * sizeTD | VF; td[3].tdStatus = 0x18800000; // Full-Speed, Active td[3].tdAction = 0x00E00069 | (dev<<8); // IN, MaxLen=8, DT=0 td[3].tdBuffer = dd_phys + 1*8; // Requst Block #0 td[4].tdNextTD = td_phys + 5 * sizeTD | VF; td[4].tdStatus = 0x18800000; // Full-Speed, Active td[4].tdAction = 0x00E80069 | (dev<<8); // IN, MaxLen=8, DT=1 td[4].tdBuffer = dd_phys + 2*8; // Requst Block #0 td[5].tdNextTD = 0x00000001; td[5].tdStatus = 0x18800000; // Full-Speed, Active td[5].tdAction = 0xFFE800E1 | (dev<<8); // OUT, MaxLen=0, DT=1 td[5].tdBuffer = 0x00000000; // Buffer = NULL // This Queue-Head's horizontal link points to itself qh[1].qhHorzLink = qh_phys + 1*sizeQH | QH; qh[1].qhVertLink = td_phys + 1*sizeQH | VF; qh[1].qhPadding0 = 0xFFFFFFFF; qh[1].qhPadding1 = 0xFFFFFFFF; // add this new Queue-Head to our schedule int frindx = ( inw( io + 6 ) + 2 ) % 1024; // FRNUM framelist[ frindx ] = qh_phys + 1*sizeQH | QH; // poll the Transfer Descriptor status while ( td[5].tdStatus & (1 << 23) ) if ( td[1].tdStatus & (1 << 22) ) break; // remove Queue Head #1 from the schedule framelist[ frindx ] = qh_phys + 0*sizeQH | QH; // see if the control-transfer was successful if ( td[5].tdStatus & (1 << 23 ) ) continue; // see if the Logitech USB Stereo Speaker device was detected if (( dd[0].idVendor == VENDOR_ID ) &&( dd[0].idProduct == DEVICE_ID )) break; } if ( dev < 128 ) break; restore_UHCI_registers(); } // report the Z205 speaker's USB device-number if found if (( hub == hub_count )||( dev == 128 )) { printf( "\n\n Logitech Z205 USB Stereo Speaker " ); printf( "was not detected \n\n" ); exit(1); } printf( "\n\n Logitech Z205 USB Stereo Speaker " ); printf( "found on hub #%d ", hub + 1 ); printf( "using device-number %d \n\n\n", dev ); //================================================================ //-------------------------------------------------- // execute the 'Set_Configuration' control-transfer //-------------------------------------------------- // setup the Request Block 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 the linked-list of transfer-descriptors td[6].tdNextTD = td_phys + 7*sizeTD | VF; td[6].tdStatus = 0x18800000; td[6].tdAction = 0x00E0002D | (dev << 8) | (0 << 19); td[6].tdBuffer = rb_phys + 1*sizeRB; td[7].tdNextTD = 0x00000001; td[7].tdStatus = 0x18800000; td[7].tdAction = 0xFFE00069 | (dev << 8) | (1 << 19); td[7].tdBuffer = 0xFFFFFFFF; // attach linked-list of TDs to Queue Head #1 qh[1].qhVertLink = td_phys + 6 * sizeTD | VF; // add Queue Head #1 into the schedule again unsigned int frindx = ( inw( io + 6 ) + 5 ) % 1024; // FRNUM framelist[ frindx ] = qh_phys + 1*sizeQH | QH; // poll the Transfer Descriptor status while ( td[7].tdStatus & (1 << 23) ) if ( td[6].tdStatus & (1 << 22) ) break; // remove Queue Head #1 from the schedule framelist[ frindx ] = qh_phys + 0*sizeQH | QH; if ( (qh[1].qhVertLink & 1 ) == 0 ) { 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 //---------------------------------------------- // setup the Request Block 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 linked-list of transfer-descriptors td[8].tdNextTD = td_phys + 9 * sizeTD | VF; td[8].tdStatus = 0x18800000; td[8].tdAction = 0x00E0002D | (dev << 8) | (0 << 19); td[8].tdBuffer = rb_phys + 2*sizeRB; td[9].tdNextTD = 0x00000001; td[9].tdStatus = 0x18800000; td[9].tdAction = 0xFFE00069 | (dev << 8) | (1 << 19); td[9].tdBuffer = 0xFFFFFFFF; // attach linked-list of TDs to Queue Head #1 qh[1].qhVertLink = td_phys + 8 * sizeTD | VF; // add Queue Head #1 into the schedule again frindx = ( inw( io + 6 ) + 5 ) % 1024; // FRNUM framelist[ frindx ] = qh_phys + 1*sizeQH | QH; // poll the Transfer Descriptor status while ( td[9].tdStatus & (1 << 23) ) if ( td[8].tdStatus & (1 << 22) ) break; // remove Queue Head #1 from the schedule framelist[ frindx ] = qh_phys + 0*sizeQH | QH; if ( (qh[1].qhVertLink & 1 ) == 0 ) { printf( " SET_INTERFACE failed \n\n" ); exit(1); } printf( " executed SET_INTERFACE control-transfer:" ); printf( " interface=%d \n\n", rb[2].wValue ); //================================================================ // temporarily reinitialize our framelist with invalid pointers for (int i = 0; i < 1024; i++) framelist[i] = 0x00000001; //================================================================ // erase any prior contents of the buffer-array memset( &bf[0], 0xFF, 4096 ); // setup a square-wave PCM stream 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 the temporarily empty Queue Head qh[0].qhHorzLink = 0x00000001; qh[0].qhVertLink = 0x00000001; qh[0].qhPadding0 = 0xFFFFFFFF; qh[0].qhPadding1 = 0xFFFFFFFF; // Setup a Transfer Descriptor for the OUT endpoint unsigned int action, status; unsigned int token = 0xE1; unsigned int endpt = 2; unsigned int maxpkt = 127; action = (maxpkt << 21) | (endpt << 15) | (dev << 8 ) | token; unsigned int c_err = 3; unsigned int speed = 0; unsigned int active = 1; unsigned int isoch = 1; status = (c_err << 27) | (speed << 26) | (active << 23) | (isoch << 25); td[10].tdNextTD = 0x00000001; td[10].tdStatus = status; td[10].tdAction = action; td[10].tdBuffer = bf_phys; // reinitialize our framelist for 1-ms polling-frequency for (int i = 0; i < 1024; i++) framelist[ i ] = qh_phys | QH; // Execute Isochronous Transactions on Endpoint 2 (OUT) // with MaxPacketSize=128 at 1-millisecond intervals for (int i = 0; i < 0x400; i++) { td[10].tdBuffer = bf_phys + ((0x80 * i)&0x3FF); td[10].tdAction = action; td[10].tdStatus = status; qh[0].qhVertLink = td_phys + 10 * sizeTD; while ( td[10].tdStatus & (1 << 23) ); // Active?? } //========================================================= //---------------------------------------------- // execute the 'Set_Interface' control-transfer //---------------------------------------------- // setup the Request Block 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 linked-list of transfer-descriptors td[8].tdNextTD = td_phys + 9 * sizeTD | VF; td[8].tdStatus = 0x18800000; td[8].tdAction = 0x00E0002D | (dev << 8) | (0 << 19); td[8].tdBuffer = rb_phys + 3*sizeRB; td[9].tdNextTD = 0x00000001; td[9].tdStatus = 0x18800000; td[9].tdAction = 0xFFE00069 | (dev << 8) | (1 << 19); td[9].tdBuffer = 0xFFFFFFFF; // attach linked-list of TDs to Queue Head #1 qh[1].qhVertLink = td_phys + 8 * sizeTD | VF; // add Queue Head #1 into the schedule again frindx = ( inw( io + 6 ) + 5 ) % 1024; // FRNUM framelist[ frindx ] = qh_phys + 1*sizeQH | QH; // poll the Transfer Descriptor status while ( td[9].tdStatus & (1 << 23) ) if ( td[8].tdStatus & (1 << 22) ) break; // remove Queue Head #1 from the schedule framelist[ frindx ] = qh_phys + 0*sizeQH | QH; if ( (qh[1].qhVertLink & 1 ) == 0 ) { printf( " SET_INTERFACE failed \n\n" ); exit(1); } printf( " executed SET_INTERFACE control-transfer:" ); printf( " interface=%d \n\n", rb[3].wValue ); // reinitialize our framelist with invalid pointers for (int i = 0; i < 1024; i++) framelist[i] = 0x00000001; //===================================== // Exhibit the sequence of TDs and QHs //===================================== printf( " UHCI Transfer Descriptors \n" ); for (int i = 0; i <= 10; 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( " UHCI Queue Heads \n" ); for (int i = 0; i < 2; i++) { unsigned int *entry = (unsigned int *)&qh[i]; printf( " QH%X ", i ); printf( "<0x%08X>: ", qh_phys + i * sizeQH ); for (int j = 0; j < 4; j++) printf( "%08X ", entry[j] ); printf( "\n" ); } printf( "\n" ); }