//------------------------------------------------------------------- // mynetdvr.c // // This module implements a minimal Linux network device-driver // for our Intel PRO1000 gibabit ethernet interface controller. // // NOTE: Written and tested with Linux kernel version 2.6.22.5. // // programmer: ALLAN CRUSE // written on: 22 FEB 2008 //------------------------------------------------------------------- #include // for init_module() #include // for alloc_etherdev() #include // for request_irq() #include // for create_proc_info_entry() #include // for pci_get_device() #define VENDOR_ID 0x8086 // Intel Corporation #define DEVICE_ID 0x109A // 82573L controller //#define DEVICE_ID 0x10B9 // 82572EI controller #define INTR_MASK 0xFFFFFFFF // nic interrupt mask #define N_RX_DESC 8 // number of RX Descriptors #define N_TX_DESC 8 // number of TX Descriptors #define RX_BUFSIZ 2048 // size of RX packet-buffer #define TX_BUFSIZ 2048 // size of TX packet-buffer typedef struct { unsigned long long base_address; unsigned short packet_length; unsigned short packet_chksum; unsigned char desc_status; unsigned char desc_errors; unsigned short vlan_tag; } RX_DESCRIPTOR; typedef struct { unsigned long long base_address; unsigned short packet_length; unsigned char cksum_offset; unsigned char desc_command; unsigned char desc_status; unsigned char cksum_origin; unsigned short special_info; } TX_DESCRIPTOR; typedef struct { RX_DESCRIPTOR rxring[ N_RX_DESC ]; TX_DESCRIPTOR txring[ N_TX_DESC ]; unsigned char rxbuff[ N_RX_DESC * RX_BUFSIZ ]; unsigned char txbuff[ N_TX_DESC * TX_BUFSIZ ]; unsigned int rxnext; struct tasklet_struct rx_tasklet; } MY_DRIVERDATA; enum { E1000_CTRL = 0x0000, // Device Control E1000_STATUS = 0x0008, // Device Status E1000_ICR = 0x00C0, // Interrupt Cause Read E1000_ICS = 0x00C8, // Interrupt Cause Set E1000_IMS = 0x00D0, // Interrupt Mask Set E1000_IMC = 0x00D8, // Interrupt Mask Clear E1000_RCTL = 0x0100, // Receive Control E1000_TCTL = 0x0400, // Transmit Control E1000_RDBAL = 0x2800, // RX Descriptor Base-Address Low E1000_RDBAH = 0x2804, // RX Descriptor Base-Address High E1000_RDLEN = 0x2808, // RX Descriptor-queue Length E1000_RDH = 0x2810, // RX Descriptor-queue Head E1000_RDT = 0x2818, // RX Descriptor-queue Tail E1000_RXDCTL = 0x2828, // RX Descriptor-queue Control E1000_TDBAL = 0x3800, // TX Descriptor Base-Address Low E1000_TDBAH = 0x3804, // TX Descriptor Base-Address High E1000_TDLEN = 0x3808, // TX Descriptor-queue Length E1000_TDH = 0x3810, // TX Descriptor-queue Head E1000_TDT = 0x3818, // TX Descriptor-queue Tail E1000_TXDCTL = 0x3828, // TX Descriptor-queue Control E1000_RA = 0x5400, // Receive-address Array }; // function prototypes static int __init my_init( void ); static void __exit my_exit( void ); irqreturn_t my_isr( int, void * ); void my_rx_handler( unsigned long ); int my_open( struct net_device * ); int my_stop( struct net_device * ); int my_hard_start_xmit( struct sk_buff *, struct net_device * ); int my_get_info( char *buf, char **start, off_t off, int buflen ); module_init( my_init ); module_exit( my_exit ); MODULE_LICENSE("GPL"); // global variables char modname[] = "mynetdvr"; struct pci_dev *devp; unsigned int mmio_base; unsigned int mmio_size; void *io; struct net_device *my_netdev; static int __init my_init( void ) { u16 pci_cmd; printk( "<1>\nInstalling \'%s\' module\n", modname ); // detect presence of the Intel Pro1000 controller devp = pci_get_device( VENDOR_ID, DEVICE_ID, NULL ); if ( !devp ) return -ENODEV; // map the controller's i/o-memory into kernel space mmio_base = pci_resource_start( devp, 0 ); mmio_size = pci_resource_len( devp, 0 ); io = ioremap_nocache( mmio_base, mmio_size ); if ( !io ) return -ENOSPC; // insure the controller's Bus Master capability is enabled pci_read_config_word( devp, 4, &pci_cmd ); pci_cmd |= (1<<2); pci_write_config_word( devp, 4, pci_cmd ); // allocate kernel memory for the 'net_device' structure my_netdev = alloc_etherdev( sizeof( MY_DRIVERDATA ) ); if ( !my_netdev ) { iounmap( io ); return -ENOMEM; } // initialize essential fields in the 'net_device' structure memcpy( my_netdev->perm_addr, io + E1000_RA, 6 ); memcpy( my_netdev->dev_addr, io + E1000_RA, 6 ); my_netdev->open = my_open; my_netdev->stop = my_stop; my_netdev->hard_start_xmit = my_hard_start_xmit; my_netdev->mem_start = mmio_base; my_netdev->mem_end = mmio_base + mmio_size; my_netdev->irq = devp->irq; my_netdev->flags |= IFF_PROMISC; // create our driver's pseudo-file (for debugging) create_proc_info_entry( modname, 0, NULL, my_get_info ); // register this driver's 'net_device' structure return register_netdev( my_netdev ); } static void __exit my_exit( void ) { remove_proc_entry( modname, NULL ); unregister_netdev( my_netdev ); iounmap( io ); free_netdev( my_netdev ); printk( "<1>Removing \'%s\' module\n", modname ); } int my_open( struct net_device *dev ) { //-------------------------------------------------------- // The kernel calls this function whenever the 'ifconfig' // command is executed to bring up the device interface. // This function needs to call 'netif_start_queue()'. //-------------------------------------------------------- MY_DRIVERDATA *mdp = (MY_DRIVERDATA *)dev->priv; RX_DESCRIPTOR *rxq = mdp->rxring; TX_DESCRIPTOR *txq = mdp->txring; unsigned long rbuf = virt_to_phys( mdp->rxbuff ); unsigned long tbuf = virt_to_phys( mdp->txbuff ); unsigned long rxdescaddr = virt_to_phys( rxq ); unsigned long txdescaddr = virt_to_phys( txq ); int i; printk( " %s: opening the \'%s\' device \n", modname, dev->name ); // reset the network controller iowrite32( 0xFFFFFFFF, io + E1000_IMC ); iowrite32( 0x00000000, io + E1000_STATUS ); iowrite32( 0x040C0241, io + E1000_CTRL ); iowrite32( 0x000C0241, io + E1000_CTRL ); while ( ( ioread32( io + E1000_STATUS )&3 ) != 3 ); // initialize the RX Descriptor-queues for (i = 0; i < N_RX_DESC; i++ ) { rxq[ i ].base_address = rbuf + i * RX_BUFSIZ; rxq[ i ].packet_length = 0; rxq[ i ].packet_chksum = 0; rxq[ i ].desc_status = 0; rxq[ i ].desc_errors = 0; rxq[ i ].vlan_tag = 0; } // initialize the TX Descriptor-queues for (i = 0; i < N_TX_DESC; i++ ) { txq[ i ].base_address = tbuf + i * TX_BUFSIZ; txq[ i ].packet_length = 0; txq[ i ].cksum_offset = 0; txq[ i ].desc_command = (1<<0)|(1<<1)|(1<<3); // EOP/IFCS/RS txq[ i ].desc_status = 0; txq[ i ].cksum_origin = 0; txq[ i ].special_info = 0; } // configure the controller's Receive Engine iowrite32( 0x0400801C, io + E1000_RCTL ); iowrite32( rxdescaddr, io + E1000_RDBAL ); iowrite32( 0x00000000, io + E1000_RDBAH ); iowrite32( N_RX_DESC * 16, io + E1000_RDLEN ); iowrite32( 0x01010000, io + E1000_RXDCTL ); // configure the controller's Transmit Engine iowrite32( 0x0103F0F8, io + E1000_TCTL ); iowrite32( txdescaddr, io + E1000_TDBAL ); iowrite32( 0x00000000, io + E1000_TDBAH ); iowrite32( N_TX_DESC * 16, io + E1000_TDLEN ); iowrite32( 0x01010000, io + E1000_TXDCTL ); // initialize our tasklet mdp->rxnext = 0; tasklet_init( &mdp->rx_tasklet, my_rx_handler, (unsigned long)dev ); // install our driver's interrupt-handler i = dev->irq; if ( request_irq( i, my_isr, IRQF_SHARED, dev->name, dev ) < 0 ) return -EBUSY; // unmask interrupts ioread32( io + E1000_ICR ); iowrite32( INTR_MASK, io + E1000_IMS ); // start the receive engine iowrite32( N_RX_DESC, io + E1000_RDT ); iowrite32( ioread32( io + E1000_RCTL ) | (1<<1), io + E1000_RCTL ); // start the transmit engine iowrite32( ioread32( io + E1000_TCTL ) | (1<<1), io + E1000_TCTL ); netif_start_queue( dev ); return 0; // SUCCESS } int my_stop( struct net_device *dev ) { //-------------------------------------------------------- // The kernel calls this function whenever the 'ifconfig' // command is executed to shut down the device interface. // This function needs to call 'netif_stop_queue()'. //-------------------------------------------------------- MY_DRIVERDATA *mdp = (MY_DRIVERDATA *)dev->priv; printk( " %s: stopping the \'%s\' device \n", modname, dev->name ); // stop the controller's transmit and receive engines iowrite32( ioread32( io + E1000_RCTL ) & ~(1<<1), io + E1000_RCTL ); iowrite32( ioread32( io + E1000_TCTL ) & ~(1<<1), io + E1000_TCTL ); // disable controller interrupts and remove interrupt-handler iowrite32( 0xFFFFFFFF, io + E1000_IMC ); free_irq( dev->irq, dev ); // stop our tasklet and the network interface queue tasklet_kill( &mdp->rx_tasklet ); netif_stop_queue( dev ); return 0; // SUCCESS } int my_hard_start_xmit( struct sk_buff *skb, struct net_device *dev ) { //------------------------------------------------------------ // The kernel calls this function whenever its protocol layer // has a packet that it wants the controller to transmit. It // must either call the 'netif_free_skb()' function itself or // else arrange for it to be called by our interrupt-handler. //------------------------------------------------------------ MY_DRIVERDATA *mdp = (MY_DRIVERDATA*)dev->priv; TX_DESCRIPTOR *txq = mdp->txring; unsigned int curr = ioread32( io + E1000_TDT ); unsigned int next = (1 + curr) % N_TX_DESC; unsigned char *src = skb->data; unsigned char *dst = phys_to_virt( txq[ curr ].base_address ); unsigned short len = skb->len; // save the timestamp dev->trans_start = jiffies; // copy the socket-buffer's data into the next packet-buffer if ( len > TX_BUFSIZ ) len = TX_BUFSIZ; memcpy( dst, src, len ); // setup the next TX Descriptor txq[ curr ].packet_length = len; txq[ curr ].cksum_offset = 0; txq[ curr ].cksum_origin = 0; txq[ curr ].special_info = 0; txq[ curr ].desc_status = 0; txq[ curr ].desc_command = (1<<0)|(1<<1)|(1<<3); // EOP/IFCS/RS // initiate the transmission iowrite32( next, io + E1000_TDT ); // update the 'net_device' statistics dev->stats.tx_packets += 1; dev->stats.tx_bytes += len; // it is essential to free the socket-buffer structure dev_kfree_skb( skb ); return 0; // SUCCESS } void my_rx_handler( unsigned long data ) { //-------------------------------------------------------------- // This function is scheduled by our driver's interrupt-handler // whenever the controller has received some new packets. //-------------------------------------------------------------- struct net_device *dev = (struct net_device*)data; MY_DRIVERDATA *mdp = (MY_DRIVERDATA*)dev->priv; RX_DESCRIPTOR *rdq = (RX_DESCRIPTOR*)mdp->rxring; unsigned int curr = mdp->rxnext; void *src = phys_to_virt( rdq[ curr ].base_address ); int len = rdq[ curr ].packet_length; struct sk_buff *skb = dev_alloc_skb( len + NET_IP_ALIGN ); // allocate a new socket-buffer if ( !skb ) { dev->stats.rx_dropped += 1; return; } // copy received packet-data into this socket-buffer memcpy( skb_put( skb, len ), src, len ); // adjust the socket-buffer's parameters skb->dev = dev; skb->protocol = eth_type_trans( skb, dev ); skb->ip_summed = CHECKSUM_NONE; // clear the current descriptor's status rdq[ curr ].desc_status = 0; rdq[ curr ].desc_errors = 0; // advance our driver's 'rxnext' index mdp->rxnext = (1 + curr) % N_RX_DESC; // update the 'net_device' statistics dev->stats.rx_packets += 1; dev->stats.rx_bytes += len; // record the timestamp dev->last_rx = jiffies; // now hand over the socket-buffer to the kernel netif_rx( skb ); } irqreturn_t my_isr( int irq, void *dev_id ) { struct net_device *dev = (struct net_device*)dev_id; MY_DRIVERDATA *mdp = (MY_DRIVERDATA*)dev->priv; unsigned int intr_cause = ioread32( io + E1000_ICR ); if ( intr_cause == 0 ) return IRQ_NONE; // clear these interrupts iowrite32( intr_cause, io + E1000_ICR ); // schedule our interrupt-handler's 'bottom-half' if ( intr_cause & (1<<7) ) tasklet_schedule( &mdp->rx_tasklet ); return IRQ_HANDLED; } int my_get_info( char *buf, char **start, off_t off, int buflen ) { MY_DRIVERDATA *mdp = (MY_DRIVERDATA*)my_netdev->priv; RX_DESCRIPTOR *rdq = mdp->rxring; TX_DESCRIPTOR *tdq = mdp->txring; unsigned int rxhead = ioread32( io + E1000_RDH ); unsigned int rxtail = ioread32( io + E1000_RDT ); unsigned int txhead = ioread32( io + E1000_TDH ); unsigned int txtail = ioread32( io + E1000_TDT ); int i, len = 0; len += sprintf( buf+len, "\n Receive-Descriptor Queue " ); len += sprintf( buf+len, "(head=%d, tail=%d) \n\n", rxhead, rxtail ); for (i = 0; i < N_RX_DESC; i++) { unsigned int ba = virt_to_phys( rdq+i ); len += sprintf( buf+len, " #%-2d ", i ); len += sprintf( buf+len, "%08X: ", ba ); len += sprintf( buf+len, "%016llX ", rdq[i].base_address ); len += sprintf( buf+len, "%04X ", rdq[i].packet_length ); len += sprintf( buf+len, "%04X ", rdq[i].packet_chksum ); len += sprintf( buf+len, "%02X ", rdq[i].desc_status ); len += sprintf( buf+len, "%02X ", rdq[i].desc_errors ); len += sprintf( buf+len, "%04X ", rdq[i].vlan_tag ); len += sprintf( buf+len, "\n" ); } len += sprintf( buf+len, "\n Transmit-Descriptor Queue " ); len += sprintf( buf+len, "(head=%d, tail=%d) \n\n", txhead, txtail ); for (i = 0; i < N_TX_DESC; i++) { unsigned int ba = virt_to_phys( tdq+i ); len += sprintf( buf+len, " #%-2d ", i ); len += sprintf( buf+len, "%08X: ", ba ); len += sprintf( buf+len, "%016llX ", tdq[i].base_address ); len += sprintf( buf+len, "%04X ", tdq[i].packet_length ); len += sprintf( buf+len, "%02X ", tdq[i].cksum_offset ); len += sprintf( buf+len, "%02X ", tdq[i].desc_command ); len += sprintf( buf+len, "%02X ", tdq[i].desc_status ); len += sprintf( buf+len, "%02X ", tdq[i].cksum_origin ); len += sprintf( buf+len, "%04X ", tdq[i].special_info ); len += sprintf( buf+len, "\n" ); } len += sprintf( buf+len, "\n" ); return len; }