//------------------------------------------------------------------- // rxplustx.c (An initial character-driver prototype) // // This is an interrupt-driven character device-driver for the // RealTek 8139 network interface. It is intended to allow us // to experiment with using the 'Address Resolution Protocol'. // // NOTE: Written and tested using Linux kernel version 2.6.10. // // programmer: ALLAN CRUSE // date begun: 13 APR 2005 // completion: 20 APR 2005 //------------------------------------------------------------------- #include // for init_module() #include // for init_waitqueue_head() #include // for pci_find_device() #include // for request_irq(), free_irq() #include // for copy_to_user() #define VENDOR_ID 0x10EC // ReakTek Semiconductors Corp #define DEVICE_ID 0x8139 // RTL-8139 Network Controller #define RBUFSZ (8<<10) // Length of Read Buffer (8KB) #define WBUFSZ (8<<10) // Length of Write Buffer (8KB) #define KBUFSZ (WBUFSZ+RBUFSZ) // Total Size of Kernel Memory #define RXCONFIG 0xE70F // Receive Configuration value #define TXCONFIG 0x0000 // Transmit configuration bits #define GET_HWADDR 0 // IOCTL commad-identification char modname[] = "rxplustx"; // for module identification char devname[] = "nic"; // for driver registration int my_major = 98; // static device major number int iobase, irq; // assigned adapter resources unsigned char mac[6]; // hardware address of device void *kmem; // virtual address for buffer unsigned long kmem_base; // physical address of buffer unsigned long kmem_size; // length of allocated buffer wait_queue_head_t rx_wq; // queue for sleeping readers wait_queue_head_t tx_wq; // queue for sleeping writers unsigned int txindex; // transmit descriptor number int my_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg ) { void *buf = (void*)arg; switch ( cmd ) { case GET_HWADDR: if ( copy_to_user( buf, mac, 6 ) ) return -EFAULT; return 0; // SUCCESS; } return -EINVAL; } ssize_t my_write( struct file *file, const char *buf, size_t len, loff_t *pos ) { unsigned int offset, nbytes, ioport; char *where = (char*)kmem; // sleep until the next Transmit Buffer is available wait_event_interruptible( tx_wq, inw( iobase + 0x60 )&(1< 1500 ) ? 1500 : len; if ( copy_from_user( where, buf, nbytes ) ) return -EFAULT; // transmit the packet and update the 'txindex' variable outl( nbytes, ioport ); txindex = ( 1 + txindex )%4; // advance the file position and report count of bytes copied *pos += nbytes; return nbytes; } ssize_t my_read( struct file *file, char *buf, size_t len, loff_t *pos ) { unsigned int offset, nbytes, header, status, length, nextrx; char *where = (char*)kmem + WBUFSZ; // sleep until the Receive Buffer is no longer empty wait_event_interruptible( rx_wq, (inb( iobase + 0x37 )&1) == 0 ); // prepare to copy received packet, and to update CAPR offset = ( inw( iobase + 0x38 ) + 16 ) % RBUFSZ; where += offset; header = *(unsigned int*)where; status = header & 0xFFFF; length = header >> 16; nextrx = offset + 4 + ((length + 3 )&~3); nextrx -= 16; nextrx %= RBUFSZ; where += 4; // skip packet's header nbytes = length - 4; // omit the 4-bytes CRC if ( nbytes > len ) nbytes = len; // don't exceed request if ( copy_to_user( buf, where, nbytes ) ) return -EFAULT; // update the Receive Buffer's 'guard' register outw( nextrx, iobase + 0x38 ); // update CAPR register // advance the file position and report count of bytes copied *pos += nbytes; return nbytes; } int my_open( struct inode *inode, struct file *file ) { txindex = 0; outb( 0x0C, iobase + 0x37 ); // enable xmit and recv outl( TXCONFIG, iobase + 0x40 ); // setup Tx Configuration outl( RXCONFIG, iobase + 0x44 ); // setup Rx Configuration outw( RBUFSZ-16, iobase + 0x38 ); // setup CAPR for RBUFSZ outw( 0xFFFF, iobase + 0x3E ); // acknowledge interrupts outw( 0x007F, iobase + 0x3C ); // unmask interrupts return 0; // SUCCESS } int my_release( struct inode *inode, struct file *file ) { outb( 0x00, iobase + 0x37 ); // disable xmit and recv outw( 0x0000, iobase + 0x3C ); // disable interrupts return 0; // SUCCESS } irqreturn_t my_isr( int irq, void *dev_id, struct pt_regs *regs ) { int intstatus = inw( iobase + 0x3E ); if ( intstatus == 0 ) return IRQ_NONE; // it wasn't for us if ( intstatus & 0x03 ) // packet was received wake_up_interruptible( &rx_wq ); // wake up any readers if ( intstatus & 0x0C ) // packet was transmitted wake_up_interruptible( &tx_wq ); // wake up any writers if ( intstatus & 0xFFF0 ) { printk( "unexpected RTL-8139 interrupt! " ); printk( " (intstatus=%04X) \n", intstatus ); } outw( intstatus, iobase + 0x3E ); // clear the interrupts return IRQ_HANDLED; // resume current task } struct file_operations my_fops = { owner: THIS_MODULE, ioctl: my_ioctl, write: my_write, read: my_read, open: my_open, release: my_release, }; MODULE_LICENSE("GPL"); int init_module( void ) { struct pci_dev *devp = NULL; int i; printk( "<1>\nInstalling \'%s\' module ", modname ); printk( "(major=%d) \n", my_major ); devp = pci_find_device( VENDOR_ID, DEVICE_ID, devp ); if ( !devp ) return -ENODEV; irq = devp->irq; iobase = pci_resource_start( devp, 0 ); printk( "RealTek 8139 Network Interface: " ); for (i = 0; i < 6; i++) { char ch = ( i == 5 ) ? ' ' : ':'; mac[i] = inb( iobase + i ); printk( "%02X%c", mac[i], ch ); } printk( " iobase=%04X irq=%d \n", iobase, irq ); outb( 0x10, iobase + 0x37 ); // reset the network controller while ( inb( iobase + 0x37 ) & 0x10 ); init_waitqueue_head( &rx_wq ); init_waitqueue_head( &tx_wq ); if ( request_irq( irq, my_isr, SA_SHIRQ, devname, &devname ) ) return -EBUSY; kmem = kmalloc( KBUFSZ, GFP_KERNEL ); if ( !kmem ) return -ENOMEM; kmem_base = __pa( kmem ); kmem_size = KBUFSZ; printk( "Physical address-range of kernel buffer: " ); printk( "%08lX-%08lX \n", kmem_base, kmem_base+kmem_size ); outl( kmem_base+0x0000, iobase + 0x20 ); // setup TxBuffer0 outl( kmem_base+0x0800, iobase + 0x24 ); // setup TxBuffer1 outl( kmem_base+0x1000, iobase + 0x28 ); // setup TxBuffer2 outl( kmem_base+0x1800, iobase + 0x2C ); // setup TxBuffer3 outl( kmem_base+0x2000, iobase + 0x30 ); // setup RBStart return register_chrdev( my_major, devname, &my_fops ); } void cleanup_module( void ) { outw( 0x0000, iobase + 0x3C ); // disable device's interrupts outw( 0xFFFF, iobase + 0x0E ); // and ackowledge pending ones outb( 0x10, iobase + 0x37 ); // reset the network controller free_irq( irq, &devname ); // remove the interrupt handler unregister_chrdev( my_major, devname ); kfree( kmem ); printk( "<1>Removing \'%s\' module\n", modname ); }