//------------------------------------------------------------------- // usb3.c // // This character-mode Linux device-driver offers support for // user-mode programming experiments with our Buffalo USB 3.0 // Host Controller adapter-card. // // 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 XHCI registers and kernel buffer accessible // 'open()': makes sure the Bus Master capability gets enabled // // This module does not itself modify any XHCI register-values // or PCI configuration-space registers, though it does report // their values via two pseudo-files in the '/proc' directory. // // Exception: the BME-bit in PCI Command Register is set to 1. // // NOTE: Written and tested with Linux kernel version 2.6.33.3. // // programmer: ALLAN CRUSE // written on: 14 SEP 2010 // revised on: 25 SEP 2010 -- to improve xhci register display // revised on: 30 SEP 2010 -- to show port-numbering as 1-based // revised on: 05 OCT 2010 -- to add the 'open()' driver-method //------------------------------------------------------------------- #include // for init_module() #include // for pci_get_class() #include // for create_proc_read_entry() #include // for copy_to_user() #define CLASS_XHCI 0x0C0330 // USB XHCI Host Controller Hub #define KMEM_SIZE 81920 // Size of kernel memory region char modname[] = "usb3"; char devname[] = "xhci"; char pciname[] = "pci"; int my_major = 76; struct pci_dev *devp = NULL; void *mmio, *kmem; unsigned int mmio_base, mmio_size; unsigned int kmem_base, kmem_size; unsigned int mmap_base, mmap_size; 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 } 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 = mmio_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 + mmio_size; // sanity check: mapped region should not exceed mmap-size if ( request_length < mmio_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_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, }; int my_dev_info( char *buf, char **start, off_t off, int count, int *eof, void *data ) { int hcsparams1 = ioread32( mmio + 0x04 ); int n_ports = (hcsparams1 >> 24)&0x0FF; int n_intrs = (hcsparams1 >> 8)&0x3FF; int caplen = ioread32( mmio + 0x00 )&0xFF; int rtsoff = ioread32( mmio + 0x18 ); void *mmop = mmio + caplen; void *mmrt = mmio + rtsoff; int i, len = 0; len += sprintf( buf+len, "\n capability registers: \n" ); len += sprintf( buf+len, " CAPLENGTH=%02X ", ioread8( mmio + 0x00 ) ); len += sprintf( buf+len, " HCSPARAMS1=%08X ", ioread32( mmio + 0x04 ) ); len += sprintf( buf+len, " HCSPARAMS2=%08X ", ioread32( mmio + 0x08 ) ); len += sprintf( buf+len, " HCSPARAMS3=%08X ", ioread32( mmio + 0x0C ) ); len += sprintf( buf+len, "\n" ); len += sprintf( buf+len, " HCCPARAMS=%08X ", ioread32( mmio + 0x10 ) ); len += sprintf( buf+len, " DBOFF=%08X ", ioread32( mmio + 0x14 ) ); len += sprintf( buf+len, " RTSOFF=%08X ", ioread32( mmio + 0x18 ) ); len += sprintf( buf+len, " HCIVERSION=%04X ", ioread16( mmio + 0x02 ) ); len += sprintf( buf+len, "\n\n operational registers: \n" ); len += sprintf( buf+len, " USBCMD=%08X ", ioread32( mmop + 0x00 ) ); len += sprintf( buf+len, " USBSTS=%08X ", ioread32( mmop + 0x04 ) ); len += sprintf( buf+len, " PAGESIZE=%04X ", ioread16( mmop + 0x08 ) ); len += sprintf( buf+len, " DNCTRL=%08X ", ioread32( mmop + 0x14 ) ); len += sprintf( buf+len, "\n" ); len += sprintf( buf+len, " CRCR=%08X%08X ", ioread32( mmop + 0x1C ), ioread32( mmop + 0x18 ) ); len += sprintf( buf+len, " DCBAAP=%08X%08X ", ioread32( mmop + 0x34 ), ioread32( mmop + 0x30 ) ); len += sprintf( buf+len, " CONFIG=0x%X ", ioread32( mmop + 0x38 ) ); len += sprintf( buf+len, "\n" ); for (i = 0; i < n_ports; i++) { unsigned int portsc = ioread32( mmop + 0x400 + i*16 ); len += sprintf( buf+len, " PORTSC%X=%08X ", i+1, portsc ); len += sprintf( buf+len, " speed=%X ", (portsc >> 10)&0xF ); len += sprintf( buf+len, " powered=%X ", (portsc >> 9)&0x1 ); len += sprintf( buf+len, " state=%X ", (portsc >> 5)&0xF ); len += sprintf( buf+len, " enabled=%X ", (portsc >> 1)&0x1 ); len += sprintf( buf+len, " connected=%X ", (portsc >> 0)&0x1 ); len += sprintf( buf+len, "\n" ); } len += sprintf( buf+len, "\n runtime registers: \n" ); len += sprintf( buf+len, " MFINDEX=%04X", ioread32( mmrt + 0x00 ) ); len += sprintf( buf+len, " IMOD " ); len += sprintf( buf+len, " IMAN " ); len += sprintf( buf+len, " ERSTSZ " ); len += sprintf( buf+len, " ERSTBA " ); len += sprintf( buf+len, " ERDP " ); len += sprintf( buf+len, "\n" ); for (i = 0; i < n_intrs; i++) { unsigned int *rt = mmrt + 32*(i + 1); len += sprintf( buf+len, " intr #%X ", i ); len += sprintf( buf+len, " %08X ", rt[0] ); len += sprintf( buf+len, " %08X ", rt[1] ); len += sprintf( buf+len, " %08X ", rt[2] ); len += sprintf( buf+len, " %08X%08X ", rt[5], rt[4] ); len += sprintf( buf+len, " %08X%08X ", rt[7], rt[6] ); len += sprintf( buf+len, "\n" ); } return len; } char heading[] = "PCI Configuration Space for XHCI 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, 0x60, &datum ); len += sprintf( buf+len, " SBRN=%02X ", (datum >> 0)&0xFF ); len += sprintf( buf+len, " FLADJ=%02X ", (datum >> 8)&0xFF ); len += sprintf( buf+len, "\n\n\n" ); return len; } static int __init my_init( void ) { printk( "<1>\nInstalling \'%s\' module ", modname ); printk( "supporting device-node \'/dev/%s\' ", devname ); printk( "(major=%d) \n", my_major ); devp = pci_get_class( CLASS_XHCI, devp ); if ( !devp ) return -ENODEV; mmio_base = pci_resource_start( devp, 0 ); mmio_size = pci_resource_len( devp, 0 ); mmio = ioremap_nocache( mmio_base, mmio_size ); if ( !mmio ) return -ENOSPC; printk( " XHCI Controller: " ); printk( " mmio_base=0x%08X", mmio_base ); printk( " mmio_size=0x%04X", 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%08X-0x%08X \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_dev_info, NULL ); create_proc_read_entry( pciname, 0, NULL, my_pci_info, NULL ); return register_chrdev( my_major, modname, &my_fops ); } static void __exit my_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( my_init ); module_exit( my_exit ); MODULE_LICENSE("GPL");