//------------------------------------------------------------------- // usbkeybd.cpp // // This Linux application uses direct hardware programming of // a platform's UHCI controllers to operate our Keytronic USB // keyboard peripheral (assuming it was previously attached). // Only basic keyboard-echo functionally is being implemented // in this demo. Function-keys, LEDs, and typematic behavior // are saved as future exercises for any interested students. // // NOTE: This program relies upon our 'uhci'c' device-driver. // // REFERENCE: // "Universal Host Controller Interface (UHCI) Design Guide," // Intel Corporation (March 1996). // // programmer: ALLAN CRUSE // date begun: 24 MAY 2011 // completion: 31 MAY 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 0x03F9 // Key Tronic Corporation #define DEVICE_ID 0x0100 // Keytronic USB Keyboard #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 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 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 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); unsigned int qh_phys, td_phys, rb_phys, dd_phys, frbaseaddr; unsigned short usbcmd, usbsts, usbintr, frnum, portsc0, portsc1; unsigned int io, dev, hub = 1; const int sizeBF = 8; unsigned short *bf = (unsigned short *)(KMEM_BASE + PAGE_SIZE * 6); unsigned int bf_phys; void my_sigint_handler( int signo ) { exit(1); } void display_UHCI_registers( void ) { printf( "\n" ); printf( "Host 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" ); } void restore_framelist_address( 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 ); // reset write-to-clear outw( usbintr, io + 4 ); // restore initial value outw( usbcmd, io + 0 ); // restore initial value } 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 Keytronic 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 ); portsc0 = inw( io + 16 ); portsc1 = inw( io + 18 ); atexit( restore_framelist_address ); signal( SIGINT, my_sigint_handler ); 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(); // construct data-structures for 'Get_Device_Descriptor' rb[0].bmReqType = 0x80; rb[0].bRequest = 0x06; rb[0].wValue = 0x0100; rb[0].wIndex = 0x0000; rb[0].wLength = 0x0012; 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 = 0x1C800000; // Low-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 = 0x1C800000; // Low-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 = 0x1C800000; // Low-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 = 0x1C800000; // Low-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 = 0x1C800000; // Low-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; // see if the control-transfer was successful if ( td[5].tdStatus & (1 << 23 ) ) continue; // see if the Keytronic device was detected if (( dd[0].idVendor == VENDOR_ID ) &&( dd[0].idProduct == DEVICE_ID )) break; } if ( dev < 128 ) break; restore_framelist_address(); } // report the Keytronic keyboard's USB device-number if found if (( hub == hub_count )||( dev == 128 )) { printf( "\n\n Keytronic USB Keyboard was not detected \n\n" ); exit(1); } printf( "\n\n Keytronic USB Keyboard peripheral " ); printf( "found on hub #%d ", hub + 1 ); printf( "using device-number %d \n\n\n", dev ); //================================================================ // temporarily reinitialize our framelist with invalid pointers for (int i = 0; i < 1024; i++) framelist[i] = 0x00000001; //================================================================ // Execute Interrupt Transactions on Endpoint 1 (IN) MaxPacketSize=8 unsigned int action, status, toggle; unsigned int token = 0x69; unsigned int endpt = 1; unsigned int maxpkt = 7; action = (maxpkt << 21) | (endpt << 15) | (dev << 8 ) | token; unsigned int c_err = 3; unsigned int speed = 1; unsigned int active = 1; status = (c_err << 27) | (speed << 26) | (active << 23); // setup the linked-list of active Transfer Descriptors int num_TDs = 32; for (int i = 0; i < num_TDs; i++) { toggle = (i & 1); td[i].tdNextTD = td_phys + sizeTD * (i+1); td[i].tdStatus = status; td[i].tdAction = action | (toggle << 19); td[i].tdBuffer = bf_phys + sizeBF * i; } td[ num_TDs - 1 ].tdNextTD = 0x00000001; // setup the temporarily empty Queue Head qh[0].qhHorzLink = 0x00000001; qh[0].qhVertLink = 0x00000001; qh[0].qhPadding0 = 0xFFFFFFFF; qh[0].qhPadding1 = 0xFFFFFFFF; // erase any prior contents of the buffer-array memset( &bf[0], 0xFF, sizeBF * num_TDs ); // reinitialize our framelist for 8-ms polling-frequency for (int i = 0; i < 1024; i++) { if ( (i % 8) == 4 ) framelist[ i ] = qh_phys | QH; else framelist[ i ] = 0x00000001; } char lowercase[ 256 ] = { 0, 0, 0, 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 0x0A, 0x1B, 0x08, 0x09, 0x20, '-', '=', '[', ']', '\\', 0, ';', '\'', '`', ',', '.', '/' }; char uppercase[ 256 ] = { 0, 0, 0, 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', 0x0A, 0x1B, 0x08, 0x09, 0x20, '_', '+', '{', '}', '|', 0, ':', '\"', '~', '<', '>', '?' }; int qhead = 0; int qtail = 0; short flagcode, scancode; int done = 0; while ( !done ) { // restore the 'active' status in all the linked-list's // Transfer-Descriptors whenever the queue is depleated if ( qh[0].qhVertLink & (1 << 0) ) { for (int i = 0; i < num_TDs; i++) td[i].tdStatus = status; qh[0].qhVertLink = td_phys; qhead = 0; qtail = 0; } // advance the tail-index past completed transfers while ( ((td[ qtail ].tdStatus >> 16) & 0xFF ) == 0 ) { qtail = (1 + qtail); if ( qhead == qtail ) break; } // display ascii-codes for successful keypresses while ( qhead != qtail ) { flagcode = bf[ 4 * qhead + 0 ]; scancode = bf[ 4 * qhead + 1 ]; qhead = (1 + qhead);; // special handling for the -key if ( scancode == 0x29 ) // ESCAPE-key { done = 1; break; } // translate any non-null scancode to its ascii // code and send that ascii-code to the display if ( scancode != 0 ) { int ascii = 0; switch( flagcode ) { case 0x0000: ascii = lowercase[ scancode ]; break; case 0x0002: case 0x0020: ascii = uppercase[ scancode ]; break; } write( STDOUT_FILENO, &ascii, 1 ); } } } printf( "\n" ); //display_UHCI_registers(); printf( "\n" ); }