//------------------------------------------------------------------- // ajfschar.c // // This is an experimental character-mode device-driver for the // 'NET20DC' USB 2.0 Debug Cable when operating at 'Full-Speed' // under the control of an Intel UHCI host controller. (It may // be necessary to remove the Linux 'ehci_hcd' device-driver on // at least one end of the cable in order for 'Full-Speed' mode // to replace the 'High-Speed' default mode.) For now the only // driver-method implemented are 'read()' and 'write()'. These // can be tested by using our 'xmitajay.cpp' and 'recvajay.cpp' // programs, or by using the shell's 'echo' and 'cat' commands. // We provide the '/proc/ajfschar' pseudo-file for easy viewing // of some important kernel data-structures during development. // // NOTE: Written and tested with Linux kernel version 2.6.33.2. // // programmer: ALLAN CRUSE // date begun: 13 MAY 2010 // completion: 02 JUN 2010 //------------------------------------------------------------------- #include // for init_module() #include // for create_proc_read_entry() #include // for pci_get_class() #include // for signal_pending() #include // for copy_from_user() #define CLASS_UHCI 0x0C0300 // PCI Class-Code for UHCI Host #define MAX_HC 8 // maximum number of UHCI Hosts #define KMEM_SIZE 65536 // size of kernel memory buffer #define QH (1<<1) // descriptor-flag for 'queue-head' #define VF (1<<2) // descriptor-flag for 'depth-first' #define BUFFER_SZ 40960 // maximum size of transfer-buffer #define VENDOR_ID 0x0525 // Vendor-ID for the NET20DC #define DEVICE_ID 0x127A // Product-ID for the NET20DC 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 tdNextTD; unsigned int tdStatus; unsigned int tdAction; unsigned int tdBuffer; } UHCI_TD; // UHCI Transfer Descriptor typedef struct { unsigned int qhHorzLink; unsigned int qhVertLink; unsigned int qhPadding0; unsigned int qhPadding1; } UHCI_QH; // UHCI Queue Head char modname[] = "ajfschar"; char devname[] = "ajay"; int my_major = 79; int n_hcs = 0; struct pci_dev *hcp[ MAX_HC ]; unsigned int iobase[ MAX_HC ]; void *kmem; unsigned int kmem_phys; const int sizeRB = sizeof( USB_RB ); const int sizeDD = sizeof( USB_DD ); const int sizeTD = sizeof( UHCI_TD ); const int sizeQH = sizeof( UHCI_QH ); unsigned long hub_count, port_addr; unsigned int *framelist; UHCI_QH *qh; UHCI_TD *td; USB_RB *rb; USB_DD *dd; unsigned char *bf; unsigned int fr_phys, qh_phys, td_phys, rb_phys, dd_phys, bf_phys; unsigned short usbcmd, usbsts, usbintr, frnum, portsc0, portsc1; unsigned int io, frbaseaddr, dev, hub; ssize_t my_write( struct file *file, const char *buf, size_t len, loff_t *pos ) { int endpt = 1; // device's OUT endpoint-address int txbytes = 0; // number of bytes transmitted int sizeBF = 64; // maximum packet-size int dt = 0; // initial data-toggle if ( len > BUFFER_SZ ) len = BUFFER_SZ; // avoid buffer-overrun qh[0].qhVertLink = 0x00000001; // avoid race-cdondition // copy the data from userspace to our kernel buffer if ( copy_from_user( bf, buf, len ) ) return -EFAULT; // activate our schedule on this Host Controller outw( 0x0000, io + 4 ); // USBINTR = none 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 saved frbaseaddr = inl( io + 8 ); // FRBASEADDR saved outw( 0x0000, io + 6 ); // FRNUM setup outl( fr_phys, io + 8 ); // FRBASEADDR setup outw( inw( io + 0 ) | (1<<0), io + 0 ); // USBCMD R/S=1 while(( inw( io + 2 ) & (1<<5)) != 0 ); // HCHalted? // main loop for bulk-transfer of data packets while (( txbytes <= len )&&( txbytes != BUFFER_SZ )) { // reinitialize our list of Transfer-Descriptors int to_do = len - txbytes; int max = (to_do < sizeBF) ? to_do : sizeBF; td[0].tdNextTD = 0x00000001; td[0].tdStatus = 0x18800000; // Full-Speed, Active td[0].tdAction = 0x000000E1; // OUT td[0].tdAction |= (dev << 8); // device-address td[0].tdAction |= (endpt<<15); // device-endpoint td[0].tdAction |= (max-1)<<21; // MaxLen td[0].tdAction |= (dt << 19); // data-toggle td[0].tdBuffer = bf_phys + txbytes; // attach the linked-list of TDs to our Queue-Head qh[0].qhVertLink = td_phys; // wait until list is traversed, or until HC stalls while ( qh[0].qhVertLink != 0x00000001 ) { if ( td[0].tdStatus & (1 << 22) ) break; else schedule(); if ( signal_pending( current ) ) break; } if ( signal_pending( current ) ) break; // add the number of new bytes sent to our prior total max = (td[0].tdStatus + 1) & 0x7FF; if ( ( td[0].tdStatus & 0x00FF0000 ) == 0 ) txbytes += max; // quit when the packet had fewer bytes than the maximum if ( max < sizeBF ) break; // otherwise prepare to transmit another data-packet dt = (1 + dt) % 2; // adjust data-toggle qh[0].qhVertLink = 0x00000001; // detach list of TDs } // reinitialize this endpoint's data-toggle qh[0].qhVertLink = 0x00000001; td[5].tdStatus = 0x18800000; td[6].tdStatus = 0x18800000; td[6].tdNextTD = 0x00000001; qh[0].qhVertLink = td_phys + 5 * sizeTD; while ( qh[0].qhVertLink != 0x00000001 ) { schedule(); if ( signal_pending( current ) ) break; if ( td[5].tdStatus & (1 << 22) ) break; } qh[0].qhVertLink = 0x00000001; // resume the former schedule on this controller outw( inw( io + 0 ) & ~(1<<0), io + 0 ); // USBCMD R/S=0 while (( inw( io + 2 ) & (1<<5)) == 0 ); // USBSTS HCHalted? outw( inw( io + 2 ), io + 2 ); // clear WC bits outw( frnum, io + 6 ); // FRNUM prior outl( frbaseaddr, io + 8 ); // FRBASEADDR prior outw( usbcmd, io + 0 ); // USBCMD prior outw( usbintr, io + 4 ); // USBINTR prior outw( inw( io + 16 ), io + 16 ); // clear WC bits outw( inw( io + 18 ), io + 18 ); // clear WC bits outw( inw( io + 2 ), io + 2 ); // clear WC bits // printk( " Wrote %u bytes to '/dev/ajay' \n", txbytes ); return txbytes; } ssize_t my_read( struct file *file, char *buf, size_t len, loff_t *pos ) { int endpt = 2; // device's IN endpoint-address int rxbytes = 0; // number of bytes received int sizeBF = 64; // maximum packet-size int dt = 0; // initial data-toggle if ( len > BUFFER_SZ ) len = BUFFER_SZ; // avoid buffer-overrun qh[0].qhVertLink = 0x00000001; // avoid race-condition // activate our schedule on this Host Controller outw( 0x0000, io + 4 ); // USBINTR = none 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 saved frbaseaddr = inl( io + 8 ); // FRBASEADDR saved outw( 0x0000, io + 6 ); // FRNUM setup outl( fr_phys, io + 8 ); // FRBASEADDR setup outw( inw( io + 0 ) | (1<<0), io + 0 ); // USBCMD R/S=1 while(( inw( io + 2 ) & (1<<5)) != 0 ); // HCHalted? // main loop for bulk-transfer of data packets while ( rxbytes + sizeBF <= BUFFER_SZ ) { int max = sizeBF; // reinitialize our list of Transfer-Descriptors td[0].tdNextTD = 0x00000001; td[0].tdStatus = 0x18800000; // Full-Speed, Active td[0].tdAction = 0x00000069; // IN td[0].tdAction |= (dev << 8); // device-address td[0].tdAction |= (endpt<<15); // device-endpoint td[0].tdAction |= (max-1)<<21; // MaxLen=64 td[0].tdAction |= (dt << 19); // data-toggle td[0].tdBuffer = bf_phys + rxbytes; // attach the linked-list of TDs to our Queue-Head qh[0].qhVertLink = td_phys; // wait until list is traversed, or until HC stalls while ( qh[0].qhVertLink != 0x00000001 ) { if ( td[0].tdStatus & (1 << 22) ) break; else schedule(); if ( signal_pending( current ) ) break; } if ( signal_pending( current ) ) break; // add the number of new bytes received to prior total max = (td[0].tdStatus + 1) & 0x7FF; if ( ( td[0].tdStatus & 0x00FF0000 ) == 0 ) rxbytes += max; // quit when packet has fewer bytes than the maximum if ( max < sizeBF ) break; // otherwise prepare to receive another data-packet dt = (1 + dt) % 2; // adjust data-toggle qh[0].qhVertLink = 0x00000001; // detach list of TDs } // reinitialize this endpoint's data-toggle qh[0].qhVertLink = 0x00000001; td[7].tdStatus = 0x18800000; td[8].tdStatus = 0x18800000; td[8].tdNextTD = 0x00000001; qh[0].qhVertLink = td_phys + 7 * sizeTD; while ( qh[0].qhVertLink != 0x00000001 ) { schedule(); if ( signal_pending( current ) ) break; if ( td[7].tdStatus & (1 << 22 ) ) break; } qh[0].qhVertLink = 0x00000001; // resume the former schedule on this controller outw( inw( io + 0 ) & ~(1<<0), io + 0 ); // USBCMD R/S=0 while (( inw( io + 2 ) & (1<<5)) == 0 ); // USBSTS HCHalted? outw( inw( io + 2 ), io + 2 ); // clear WC bits outw( frnum, io + 6 ); // FRNUM prior outl( frbaseaddr, io + 8 ); // FRBASEADDR prior outw( usbcmd, io + 0 ); // USBCMD prior outw( usbintr, io + 4 ); // USBINTR prior outw( inw( io + 16 ), io + 16 ); // clear WC bits outw( inw( io + 18 ), io + 18 ); // clear WC bits outw( inw( io + 2 ), io + 2 ); // clear WC bits // transfer the received data-bytes to the user's buffer len = rxbytes; if ( copy_to_user( buf, bf, len ) ) return -EFAULT; // printk( " Received %u bytes from '/dev/ajay' \n", rxbytes ); return rxbytes; } struct file_operations my_fops = { owner: THIS_MODULE, write: my_write, read: my_read, }; int my_proc_read( char *buf, char **start, off_t off, int count, int *eof, void *data ) { int i, j, len = 0; len += sprintf( buf+len, "\n Queue-Heads:" ); len += sprintf( buf+len, " qhHorzLink qhVertLink \n" ); for (i = 0; i < 1; i++) { unsigned int *elem = (unsigned int *)&qh[i]; len += sprintf( buf+len, " " ); len += sprintf( buf+len, " <0x%08X> ", qh_phys + i * sizeQH ); len += sprintf( buf+len, " QH%X:", i ); for (j = 0; j < 2; j++) len += sprintf( buf+len, " %08X ", elem[j] ); len += sprintf( buf+len, "\n" ); } len += sprintf( buf+len, "\nTransfer Descriptors:" ); len += sprintf( buf+len, " tdNextTD tdStatus " ); len += sprintf( buf+len, " tdAction tdBuffer \n" ); for (i = 0; i < 9; i++) { unsigned int *elem = (unsigned int *)&td[i]; len += sprintf( buf+len, " " ); len += sprintf( buf+len, " <0x%08X> ", td_phys + i * sizeTD ); len += sprintf( buf+len, " TD%X:", i ); for (j = 0; j < 4; j++) len += sprintf( buf+len, " %08X ", elem[j] ); len += sprintf( buf+len, "\n" ); } len += sprintf( buf+len, "\n USB Request-Blocks: \n" ); for (i = 0; i < 4; i++) { unsigned int *elem = (unsigned int *)&rb[i]; len += sprintf( buf+len, " " ); len += sprintf( buf+len, " <0x%08X> ", rb_phys + i * sizeRB ); len += sprintf( buf+len, " RB%X:", i ); for (j = 0; j < 2; j++) len += sprintf( buf+len, " %08X", elem[j] ); if ( (i % 2) == 1 ) len += sprintf( buf+len, "\n" ); } len += sprintf( buf+len, "\n Device-Descriptors: \n" ); for (i = 0; i < 1; i++) { unsigned short *elem = (unsigned short *)&dd[i]; len += sprintf( buf+len, " " ); len += sprintf( buf+len, " <0x%08X> ", dd_phys + i * sizeDD ); len += sprintf( buf+len, " DD%X:", i ); for (j = 0; j < 9; j++) len += sprintf( buf+len, " %04X", elem[j] ); len += sprintf( buf+len, "\n" ); } len += sprintf( buf+len, "\n" ); return len; } static int __init ajfschar_init( void ) { struct pci_dev *devp = NULL; int i; printk( "<1>\nInstalling \'%s\' module ", modname ); printk( "supporting '/dev/ajay' (major=%d) \n", my_major ); // detect installed UHCI controllers while ( n_hcs < MAX_HC ) { devp = pci_get_class( CLASS_UHCI, devp ); if ( !devp ) break; hcp[ n_hcs ] = devp; iobase[ n_hcs ] = pci_resource_start( devp, 4 ); ++n_hcs; } printk( " Detected %d UHCI controllers \n", n_hcs ); if ( n_hcs == 0 ) return -ENODEV; for (i = 0; i < n_hcs; i++) printk( " #%d: iobase=0x%04X \n", i+1, iobase[ i ] ); // allocate kernel memory region (for DMA by UHCI controllers) kmem = kmalloc( KMEM_SIZE, GFP_KERNEL | GFP_DMA ); if ( !kmem ) return -ENOMEM; kmem_phys = virt_to_phys( kmem ); printk( " kernel memory allocation at physical address-range " ); printk( "0x%08X-0x%08X \n", kmem_phys, kmem_phys + KMEM_SIZE ); // assign physical memory-areas for specific purposes fr_phys = kmem_phys + PAGE_SIZE * 0; qh_phys = kmem_phys + PAGE_SIZE * 1; 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; // setup the virtual addresses for these memory areas framelist = phys_to_virt( fr_phys ); qh = phys_to_virt( qh_phys ); td = phys_to_virt( td_phys ); rb = phys_to_virt( rb_phys ); dd = phys_to_virt( dd_phys ); bf = phys_to_virt( bf_phys ); // 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; // 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; // build the 'Clear_Feature' Request Block (EndPoint 1) rb[2].bmReqType = 0x02; rb[2].bRequest = 0x01; rb[2].wValue = 0x0000; rb[2].wIndex = 0x0001; rb[2].wLength = 0x0000; // build the 'Clear_Feature' Request Block (Endpoint 2) rb[3].bmReqType = 0x02; rb[3].bRequest = 0x01; rb[3].wValue = 0x0000; rb[3].wIndex = 0x0082; rb[3].wLength = 0x0000; // build the initially empty 'Queue-Head' descriptor qh[0].qhHorzLink = 0x00000001; qh[0].qhVertLink = 0x00000001; qh[0].qhPadding0 = 0xFFFFFFFF; qh[0].qhPadding1 = 0xFFFFFFFF; // build the framelist array of Queue-Head pointers for (i = 0; i < 1024; i++) framelist[i] = ( qh_phys + 0 * sizeQH ) | QH; //------------------------------- // search for the NET20DC device //------------------------------- for (hub = 0; hub < n_hcs; hub++) { io = iobase[ hub ]; printk( "\n" ); printk( " hub #%d: iobase=0x%04X ", 1+hub, io ); printk( " USBCMD=%04X ", inw( io + 0 ) ); printk( " USBSTS=%04X ", inw( io + 2 ) ); printk( " USBINTR=%04X ", inw( io + 4 ) ); printk( "\n " ); printk( " FRNUM=%04X ", inw( io + 6 ) ); printk( " FRBASEADDR=%04X ", inl( io + 8 ) ); printk( " PORTSC0=%04X ", inw( io + 16 ) ); printk( " PORTSC1=%04X ", inw( io + 18 ) ); printk( "\n" ); usbsts = inw( io + 2 ); if ( usbsts & (1<<5) ) // HCHalted? { printk( " Host Controller is halted \n" ); continue; } portsc0 = inw( io + 16 ); portsc1 = inw( io + 18 ); if ((( portsc0 & 0x101) == 0x001 ) ||(( portsc1 & 0x101 ) == 0x001 )) { printk( " Checking 'Full-Speed' device" ); printk( " found on UHCI #%d ... ", 1 + hub ); if (( portsc0 & 0x1000 )||( portsc1 & 0x1000 )) { printk( "device is suspended\n" ); continue; } else printk( "\n" ); } else { printk( " No 'Full-Speed' device is detected" ); printk( " on UHCI #%d \n", 1 + hub ); continue; } // save some UHCI registers to be restored later 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 ); // overwrite any prior data in our destination-buffer memset( dd, 0xFF, sizeDD ); // activate our schedule on this Host Controller outw( 0x0000, io + 4 ); // USBINTR = none 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 saved frbaseaddr = inl( io + 8 ); // FRBASEADDR saved outw( 0x0000, io + 6 ); // FRNUM setup outl( fr_phys, io + 8 ); // FRBASEADDR setup outw( inw( io + 0 ) | (1<<0), io + 0 ); // USBCMD R/S=1 while(( inw( io + 2 ) & (1<<5)) != 0 ); // HCHalted? // iterate over the potential device-address values for ( dev = 0; dev < 128; dev++) { // The SETUP stage (for 'Get_Descriptor') td[0].tdNextTD = (td_phys + 1 * sizeTD) | VF; td[0].tdStatus = 0x18800000; // Full-Speed, Active td[0].tdAction = 0x00E0002D; // SETUP, MaxLen=8 td[0].tdAction |= (dev << 8); // device-address td[0].tdAction |= (0 << 19); // data-toggle DATA0 td[0].tdBuffer = rb_phys + 0 * sizeRB; // Request Block // The DATA stage (for 'Get_Descriptor') td[1].tdNextTD = (td_phys + 2 * sizeTD) | VF; td[1].tdStatus = 0x18800000; // Full-Speed, Active td[1].tdAction = 0x02200069; // IN, MaxLen=18 td[1].tdAction |= (dev << 8); // device-address td[1].tdAction |= (1 << 19); // data-toggle DATA1 td[1].tdBuffer = dd_phys ; // Descriptor buffer // The HANDSHAKE stage (for 'Get_Descriptor') td[2].tdNextTD = (td_phys + 3 * sizeTD) | VF; td[2].tdStatus = 0x18800000; // Full-Speed, Active td[2].tdAction = 0xFFE000E1; // OUT, MaxLen=0 td[2].tdAction |= (dev << 8); // device-address td[2].tdAction |= (1 << 19); // data-toggle DATA1 td[2].tdBuffer = 0x00000000; // null address // The SETUP stage (for 'Set_Configuration') td[3].tdNextTD = (td_phys + 4 * sizeTD) | VF; td[3].tdStatus = 0x18800000; // Full-Speed, Active td[3].tdAction = 0x00E0002D; // SETUP, MaxLen=8 td[3].tdAction |= (dev << 8); // device-address td[3].tdAction |= (0 << 19); // data-toggle DATA0 td[3].tdBuffer = rb_phys + 1 * sizeRB; // Request Block // The HANDSHAKE stage (for 'Set_Configuration') td[4].tdNextTD = (td_phys + 5 * sizeTD) | VF; td[4].tdStatus = 0x18800000; // Full-Speed, Active td[4].tdAction = 0xFFE00069; // IN, MaxLen=0 td[4].tdAction |= (dev << 8); // device-address td[4].tdAction |= (1 << 19); // data-toggle DATA1 td[4].tdBuffer = 0x00000000; // null address // The SETUP stage (for 'Clear_Feature') td[5].tdNextTD = (td_phys + 6 * sizeTD) | VF; td[5].tdStatus = 0x18800000; // Full-Speed, Active td[5].tdAction = 0x00E0002D; // SETUP, MaxLen=8 td[5].tdAction |= (dev << 8); // device-address td[5].tdAction |= (0 << 19); // data-toggle DATA0 td[5].tdBuffer = rb_phys + 2 * sizeRB; // Request Block // The HANDSHAKE stage (for 'Clear_Feature') td[6].tdNextTD = (td_phys + 7 * sizeTD) | VF; td[6].tdStatus = 0x18800000; // Full-Speed, Active td[6].tdAction = 0xFFE00069; // IN, MaxLen=0 td[6].tdAction |= (dev << 8); // device-address td[6].tdAction |= (1 << 19); // data-toggle DATA1 td[6].tdBuffer = 0x00000000; // null address // The SETUP stage (for 'Clear_Feature') td[7].tdNextTD = (td_phys + 8 * sizeTD) | VF; td[7].tdStatus = 0x18800000; // Full-Speed, Active td[7].tdAction = 0x00E0002D; // SETUP, MaxLen=8 td[7].tdAction |= (dev << 8); // device-address td[7].tdAction |= (0 << 19); // data-toggle DATA0 td[7].tdBuffer = rb_phys + 3 * sizeRB; // Request Block // The HANDSHAKE stage (for 'Clear_Feature') td[8].tdNextTD = 1; //(td_phys + 9 * sizeTD) | VF; td[8].tdStatus = 0x18800000; // Full-Speed, Active td[8].tdAction = 0xFFE00069; // IN, MaxLen=0 td[8].tdAction |= (dev << 8); // device-address td[8].tdAction |= (1 << 19); // data-toggle DATA1 td[8].tdBuffer = 0x00000000; // null address // Attach this linked-list of TDs to our Queue-Head qh[0].qhVertLink = td_phys; // pointer to TD#0 // Wait for queue to become inactive or to stall while ( qh[0].qhVertLink != 0x00000001 ) { if ( td[0].tdStatus & (1 << 22 ) ) break; if ( td[8].tdStatus == 0x180007FF ) break; } // Check for 'NET20DC' device-descriptor if (( dd[0].bDescriptorType == 1 ) &&( dd[0].idVendor == VENDOR_ID ) &&( dd[0].idProduct == DEVICE_ID )) break; qh[0].qhVertLink = 0x00000001; // no races } // resume the former schedule on this controller outw( inw( io + 0 ) & ~(1<<0), io + 0 ); // USBCMD R/S=0 while (( inw( io + 2 ) & (1<<5)) == 0 ); // USBSTS HCHalted? outw( inw( io + 2 ), io + 2 ); // clear WC bits outw( frnum, io + 6 ); // FRNUM prior outl( frbaseaddr, io + 8 ); // FRBASEADDR prior outw( usbcmd, io + 0 ); // USBCMD prior outw( usbintr, io + 4 ); // USBINTR prior outw( inw( io + 16 ), io + 16 ); // clear WC bits outw( inw( io + 18 ), io + 18 ); // clear WC bits outw( inw( io + 2 ), io + 2 ); // clear WC bits if ( dev < 128 ) break; } // report the peripheral's device-address and its attached controller if (( dev == 128 )||( hub == n_hcs )) { printk( "\n 'NET20DC' peripheral was not detected\n" ); return -ENODEV; } printk( "\n 'NET20DC' peripheral found:" ); printk( " UHCI hub-number = %d ", 1 + hub ); printk( " USB device-address = %d \n", dev ); create_proc_read_entry( modname, 0, NULL, my_proc_read, NULL ); return register_chrdev( my_major, devname, &my_fops ); } static void __exit ajfschar_exit(void ) { remove_proc_entry( modname, NULL ); unregister_chrdev( my_major, devname ); kfree( kmem ); printk( "<1>Removing \'%s\' module\n", modname ); } module_init( ajfschar_init ); module_exit( ajfschar_exit ); MODULE_LICENSE("GPL");