//------------------------------------------------------------------- // ajayxmit.cpp // // This example illustrates programming of the EHCI controller // to perform a High-Speed PC-to-PC transfer of data using the // 'NET20DC' USB 2.0 Debug Cable made by Ajays Technology LLC. // It can be tested with our companion 'ajayrecv.cpp' program. // This application, being independent of the EHCI Debug Port, // does not require connecting that cable at a specific outlet // nor are its bulk-transfers limited to using 8-byte packets. // (Transfers of textfiles of sizes up to 20KB are supported.) // // to compile: $ g++ ajayxmit.cpp -o ajayxmit // to execute: $ ./ajayxmit // // NOTE: Requires installing our 'ehci.c' device-driver module. // // programmer: ALLAN CRUSE // date begun: 04 APR 2010 // completion: 11 APR 2010 // revised on: 26 APR 2010 -- add Data-Toggle reset for EndPt 1 // revised on: 28 APR 2010 -- remove some unneeded transactions // revised on: 29 APR 2010 -- corrected calculation for txbytes //------------------------------------------------------------------- #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 EHCI_BASE 0x80000 // suitable address for i/o mapping #define PAGE_SIZE 0x1000 // memory-granularity on x86 system #define VENDOR_ID 0x0525 // USB Vendor Identification-number #define DEVICE_ID 0x127A // USB Device Identification-number 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 bmReqType; unsigned char bRequest; unsigned short wValue; unsigned short wIndex; unsigned short wLength; } USB_RB; // USB Request Block typedef struct { unsigned int qhHorizontalLink; unsigned int qhEndPtCharacteristics; unsigned int qhEndPtCapabilities; unsigned int qhCurrentTDPtr; unsigned int qhNextTDPtr; unsigned int qhAltNextTDPtr; unsigned char qhStatus; unsigned char qhAction; unsigned short qhTotalBytes; unsigned int qhBufferPtr[5]; unsigned int qhExtBufferPtr[5]; unsigned int qhPadding[15]; } EHCI_QH64; // EHCI Queue Head typedef struct { unsigned int tdNextTDPtr; unsigned int tdAltNextTDPtr; unsigned char tdStatus; unsigned char tdAction; unsigned short tdTotalBytes; unsigned int tdBufferPtr[5]; unsigned int tdExtBufferPtr[5]; unsigned int tdPadding[3]; } EHCI_TD64; // EHCI Transfer Descriptor void display_QH64( int index ); void display_TD64( int index ); char devname[] = "/dev/ehci"; unsigned long kmem_phys; unsigned int *lp = (unsigned int *)(EHCI_BASE + PAGE_SIZE * 0); EHCI_QH64 *qh = (EHCI_QH64 *)(EHCI_BASE + PAGE_SIZE * 1); EHCI_TD64 *td = (EHCI_TD64 *)(EHCI_BASE + PAGE_SIZE * 3); USB_RB *rb = (USB_RB *)(EHCI_BASE + PAGE_SIZE * 4); USB_DD *dd = (USB_DD *)(EHCI_BASE + PAGE_SIZE * 5); unsigned char *bf = (unsigned char*)(EHCI_BASE + PAGE_SIZE * 6); unsigned int usb2cmd, usb2sts, usb2intr, asynclistaddr; unsigned int qh_phys, td_phys, rb_phys, dd_phys, bf_phys; void restore_asynclist_address( void ) { lp[ 0x20 >> 2 ] &= ~(1<<5); // disable asynchronous schedule while( ( lp[ 0x24 >> 2 ] & (1<<15) ) != 0 ); // wait till done lp[ 0x38 >> 2 ] = asynclistaddr; // restore initial value usb2sts = lp[ 0x24 >> 2 ]; // read current status lp[ 0x24 >> 2 ] = usb2sts; // reset write-to-clears lp[ 0x28 >> 2 ] = usb2intr; // restore initial value lp[ 0x20 >> 2 ] = usb2cmd; // restore initial value } void my_sigint_handler( int signo ) { exit(1); } int main( int argc, char **argv ) { // open the device-file for reading and writing int fd = open( devname, O_RDWR ); if ( fd < 0 ) { perror( devname ); exit(1); } // initialize our physical region memory-addresses if ( ioctl( fd, 0, &kmem_phys ) < 0 ) { perror( "ioctl" ); exit(1); } qh_phys = kmem_phys + PAGE_SIZE * 0; td_phys = kmem_phys + PAGE_SIZE * 2; rb_phys = kmem_phys + PAGE_SIZE * 3; dd_phys = kmem_phys + PAGE_SIZE * 4; bf_phys = kmem_phys + PAGE_SIZE * 5; // map EHCI registers and kernel-memory regions into userspace int size = lseek( fd, 0, SEEK_END ); int prot = PROT_READ | PROT_WRITE; int flag = MAP_FIXED | MAP_SHARED; void *mm = (void*)EHCI_BASE; if ( mmap( mm, size, prot, flag, fd, 0 ) == MAP_FAILED ) { perror( "mmap" ); exit(1); } // save some initial register-values to be restored later usb2cmd = lp[ 0x20 >> 2 ]; usb2sts = lp[ 0x24 >> 2 ]; usb2intr = lp[ 0x28 >> 2 ]; asynclistaddr = lp[ 0x38 >> 2 ]; // insure that 'asynclistaddr' will be restored upon exit atexit( restore_asynclist_address ); signal( SIGINT, my_sigint_handler ); // preinitialize a linked list of four Transfer Descriptors memset( &td[0], 0x00, 8 * sizeof( EHCI_TD64 ) ); td[0].tdNextTDPtr = 0x00000001; // invalid td[0].tdAltNextTDPtr = 0x00000001; // invalid td[0].tdStatus = 0x40; // Halted td[1].tdNextTDPtr = td_phys + 2 * sizeof( EHCI_TD64 ); td[1].tdAltNextTDPtr = 0x00000001; // invalid td[2].tdNextTDPtr = td_phys + 3 * sizeof( EHCI_TD64 ); td[2].tdAltNextTDPtr = 0x00000001; // invalid td[3].tdNextTDPtr = td_phys + 0 * sizeof( EHCI_TD64 ); td[3].tdAltNextTDPtr = 0x00000001; // invalid // setup a circular list containing a pair of queue-heads memset( &qh[0], 0x00, 2 * sizeof( EHCI_QH64 ) ); qh[0].qhHorizontalLink = (qh_phys + 1 * sizeof( EHCI_QH64 )) | (1<<1); qh[0].qhEndPtCharacteristics = (1<<15); // H=1 qh[0].qhEndPtCapabilities = 0x00000000; // MULT=0 qh[0].qhNextTDPtr = td_phys; // TD#0 qh[0].qhAltNextTDPtr = 0x00000001; // invalid qh[1].qhHorizontalLink = (qh_phys + 0 * sizeof( EHCI_QH64 )) | (1<<1); qh[1].qhEndPtCharacteristics = 0x40406000; // H=0 qh[1].qhEndPtCapabilities = 0x40000000; // MULT=1 qh[1].qhNextTDPtr = td_phys; // TD#0 qh[1].qhAltNextTDPtr = 0x00000001; // invalid // now activate this temporarily 'empty' asynchronous schedule lp[ 0x20 >> 2 ] |= (1<<0); // make sure the EHCI is running while ( ( lp[ 0x24 >> 2 ] & (1<<12) ) != 0 ); // spin till done lp[ 0x20 >> 2 ] &= ~(1<<5); // disable acynchronous schedule while ( ( lp[ 0x24 >> 2 ] & (1<<15) ) != 0 ); // spin till done lp[ 0x38 >> 2 ] = qh_phys | (1<<1); // our queue-head lp[ 0x20 >> 2 ] |= (1<<5); // reenable acynchronous schedule while ( ( lp[ 0x24 >> 2 ] & (1<<15) ) == 0 ); // spin till done // setup 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 = 0x12; // setup the 'Set_Configuration' request-block rb[1].bmReqType = 0x00; rb[1].bRequest = 0x09; rb[1].wValue = 0x0001; rb[1].wIndex = 0x0000; rb[1].wLength = 0x0000; // setup the 'Clear_Feature' request-block rb[2].bmReqType = 0x02; rb[2].bRequest = 0x01; rb[2].wValue = 0x0000; rb[2].wIndex = 0x0001; rb[2].wLength = 0x0000; // loop to discover the device-address of the NET20DC peripheral // and -- when found -- prepare it for sending a bulk-transfer unsigned int dev = 0; do { // overwrite any previous data in the destination buffer memset( &dd[0], 0xFF, sizeof( USB_DD ) ); // (re)initialize volatile fields in linked-list of TDs td[1].tdNextTDPtr = td_phys + 2 * sizeof( EHCI_TD64 ); td[1].tdAltNextTDPtr = 0x00000001; // invalid td[1].tdStatus = 0x80; // Active td[1].tdAction = 0x0E; // SETUP td[1].tdTotalBytes = 0x0008 | (0<<15); // DATA0 td[1].tdBufferPtr[0] = rb_phys + 0 * sizeof( USB_RB ); // RB#0 td[2].tdNextTDPtr = td_phys + 3 * sizeof( EHCI_TD64 ); td[2].tdAltNextTDPtr = 0x00000001; // invalid td[2].tdStatus = 0x80; // Active td[2].tdAction = 0x0D; // IN td[2].tdTotalBytes = 0x0012 | (1<<15); // DATA1 td[2].tdBufferPtr[0] = dd_phys; // Descriptor-Buffer td[3].tdNextTDPtr = td_phys + 4 * sizeof( EHCI_TD64 ); td[3].tdAltNextTDPtr = 0x00000001; // invalid td[3].tdStatus = 0x80; // Active td[3].tdAction = 0x0C; // OUT td[3].tdTotalBytes = 0x0000 | (1<<15); // DATA1 td[4].tdNextTDPtr = td_phys + 5 * sizeof( EHCI_TD64 ); td[4].tdAltNextTDPtr = 0x00000001; // invalid td[4].tdStatus = 0x80; // Active td[4].tdAction = 0x0E; // SETUP td[4].tdTotalBytes = 0x0008 | (0<<15); // DATA0 td[4].tdBufferPtr[0] = rb_phys + 1 * sizeof( USB_RB ); // RB#1 td[5].tdNextTDPtr = td_phys + 6 * sizeof( EHCI_TD64 ); td[5].tdAltNextTDPtr = 0x00000001; // invalid td[5].tdStatus = 0x80; // Active td[5].tdAction = 0x0D; // IN td[5].tdTotalBytes = 0x0000 | (1<<15); // DATA1 td[6].tdNextTDPtr = td_phys + 7 * sizeof( EHCI_TD64 ); td[6].tdAltNextTDPtr = 0x00000001; // invalid td[6].tdStatus = 0x80; // Active td[6].tdAction = 0x0E; // SETUP td[6].tdTotalBytes = 0x0008 | (0<<15); // DATA0 td[6].tdBufferPtr[0] = rb_phys + 2 * sizeof( USB_RB ); // RB#2 td[7].tdNextTDPtr = 0x00000001; // invalid td[7].tdAltNextTDPtr = 0x00000001; // invalid td[7].tdStatus = 0x80; // Active td[7].tdAction = 0x0D; // IN td[7].tdTotalBytes = 0x0000 | (1<<15); // DATA1 // (re)attach list of TDs to Queue Head #1 in the schedule qh[1].qhEndPtCharacteristics = 0x40406000 | dev; // H=0 qh[1].qhNextTDPtr = td_phys + 1 * sizeof( EHCI_TD64) ; qh[1].qhStatus = 0x00; // not 'Halted' // wait until this Queue Head #1 is no longer 'active' while ( td[7].tdStatus & 0x80 ) // still 'Active'? if ( qh[1].qhStatus & 0x40 ) break; // Halted? // break if the NET20DC device-descriptor was received if (( dd[0].bDescriptorType == 1 ) &&( dd[0].idVendor == VENDOR_ID ) &&( dd[0].idProduct == DEVICE_ID )) break; } while ( ++dev < 128 ); if ( dev == 128 ) { printf( "\n NET20DC device not detected\n\n" ); exit(1); } printf( "\n NET20DC device has USB device-address %u \n\n", dev ); // display the relevant QH and TD descriptors for (int i = 1; i <= 1; i++) { unsigned int *el = (unsigned int *)&qh[i]; printf( "\nQH%X ", i ); printf( "<%08X>: ", qh_phys + i * sizeof( EHCI_QH64 ) ); for (int j = 0; j < 10; j++) { if ( j == 4 ) printf( "\n " ); printf( " %08X", el[j] ); } } printf( "\n" ); for (int i = 1; i <= 7; i++) { unsigned int *el = (unsigned int *)&td[i]; printf( "\nTD%X ", i ); printf( "<%08X>: ", td_phys + i * sizeof( EHCI_TD64 ) ); for (int j = 0; j < 6; j++) printf( " %08X", el[j] ); } printf( "\n\n" ); //-------------------------------------------------------- // initialize our kernel-buffer with the data to transfer //-------------------------------------------------------- int nbytes = 1025; // default transfer-size for (int i = 0; i < nbytes; i++) { int j = i / 64; int k = i % 64; bf[i] = ( k == 63 ) ? '\n' : 'A' + k; } int dat = open( argv[1], O_RDONLY ); if ( dat > 0 ) { int rbytes = read( dat, bf, 5*PAGE_SIZE ); if ( rbytes > 0 ) nbytes = rbytes; } printf( "\nTransferring %lu bytes...\n", nbytes ); for (int i = 0; i < nbytes; i++) printf( "%c", bf[ i ] ); printf( "\n" ); //----------------------------------------------------- // now we implement an 'OUT' transaction to endpoint 1 //----------------------------------------------------- // setup the linked-list of 'OUT' transfer-descriptors memset( &td[8], 0x00, sizeof( EHCI_TD64 ) ); td[8].tdNextTDPtr = td_phys + 9 * sizeof( EHCI_TD64 ); td[8].tdAltNextTDPtr = td_phys + 10 * sizeof( EHCI_TD64 ); td[8].tdStatus = 0x80; // Active td[8].tdAction = 0x0C; // OUT td[8].tdTotalBytes = nbytes | (0<<15); // DATA0 for (int i = 0; i < 5; i++) td[8].tdBufferPtr[i] = bf_phys + i * PAGE_SIZE; memset( &td[9], 0x00, sizeof( EHCI_TD64 ) ); td[9].tdNextTDPtr = td_phys + 0 * sizeof( EHCI_TD64 ); td[9].tdAltNextTDPtr = td_phys + 0 * sizeof( EHCI_TD64 ); td[9].tdStatus = 0x80; // Active td[9].tdAction = 0x0C; // OUT td[9].tdTotalBytes = 0x0000 | (0<<15); // DATA0 memset( &td[10], 0x00, sizeof( EHCI_TD64 ) ); td[10].tdNextTDPtr = td_phys + 0 * sizeof( EHCI_TD64 ); td[10].tdAltNextTDPtr = td_phys + 0 * sizeof( EHCI_TD64 ); td[10].tdStatus = 0x80; // Active td[10].tdAction = 0x0C; // OUT td[10].tdTotalBytes = 0x0000 | (0<<15); // DATA0 printf( "\n Press key to transfer %d bytes...\n", nbytes ); getchar(); // attach list of TDs to Queue Head #1 in the schedule unsigned int EndPt = 1; qh[1].qhEndPtCharacteristics = 0xF2002000 | dev | (EndPt<<8); // DTC=0 qh[1].qhNextTDPtr = td_phys + 8 * sizeof( EHCI_TD64) ; qh[1].qhStatus = 0x00; // not 'Halted' // wait until this Queue #1 is no longer 'active' while (( td[9].tdStatus == 0x80 )&&( td[10].tdStatus == 0x80 )) if ( qh[1].qhStatus & 0x40 ) break; // Halted? int txbytes = td[8].tdBufferPtr[0] & 0xFFF; txbytes += ((td[8].tdAction >> 4)&7) * PAGE_SIZE;; if (( !txbytes )&&( !(td[8].tdTotalBytes & 0x7FFF) )) txbytes = nbytes; printf( " OK, transmitted %u bytes \n", txbytes ); // display the relevant QH and TD descriptors for (int i = 1; i <= 1; i++) { unsigned int *el = (unsigned int *)&qh[i]; printf( "\nQH%X ", i ); printf( "<%08X>: ", qh_phys + i * sizeof( EHCI_QH64 ) ); for (int j = 0; j < 10; j++) { if ( j == 4 ) printf( "\n " ); printf( " %08X", el[j] ); } } printf( "\n" ); for (int i = 1; i <= 10; i++) { unsigned int *el = (unsigned int *)&td[i]; printf( "\nTD%X ", i ); printf( "<%08X>: ", td_phys + i * sizeof( EHCI_TD64 ) ); for (int j = 0; j < 6; j++) printf( " %08X", el[j] ); } printf( "\n\n" ); }