//------------------------------------------------------------------- // user8139.c (An enhanced version of 'mmap8139.c') // // This module implements a character device-driver that lets // an application map the RTL-8139 registers into user-space. // // In addition, it allocates a block of physically contiguous // kernel memory that the network controller may then use for // its transmit and receive buffers. To facilitate operation // of the network controller from user-space, via the memory- // mapped register-interface, this module preprograms each of // the five buffer-address registers with suitable values for // transmission and reception of normal-size ethernet frames, // and thereafter those address-registers must not be changed // from user-space, to avoid possibly corrupting kernel data. // // This driver's 'read()', 'write()', and 'lseek()' functions // provide user-access to the allocated kernel buffer memory. // // The total length of the kernel buffer allocation is 80-KB, // with address-registers programmed sequentially as follows: // // TxBuffer0 = base-address + 0x0000 (size 4kb) // TxBuffer1 = base-address + 0x1000 (size 4kb) // TxBuffer0 = base-address + 0x2000 (size 4kb) // TxBuffer0 = base-address + 0x3000 (size 4kb) // RxAddress = base-address + 0x4000 (size 64kb) // // // NOTE: Written and tested with Linux kernel version 2.6.10. // // programmer: ALLAN CRUSE // date begun: 04 APR 2005 // completion: 06 APR 2005 //------------------------------------------------------------------- #include // for init_module() #include // for pci_find_device() #include // for copy_to/from_user() #define VENDOR_ID 0x10EC // RealTek Semiconductor Corp #define DEVICE_ID 0x8139 // RTL-8139 Network Processor #define KBUF_SIZE (80 << 10) // Contiguous memory (80-KB) char modname[] = "user8139"; // for displaying module name char devname[] = "nic"; // for registering the driver int my_major = 98; // static major-ID assignment struct pci_dev *devp; // Nic's pci device-structure unsigned long mmio_base; // registers physical address unsigned long mmio_size; // size of the registers bank void *kmem; // virtual address for buffer unsigned long kmem_base; // physical address of buffer unsigned long kmem_size; // contiguous buffer's length enum rtl8139_offsets { TxBuffer0 = 0x20, TxBuffer1 = 0x24, TxBuffer2 = 0x28, TxBuffer3 = 0x2C, RxAddress = 0x30 }; int my_mmap( struct file *file, struct vm_area_struct *vma ) { unsigned long region_origin = vma->vm_pgoff * PAGE_SIZE; unsigned long region_length = vma->vm_end - vma->vm_start; unsigned long physical_addr = mmio_base + region_origin; unsigned long user_virtaddr = vma->vm_start; // sanity check: mapped region is confined to just one page if ( region_length > PAGE_SIZE ) return -EINVAL; // tell the kernel not to try swapping out this region vma->vm_flags |= VM_RESERVED; // tell the kernel that this region maps io memory vma->vm_flags |= VM_IO; // ask the kernel to set up the required page-tables if ( io_remap_page_range( vma, user_virtaddr, physical_addr, region_length, vma->vm_page_prot ) ) return -EAGAIN; // preprogram the network controller's address-registers if ( file->f_mode & FMODE_WRITE ) { int iobase = pci_resource_start( devp, 0 ); outl( kmem_base + 0x0000, iobase + TxBuffer0 ); outl( kmem_base + 0x1000, iobase + TxBuffer1 ); outl( kmem_base + 0x2000, iobase + TxBuffer2 ); outl( kmem_base + 0x3000, iobase + TxBuffer3 ); outl( kmem_base + 0x4000, iobase + RxAddress ); } return 0; // SUCCESS } loff_t my_llseek( struct file *file, loff_t offset, int whence ) { loff_t newpos = -1; switch ( whence ) { case 0: newpos = offset; break; // SEEK_SET case 1: newpos = file->f_pos + offset; break; // SEEK_CUR case 2: newpos = kmem_size + offset; break; // SEEK_END } if (( newpos < 0 )||( newpos > kmem_size )) return -EINVAL; file->f_pos = newpos; return newpos; } ssize_t my_write( struct file *file, const char *buf, size_t len, loff_t *pos ) { // transfers data from user-space into the kernel-buffer void *dst = (void*)( (int)kmem + (int)*pos ); // we cannot write past the end of the kernel-buffer if ( *pos >= kmem_size ) return 0; // adjust the number of bytes to transfer if necessary if ( *pos + len > kmem_size ) len = kmem_size - *pos; // perform the copying if ( copy_from_user( dst, buf, len ) ) return -EFAULT; // adjust file-pointer and return count of bytes copied *pos += len; return len; } ssize_t my_read( struct file *file, char *buf, size_t len, loff_t *pos ) { // transfers data to user-space from the kernel-buffer void *src = (void*)( (int)kmem + (int)*pos ); // we cannot read past the end of the kernel-buffer if ( *pos >= kmem_size ) return 0; // adjust the number of bytes to transfer if necessary if ( *pos + len > kmem_size ) len = kmem_size - *pos; // perform the copying if ( copy_to_user( buf, src, len ) ) return -EFAULT; // adjust file-pointer and return count of bytes copied *pos += len; return len; } struct file_operations my_fops = { owner: THIS_MODULE, llseek: my_llseek, write: my_write, read: my_read, mmap: my_mmap, }; void cleanup_module( void ) { kfree( kmem ); unregister_chrdev( my_major, devname ); printk( "<1>Removing \'%s\' module\n", modname ); } int init_module( void ) { printk( "<1>\nInstalling \'%s\' module\n", modname ); printk( " device-file is \'/dev/%s\' ", devname ); printk( "(major=%d) \n", my_major ); devp = pci_find_device( VENDOR_ID, DEVICE_ID, devp ); if ( !devp ) return -ENODEV; mmio_base = pci_resource_start( devp, 1 ); mmio_size = pci_resource_len( devp, 1 ); printk( " RealTek 8139 Network Interface:" ); printk( " %08lX-%08lX \n", mmio_base, mmio_base+mmio_size ); kmem_size = KBUF_SIZE; kmem = kmalloc( kmem_size, GFP_KERNEL ); if ( !kmem ) return -ENOMEM; kmem_base = __pa( kmem ); memset( kmem, 0xFF, kmem_size ); printk( " Contiguous kernel storage area:" ); printk( " %08lX-%08lX \n", kmem_base, kmem_base+kmem_size ); register_chrdev( my_major, devname, &my_fops ); return 0; // SUCCESS } MODULE_LICENSE("GPL");