//------------------------------------------------------------------- // watch235.c // // This module creates a pseudo-driver for the RTL8139 network // adapter installed in our Harney-235 classroom workstations. // It allows an application program to intercept every network // packet received by the workstation by opening and reading a // binary pseudo-file (named '/proc/watch235'). The mechanism // employed here is designed to coexist with the system driver // by merely intercepting the interrupts that are generated by // the network adapter in order to log the memory-location for // each newly received packet. Only minimal information about // the RTL8139 device is needed for understanding this driver. // A companion application (called 'trywatch.cpp') is provided // for use in demonstrating this module's functionality. // // NOTE: Written and tested using Linux kernel version 2.4.26. // // programmer: ALLAN CRUSE // written on: 06 DEC 2004 //------------------------------------------------------------------- #include // for init_module() #include // for create_proc_read_entry() #include // for pci_find_device() #include // for copy_to_user() #include // for inw(), inl() #define PROC_DIR NULL #define PROC_MODE 0666 #define VENDOR_ID 0x10EC #define DEVICE_ID 0x8139 enum rtl8139_registers { RxAddress = 0x30, // Receive Buffer Start Address RxBufTail = 0x38, // Current Address of Packet Read IntStatus = 0x3E, // Interrupt Status Register }; static char modname[] = "watch235"; static unsigned short oldidtr[3], newidtr[3]; static unsigned long long *oldidt, *newidt; static unsigned long kpage, isr_orig, selector_cs; static struct pci_dev *devp = NULL; static int iobase, irq, intID; static unsigned char *rxbufstart; static unsigned short rxbufinfo[ 256 ]; static unsigned char head, tail; DECLARE_WAIT_QUEUE_HEAD( wq ); static void load_IDTR( void *regimage ) { asm(" lidt %0 " : : "m" (*(unsigned short *)regimage) ); } static void nic_intercept( unsigned long *tos ) { unsigned short intstatus, rxbuftail; // read the RTL8139 interrupt status register intstatus = inw( iobase+IntStatus ); if ( intstatus == 0x0001 ) // new packet received { // read current packet read address register rxbuftail = inw( iobase+RxBufTail ); // mask for offset into 32KB receive buffer rxbuftail &= 0x7FFF; // store to our received-packet log rxbufinfo[ tail++ ] = rxbuftail; // discard stale log information if ( tail == head ) ++head; // awaken any sleeping reader wake_up_interruptible( &wq ); } } //--------- RTL8139 INTERRUPT-HANDLER --------// asmlinkage void isr_nic_device( void ); asm(" .text "); asm(" .type isr_nic_device, @function "); asm("isr_nic_device: "); asm(" pushal "); asm(" pushl %ds "); asm(" pushl %es "); // asm(" movl %ss, %eax "); asm(" movl %eax, %ds "); asm(" movl %eax, %es "); // asm(" pushl %esp "); asm(" call nic_intercept "); asm(" addl $4, %esp "); // asm(" popl %es "); asm(" popl %ds "); asm(" popal "); asm(" jmp *isr_orig "); //----------------------------------------------// MODULE_LICENSE("GPL"); static ssize_t my_read( struct file *file, char *buf, size_t len, loff_t *pos ) { unsigned char *where; int nbytes; // sleep until a packet has been received while ( head == tail ) { interruptible_sleep_on( &wq ); if ( signal_pending( current ) ) return -ERESTARTSYS; } // copy received packet into the user's buffer where = rxbufstart + rxbufinfo[ head++ ]; nbytes = *(short*)(where+18); if ( copy_to_user( buf, where+20, nbytes ) ) return -EFAULT; return nbytes; } static int my_open( struct inode *inode, struct file *file ) { // reinitialize our global variables unsigned long rxaddress = inl( iobase+ RxAddress ); rxbufstart = (unsigned char*)phys_to_virt( rxaddress ); head = tail = 0; // now activate the new Interrupt Descriptor Table load_IDTR( newidtr ); smp_call_function( load_IDTR, newidtr, 1, 1 ); return 0; } static int my_close( struct inode *inode, struct file *file ) { // deactivate our altered Interrupt Descriptor Table smp_call_function( load_IDTR, oldidtr, 1, 1 ); load_IDTR( oldidtr ); return 0; } static struct file_operations my_fops = { owner: THIS_MODULE, read: my_read, open: my_open, release: my_close, }; int init_module( void ) { struct proc_dir_entry *entry; unsigned long long gate_desc; printk( "<1>\nInstalling \'%s\' module\n", modname ); devp = pci_find_device( VENDOR_ID, DEVICE_ID, devp ); if ( !devp ) return -ENODEV; iobase = pci_resource_start( devp, 0 ); irq = devp->irq; intID = irq + 0x20; printk( "%s\n", devp->name ); printk( " iobase=%04X irq=%d \n", iobase, irq ); // allocate a page of kernel memory to hold our new IDT kpage = get_free_page( GFP_KERNEL ); if ( !kpage ) return -ENOMEM; // copy the Interrupt Descriptor Table to this 'writable' page asm(" sidt oldidtr \n sidt newidtr \n movw %cs, selector_cs "); memcpy( newidtr+1, &kpage, sizeof( kpage ) ); oldidt = (unsigned long long*)(*(unsigned long*)(oldidtr+1)); newidt = (unsigned long long*)(*(unsigned long*)(newidtr+1)); memcpy( newidt, oldidt, 256 * sizeof( unsigned long long ) ); // initialize our exception-handler's indirect jump address gate_desc = oldidt[ intID ]; gate_desc &= 0xFFFF00000000FFFFLL; gate_desc |= ( gate_desc >> 32 ); isr_orig = (unsigned long)gate_desc; // modify our copy of the Interrupt Descriptor Table gate_desc = (unsigned long long)isr_nic_device; gate_desc &= 0x00000000FFFFFFFFLL; gate_desc |= ( gate_desc << 32 ); gate_desc &= 0xFFFF00000000FFFFLL; gate_desc |= 0x00008E0000000000LL; gate_desc |= ( selector_cs << 16 ); newidt[ intID ] = gate_desc; // create the pseudo-file that lets users see device information entry = create_proc_entry( modname, PROC_MODE, PROC_DIR ); entry->proc_fops = &my_fops; return 0; //SUCCESS } void cleanup_module( void ) { // delete our pseudo-file remove_proc_entry( modname, PROC_DIR ); // release the allocated page of kernel memory if ( kpage ) free_page( kpage ); printk( "<1>Removing \'%s\' module\n", modname ); }