//------------------------------------------------------------------- // uhci.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 // the x86 platform's USB Universal Host Controller Interfaces. // Specifically, it lets an application memory-map a contiguous // page-aligned region of kernel memory into its userspace, and // it adjust the program's I/O-Privilege Level so that the UHCI // registers can be accessed using 'in' and 'out' instructions. // // NOTE: Written and tested with Linux kernel version 2.6.31.5. // // programmer: ALLAN CRUSE // date begun: 20 MAR 2010 // completion: 24 MAR 2010 // revised on: 07 OCT 2011 -- to insure 'Bus Master' capability //------------------------------------------------------------------- #include // for init_module() #include // for create_proc_read_entry() #include // for pci_get_class() #include // for thread_struct #include // for copy_to_user() #define CLASS_UHCI 0x0C0300 // USB UHCI Host Controller Hub #define MAX_HOSTS 8 // Maximum number of UHCI Hosts #define KMEM_SIZE 81920 // Size of kernel memory region char modname[] = "uhci"; char devname[] = "uhci"; int my_major = 74; struct pci_dev *hubp[ MAX_HOSTS ]; unsigned short devfn[ MAX_HOSTS ]; unsigned short iobase[ MAX_HOSTS ]; unsigned short dev_ID[ MAX_HOSTS ]; unsigned long kmem_base, kmem_size; unsigned int n_hubs = 0; void *kmem; 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 = kmem_size + where; break; // SEEK_END } if (( newpos < 0 )||( newpos > kmem_size )) return -EINVAL; file->f_pos = newpos; return newpos; } int my_mmap( struct file *file, struct vm_area_struct *vma ) { unsigned long region_length = vma->vm_end - vma->vm_start; unsigned long region_origin = vma->vm_pgoff * PAGE_SIZE; unsigned long physical_addr = kmem_base + region_origin; unsigned long virtual_start = vma->vm_start; // sanity check: mapped region should not exceed kmem-size if ( region_length > kmem_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_start, physical_addr>>PAGE_SHIFT, region_length, vma->vm_page_prot ) ) return -EAGAIN; return 0; // SUCCESS } int my_open( struct inode *inode, struct file *file ) { // As a 'side-effect' of opening the device-file, the // task's I/O Privilege-Level will be set equal to 3. // This code below closely follows ideas from Linux's // 'sys_iopl()' implementation, but without checks on // CAP_SYS_RAWIO and without options for IOPL values unsigned long structsize = sizeof( struct pt_regs ); struct thread_struct *ts = ¤t->thread; unsigned long regsp = ts->sp0 - structsize; struct pt_regs *regs = (struct pt_regs *)regsp; // For the i386 architecture (or in "compatibility mode" // of the x86_64 architecture) the user's FLAGS-register // is saved and restored during system-calls from fields // in its kernel data-structures (which we modify here) regs->flags |= (3 << 12); ts->iopl = (3 << 12); set_iopl_mask( ts->iopl ); #ifndef i386 // In the x86_64 architecture, the 'syscall' instruction // saves the user's RFLAGS values in register R11. Then // the Linux kernel pushes %r11 onto its stack and later // restores R11 from its stack before executing 'sysret' regs->r11 |= (3 << 12); // sets IOPL-image to 3 #endif return 0; // SUCCESS } int my_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long addr ) { unsigned long kmem_phys, hub_count, hub_index, port_addr; void *src, *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 kmem_phys = virt_to_phys( kmem ); src = &kmem_phys; if ( copy_to_user( dst, src, len ) ) return -EFAULT; return 0; case 1: // when an application needs to know the number // of UHCI controllers detected, or the io-port // base-address of a particular UHCI controller src = (void*)addr; hub_count = n_hubs; if ( copy_from_user( &hub_index, src, len ) ) return -EFAULT; if ( hub_index > hub_count ) return -EINVAL; if ( hub_index == 0 ) { // return a count of the UHCI controllers src = &hub_count; if ( copy_to_user( dst, src, len ) ) return -EFAULT; return 0; } else { // return the i/o-port base for a controller hub_index -= 1; port_addr = (unsigned long)iobase[ hub_index ]; src = &port_addr; if ( copy_to_user( dst, src, len ) ) return -EFAULT; return 0; } } return -EINVAL; } struct file_operations my_fops = { owner: THIS_MODULE, llseek: my_llseek, ioctl: my_ioctl, mmap: my_mmap, open: my_open, }; // the static message-header to be displayed in our '/proc/uhci' pseudo-file char legend[] = "HUB USBCMD USBSTS USBINTR FRNUM FRBASEADDR SOFMOD PORTSC0 PORTSC1"; int my_proc_read( char *buf, char **start, off_t off, int count, int *eof, void *data ) { int i, io, len = 0; len += sprintf( buf+len, "\n %s\n", legend ); for (i = 0; i < n_hubs; i++) { io = iobase[ i ]; len += sprintf( buf+len, " #%d: ", i+1 ); len += sprintf( buf+len, " %04X ", inw( io + 0x00 ) ); len += sprintf( buf+len, " %04X ", inw( io + 0x02 ) ); len += sprintf( buf+len, " %03X ", inw( io + 0x04 ) ); len += sprintf( buf+len, " %03X ", inw( io + 0x06 ) ); len += sprintf( buf+len, " %08X ", inl( io + 0x08 ) ); len += sprintf( buf+len, " %02X ", inb( io + 0x0C ) ); len += sprintf( buf+len, " %03X ", inw( io + 0x10 ) ); len += sprintf( buf+len, " %03X ", inw( io + 0x12 ) ); len += sprintf( buf+len, "\n" ); } len += sprintf( buf+len, "\n" ); return len; } static int __init uhci_init( void ) { struct pci_dev *devp = NULL; int i, j; printk( "<1>\nInstalling \'%s\' module ", modname ); printk( "(major=%d) \n", my_major ); // detect the presence of UHCI controllers while ( n_hubs < MAX_HOSTS ) { devp = pci_get_class( CLASS_UHCI, devp ); if ( !devp ) break; hubp[ n_hubs ] = devp; ++n_hubs; } if ( !n_hubs ) return -ENODEV; printk( " Number of UHCI controllers found = %u \n", n_hubs ); // store each controller's PCI Configuration Space parameters for (i = 0; i < n_hubs; i++) { u16 deviceID; pci_read_config_word( hubp[ i ], 2, &deviceID ); iobase[ i ] = pci_resource_start( hubp[ i ], 4 ); dev_ID[ i ] = deviceID; devfn[ i ] = hubp[ i ]->devfn; } // make sure each controller's Bus Master capability is enabled 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 ); } // sort these cotrollers' parameter-lists in order of their // PCI DeviceIDs (for conformity with Linux's numbering) i = 1; j = 0; while ( i < n_hubs ) if ( dev_ID[ i ] < dev_ID[ j ] ) { unsigned short tempi, tempj; tempi = dev_ID[ i ]; tempj = dev_ID[ j ]; dev_ID[ j ] = tempi; dev_ID[ i ] = tempj; tempi = iobase[ i ]; tempj = iobase[ j ]; iobase[ j ] = tempi; iobase[ i ] = tempj; tempi = devfn[ i ]; tempj = devfn[ j ]; devfn[ j ] = tempi; devfn[ i ] = tempj; i = 1; j = 0; } else { ++i; ++j; } // 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( "deviceID=%04X ", dev_ID[ i ] ); printk( " iobase=0x%04X \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 uhci_exit(void ) { unregister_chrdev( my_major, devname ); remove_proc_entry( modname, NULL ); kfree( kmem ); printk( "<1>Removing \'%s\' module\n", modname ); } module_init( uhci_init ); module_exit( uhci_exit ); MODULE_LICENSE("GPL");