//------------------------------------------------------------------- // ohci.c // // The purpose of this character-mode Linux device-driver is to // provide user-mode programs with access to privileged kernel- // level resources needed when doing programming experiments on // our Sonnet Allegro USB 2.0 5-port PCI-e add-in Adapter Card. // Specifically, it lets an application memory-map a contiguous // page-aligned region of kernel memory into its userspace, for // DMA usage; also it memory-maps OHCI operational registers to // userspace so they can be read and written by an application. // // NOTE: Developed and tested with Linux kernel version 2.6.34. // // programmer: ALLAN CRUSE // date begun: 03 JUN 2010 // completion: 21 JUN 2010 // revised on: 15 AUG 2011 -- to exhibit more than three ports // revised on: 07 OCT 2011 -- to insure Bus Master capability // revised on: 21 OCT 2011 -- for Linux kernel version 2.6.36 //------------------------------------------------------------------- #include // for init_module() #include // for create_proc_read_entry() #include // for pci_get_class() #include // for copy_to_user() #define CLASS_OHCI 0x0C0310 // USB OHCI Host Controller Hub #define MAX_HOSTS 2 // Maximum number of OHCI Hosts #define KMEM_SIZE 0x20000 // Size of kernel memory region char modname[] = "ohci"; char devname[] = "ohci"; int my_major = 73; struct pci_dev *hubp[ MAX_HOSTS ]; unsigned short devfn[ MAX_HOSTS ]; unsigned int iobase[ MAX_HOSTS ]; unsigned short dev_ID[ MAX_HOSTS ]; unsigned short mfg_ID[ MAX_HOSTS ]; unsigned long kmem_base, kmem_size; unsigned int n_hubs = 0; void *kmem; unsigned long ohci_size = 16 * PAGE_SIZE; loff_t my_llseek( struct file *file, loff_t where, int whence ) { loff_t newpos = -1; switch ( whence ) { case 0: newpos = where; break; // SEEK_SET case 1: newpos = file->f_pos + where; break; // SEEK_CUR; case 2: newpos = ohci_size + where; break; // SEEK_END } if (( newpos < 0 )||( newpos > ohci_size )) return -EINVAL; file->f_pos = newpos; return newpos; } long my_ioctl( struct file *file, unsigned int cmd, unsigned long addr ) { struct inode *inode = file->f_dentry->d_inode; unsigned int minor = iminor( inode ); unsigned int index = (minor - 1) & 0x07; unsigned long kmem_phys = virt_to_phys( kmem ) + index * 0x10000; void *src = &kmem_phys; void *dst = (void*)addr; int len = sizeof( unsigned long ); switch ( cmd ) { case 0: // when an application needs to know the physical // address of this driver's kernel memory buffer if ( copy_to_user( dst, src, len ) ) return -EFAULT; return 0; } return -EINVAL; } int my_mmap( struct file *file, struct vm_area_struct *vma ) { unsigned int minor = iminor( file->f_dentry->d_inode ); unsigned int index = (minor - 1) & 0x07; unsigned long request_length = vma->vm_end - vma->vm_start; unsigned long region1_length = PAGE_SIZE; unsigned long physical_addr1 = iobase[ index ]; unsigned long region2_length = request_length - region1_length; unsigned long physical_addr2 = kmem_base + (0x10000 * index); unsigned long virtual_start1 = vma->vm_start; unsigned long virtual_start2 = vma->vm_start + PAGE_SIZE; // sanity check: mapped region should not exceed kmem-size if ( request_length < PAGE_SIZE ) return -EINVAL; if ( request_length > ohci_size ) return -EINVAL; // let the kernel know not to try swapping out this region vma->vm_flags |= VM_RESERVED; // tell the kernel to exclude this region from core dumps vma->vm_flags |= VM_IO; // ask the kernel to set up the required page-table entries if ( remap_pfn_range( vma, virtual_start1, physical_addr1>>PAGE_SHIFT, region1_length, vma->vm_page_prot ) ) return -EAGAIN; if ( remap_pfn_range( vma, virtual_start2, physical_addr2>>PAGE_SHIFT, region2_length, vma->vm_page_prot ) ) return -EAGAIN; return 0; // SUCCESS } struct file_operations my_fops = { owner: THIS_MODULE, llseek: my_llseek, //ioctl: my_ioctl, unlocked_ioctl: my_ioctl, mmap: my_mmap, }; int my_proc_read( char *buf, char **start, off_t off, int count, int *eof, void *data ) { void *mmio; int i, j, ndp, len = 0; for (i = 0; i < n_hubs; i++) { mmio = ioremap( iobase[ i ], PAGE_SIZE ); if ( !mmio ) return 0; // get this root hub's number of downstream ports ndp = ioread32( mmio + 0x48 ) & 0xFF; // HcRhDescriptorA len += sprintf( buf+len, "\n\n" ); len += sprintf( buf+len, " USB OHCI Controller #%d: ", i+1 ); len += sprintf( buf+len, " VendorID=%04X ", mfg_ID[i] ); len += sprintf( buf+len, " DeviceID=%04X ", dev_ID[i] ); len += sprintf( buf+len, " iobase=0x%08X ", iobase[ i ] ); for (j = 0; j < 0x54 + ndp*4; j+=4) { if ( (j % 32) == 0 ) len += sprintf( buf+len, "\n 0x%02X: ", j ); len += sprintf( buf+len, "%08X ", ioread32( mmio+j ) ); } len += sprintf( buf+len, "\n" ); iounmap( mmio ); } len += sprintf( buf+len, "\n" ); return len; } static int __init ohci_init( void ) { struct pci_dev *devp = NULL; int i; printk( "<1>\nInstalling \'%s\' module ", modname ); printk( "(major=%d) \n", my_major ); // detect the presence of OHCI controllers while ( n_hubs < MAX_HOSTS ) { devp = pci_get_class( CLASS_OHCI, devp ); if ( !devp ) break; hubp[ n_hubs ] = devp; ++n_hubs; } if ( !n_hubs ) return -ENODEV; printk( " Number of OHCI controllers found = %u \n", n_hubs ); // make sure each controller's Bus Master capability is ebabled for (i = 0; i < n_hubs; i++) { u16 pci_cmd; pci_read_config_word( hubp[ i ], 0x04, &pci_cmd ); pci_cmd |= (1 << 2); pci_write_config_word( hubp[ i ], 0x04, pci_cmd ); } // store each controller's PCI Configuration Space parameters for (i = 0; i < n_hubs; i++) { u16 deviceID, vendorID; pci_read_config_word( hubp[ i ], 0, &vendorID ); pci_read_config_word( hubp[ i ], 2, &deviceID ); iobase[ i ] = pci_resource_start( hubp[ i ], 0 ); mfg_ID[ i ] = vendorID; dev_ID[ i ] = deviceID; devfn[ i ] = hubp[ i ]->devfn; } // output these parameter-lists to the kernel's log-file for (i = 0; i < n_hubs; i++) { printk( " #%d: ", i+1 ); printk( " (device=%d, ", (devfn[ i ] >> 3)&0x1F ); printk( "function=%d) ", (devfn[ i ] >> 0)&0x07 ); printk( " vendorID=%04X ", mfg_ID[ i ] ); printk( " deviceID=%04X ", dev_ID[ i ] ); printk( " iobase=0x%08X \n", iobase[ i ] ); } // allocate the kernel memory that applications can map kmem = kmalloc( KMEM_SIZE, GFP_KERNEL | GFP_DMA ); if ( !kmem ) return -ENOMEM; memset( kmem, 0x55, KMEM_SIZE ); kmem_base = virt_to_phys( kmem ); kmem_size = KMEM_SIZE; // output kernel-memory's physical address (this will aid debugging) printk( " kernel memory region allocated at physical address-range " ); printk( "0x%08lX-0x%08lX \n", kmem_base, kmem_base + kmem_size ); // create the pseudo-file and register the character-mode driver create_proc_read_entry( modname, 0, NULL, my_proc_read, NULL ); return register_chrdev( my_major, devname, &my_fops ); } static void __exit ohci_exit(void ) { remove_proc_entry( modname, NULL ); unregister_chrdev( my_major, devname ); kfree( kmem ); printk( "<1>Removing \'%s\' module\n", modname ); } module_init( ohci_init ); module_exit( ohci_exit ); MODULE_LICENSE("GPL");