//------------------------------------------------------------------- // ehci.c (Revises our earlier 'ajay.c' module) // // This is a character-mode device-driver supporting user-mode // programming experiments with the USB 2.0 Enhanced Universal // Serial Bus Host Controller, modifying our previous 'ajay.c' // device-driver by adding access to a contiguous page-aligned // non-pageable region of kernel memory for setting up various // data-structures required by the EHCI controller operations. // // User-space layout of the 'mapped' memory-region // +-----------+-------------------------------------------+ // | EHCI | physically continuous | // | registers | kernel memory | // | (1-page) | (20-pages) | // +-----------+-------------------------------------------+ // // Specifically, this module implements three driver methods: // 'lseek()': reports total length of the mapped memory region // 'ioctl()': reports a physical address for the buffer memory // 'mmap()': makes EHCI registers and kernel buffer accessible // // This module does not itself modify any EHCI register-values // or PCI configuration-space registers, though it does report // their values via two pseudo-files in the '/proc' directory. // // NOTE: Written and tested with Linux kernel version 2.6.31.5. // // programmer: ALLAN CRUSE // written on: 23 JAN 2010 // revised on: 14 MAR 2010 // revised on: 03 AUG 2010 -- to add an 'open()' driver-method // revised on: 29 DEC 2010 -- allows physical memory above 4GB //------------------------------------------------------------------- #include // for init_module() #include // for create_proc_read_entry() #include // for pci_get_class() #include // for copy_to_user() #define CLASS_EHCI 0x0C0320 // USB EHCI Host Controller Hub #define PCI_DEV_FN 0xEF // Device-number 29, Function 7 #define KMEM_SIZE 81920 // Size of kernel memory region char modname[] = "ehci"; char devname[] = "ehci"; char pciname[] = "pci"; int my_major = 75; struct pci_dev *devp = NULL; unsigned long mmio_base; unsigned long mmio_size; void *mmio; unsigned long kmem_base; unsigned long kmem_size; void *kmem; unsigned long mmap_base; unsigned long mmap_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 = mmap_size + where; break; // SEEK_END } if (( newpos < 0 )||( newpos > mmap_size )) return -EINVAL; file->f_pos = newpos; return newpos; } int my_mmap( struct file *file, struct vm_area_struct *vma ) { unsigned long request_length = vma->vm_end - vma->vm_start; unsigned long region1_origin = vma->vm_pgoff * PAGE_SIZE; unsigned long region1_length = PAGE_SIZE; unsigned long physical_addr1 = mmap_base + region1_origin; unsigned long region2_length = request_length - region1_length; unsigned long physical_addr2 = kmem_base; unsigned long virtual_start1 = vma->vm_start; unsigned long virtual_start2 = vma->vm_start + PAGE_SIZE; // sanity check: mapped region should not exceed mmap-size if ( request_length < PAGE_SIZE ) return -EINVAL; if ( request_length > mmap_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 } int my_open( struct inode *inode, struct file *file ) { // Make sure the controller's Bus Master capability is enabled u16 pci_cmd; pci_read_config_word( devp, 0x04, &pci_cmd ); pci_cmd |= (1 << 2); pci_write_config_word( devp, 0x04, pci_cmd ); return 0; // SUCCESS } int my_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long addr ) { unsigned long kmem_phys = virt_to_phys( kmem ); 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; } struct file_operations my_fops = { owner: THIS_MODULE, llseek: my_llseek, ioctl: my_ioctl, mmap: my_mmap, open: my_open, }; char legend[] = "-- USB Enhanced Host Controller registers --"; int my_proc_read( char *buf, char **start, off_t off, int count, int *eof, void *data ) { int n_ports = ioread32( mmio + 0x04 )&0xF; int i, len = 0; len += sprintf( buf+len, "\n\n %15s%s \n", " ", legend ); len += sprintf( buf+len, "\n " ); len += sprintf( buf+len, " CAPLENGTH=%02X ", ioread16( mmio+0x00 ) ); len += sprintf( buf+len, " HCIVERSION=%04X ", ioread16( mmio+0x02 ) ); len += sprintf( buf+len, " HCSPARAMS=%08X ", ioread32( mmio+0x04 ) ); len += sprintf( buf+len, " HCCPARAMS=%08X ", ioread32( mmio+0x08 ) ); len += sprintf( buf+len, "\n " ); len += sprintf( buf+len, " CTRLDSSEGM=%08X ", ioread32( mmio+0x30 ) ); len += sprintf( buf+len, "PERIODICLISTBASE=%08X ",ioread32(mmio+0x34)); len += sprintf( buf+len, "ASYNCLISTBASE=%08X ", ioread32(mmio+0x38)); len += sprintf( buf+len, "\n " ); len += sprintf( buf+len, " USB2CMD=%08X ", ioread32( mmio + 0x20 ) ); len += sprintf( buf+len, " USB2STS=%08X ", ioread32( mmio + 0x24 ) ); len += sprintf( buf+len, " USB2INTR=%08X ", ioread32( mmio + 0x28 ) ); len += sprintf( buf+len, " FRINDEX=0x%04X ", ioread32( mmio + 0x2C ) ); len += sprintf( buf+len, "\n" ); len += sprintf( buf+len, "\n" ); len += sprintf( buf+len, " CONFIGFLAG=%X ", ioread32( mmio + 0x60 ) ); for (i = 0; i < n_ports; i++) { unsigned int datum = ioread32( mmio + 0x64 + i*4 ); len += sprintf( buf+len, "\n PORT%XSC=%08X ", i, datum ); len += sprintf( buf+len, " owner=%X ", (datum >> 13)&1 ); len += sprintf( buf+len, " power=%X ", (datum >> 12)&1 ); len += sprintf( buf+len, " suspend=%X ", (datum >> 7)&1 ); len += sprintf( buf+len, " enabled=%X ", (datum >> 2)&1 ); len += sprintf( buf+len, " connected=%X ", (datum >> 0)&1 ); } len += sprintf( buf+len, "\n" ); len += sprintf( buf+len, "\n DEBUG PORT REGISTERS \n" ); len += sprintf( buf+len, " CNTL_STS=%08X ", ioread32( mmio + 0xA0 ) ); len += sprintf( buf+len, " USBPID=%06X ", ioread32( mmio + 0xA4 ) ); len += sprintf( buf+len, " DATABUF=" ); len += sprintf( buf+len, "%08X-", ioread32( mmio + 0xA8 ) ); len += sprintf( buf+len, "%08X ", ioread32( mmio + 0xAC ) ); len += sprintf( buf+len, " CONFIG=%04X ", ioread32( mmio + 0xB0 ) ); len += sprintf( buf+len, "\n\n\n" ); return len; } char heading[] = "PCI Configuration Space for EHCI Host Controller"; int my_pci_info( char *buf, char **start, off_t off, int count, int *eof, void *data ) { u32 datum; int i, dev, fnc, len = 0; dev = ( devp->devfn >> 3)&0x1F; fnc = ( devp->devfn >> 0)&0x07; len += sprintf( buf+len, "\n\n %s ", heading ); len += sprintf( buf+len, "(device=%d,function=%d) \n", dev, fnc ); for (i = 0; i < 0x100; i+=4) { pci_read_config_dword( devp, i, &datum ); if ( (i%32) == 0 ) len += sprintf( buf+len, "\n 0x%02X:", i ); len += sprintf( buf+len, " %08X", datum ); } len += sprintf( buf+len, "\n\n" ); pci_read_config_dword( devp, 0x00, &datum ); len += sprintf( buf+len, " VENDOR=%04X", (datum >> 0) & 0xFFFF ); len += sprintf( buf+len, " DEVICE=%04X", (datum >> 16) & 0xFFFF ); pci_read_config_dword( devp, 0x04, &datum ); len += sprintf( buf+len, " CMD=%04X", (datum >> 0)&0xFFFF ); len += sprintf( buf+len, " STS=%04X", (datum >> 16)&0xFFFF ); pci_read_config_dword( devp, 0x08, &datum ); len += sprintf( buf+len, " CLASS_CODE=%06X", (datum >> 8) ); pci_read_config_dword( devp, 0x10, &datum ); len += sprintf( buf+len, " MEM_BASE=%08X", datum ); len += sprintf( buf+len, "\n" ); pci_read_config_dword( devp, 0x3C, &datum ); len += sprintf( buf+len, " INT_LN=0x%02X ", (datum >> 0)&0xFF ); pci_read_config_dword( devp, 0x68, &datum ); len += sprintf( buf+len, " LEG_EXT_CAP=%08X ", datum ); pci_read_config_dword( devp, 0x6C, &datum ); len += sprintf( buf+len, " LEG_EXT_CS=%08X ", datum ); pci_read_config_dword( devp, 0x70, &datum ); len += sprintf( buf+len, " SPECIAL_SMI=%08X ", datum ); len += sprintf( buf+len, "\n\n\n" ); return len; } static int __init ehci_init( void ) { printk( "<1>\nInstalling \'%s\' module ", modname ); printk( "supporting device-node \'/dev/%s\' ", devname ); printk( "(major=%d) \n", my_major ); do { devp = pci_get_class( CLASS_EHCI, devp ); if ( !devp ) return -ENODEV; } while ( devp->devfn != PCI_DEV_FN ); mmio_base = pci_resource_start( devp, 0 ); mmio_size = PAGE_SIZE; mmio = ioremap_nocache( mmio_base, mmio_size ); if ( !mmio ) return -ENOSPC; printk( " EHCI Controller (dev=29,fnc=7):" ); printk( " mmio_base=0x%08lX", mmio_base ); printk( " mmio_size=0x%04lX", mmio_size ); printk( " irq=0x%02X ", devp->irq ); printk( "\n" ); 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; printk( " kernel memory region allocated at physical address-range " ); printk( "0x%08lX-0x%08lX \n", kmem_base, kmem_base + kmem_size ); mmap_base = mmio_base; mmap_size = mmio_size + kmem_size; create_proc_read_entry( devname, 0, NULL, my_proc_read, NULL ); create_proc_read_entry( pciname, 0, NULL, my_pci_info, NULL ); return register_chrdev( my_major, modname, &my_fops ); } static void __exit ehci_exit(void ) { unregister_chrdev( my_major, modname ); remove_proc_entry( pciname, NULL ); remove_proc_entry( devname, NULL ); kfree( kmem ); iounmap( mmio ); printk( "<1>Removing \'%s\' module\n", modname ); } module_init( ehci_init ); module_exit( ehci_exit ); MODULE_LICENSE("GPL");