1. 8139too网卡设备简介
一个PCI设备,总共有三个地址空间:内存,端口和配置。内存和端口其实是同一个内容的不同访问路径而已。PCI设备的配置空间是标准化的,每个PCI设备的配置空间的前个字节的含义都是一样的。但各个设备的内存和端口空间是完全不一样的。在硬件概念上,设备的I/O内存和I/O端口空间没有区别,其实都是设备拥用的一些读写寄存器。 8139too网卡拥有256字节的读写寄存器空间。它的整个布局如下: /* Symbolic offsets to registers. */ enum RTL8139_registers {
MAC0 = 0, /* Ethernet hardware address. */ MAR0 = 8, /* Multicast filter. */
TxStatus0 = 0x10, /* Transmit status (Four 32bit registers). */ TxAddr0 = 0x20, /* Tx descriptors (also four 32bit). */ RxBuf = 0x30, ChipCmd = 0x37, RxBufPtr = 0x38, RxBufAddr = 0x3A, IntrMask = 0x3C, IntrStatus = 0x3E, TxConfig = 0x40, RxConfig = 0x44,
Timer = 0x48, /* A general-purpose counter. */ RxMissed = 0x4C, /* 24 bits valid, write clears. */ Cfg9346 = 0x50, Config0 = 0x51, Config1 = 0x52, FlashReg = 0x, MediaStatus = 0x58, Config3 = 0x59,
Config4 = 0x5A, /* absent on RTL-8139A */ HltClk = 0x5B, MultiIntr = 0x5C, TxSummary = 0x60, BasicModeCtrl = 0x62, BasicModeStatus = 0x, NWayAdvert = 0x66, NWayLPAR = 0x68, NWayExpansion = 0x6A,
/* Undocumented registers, but required for proper operation. */ FIFOTMS = 0x70, /* FIFO Control and test. */
CSCR = 0x74, /* Chip Status and Configuration Register. */ PARA78 = 0x78,
PARA7c = 0x7c, /* Magic transceiver parameter register. */ Config5 = 0xD8, /* absent on RTL-8139A */ };
每个寄存器都有它特殊的含义和用途。举个例子(我们假设使用I/O内存的方式,ioaddr为设备内存映射在CPU内存地址空间的起始地址,下述代码能读取板卡的版本号): #define HW_REVID(b30, b29, b28, b27, b26, b23, b22) \\
(b30<<30 | b29<<29 | b28<<28 | b27<<27 | b26<<26 | b23<<23 | b22<<22) #define HW_REVID_MASK HW_REVID(1, 1, 1, 1, 1, 1, 1) /* identify chip attached to board */
version = ioread32( ioaddr + TxConfig ) & HW_REVID_MASK;
在我的电脑上,version读出来的值的0x74400000,经过比对,板卡名称为:RTL-8100B/8139D。
下面是整个系例版本号的表格: enum chip_flags {
HasHltClk = (1 << 0), HasLWake = (1 << 1), };
/* directly indexed by chip_t, above */ const static struct { const char *name;
u32 version; /* from RTL8139C/RTL8139D docs */ u32 flags;
} rtl_chip_info[] = {
{ \"RTL-8139\HW_REVID(1, 0, 0, 0, 0, 0, 0),HasHltClk,}, { \"RTL-8139 rev K\ { \"RTL-8139A\HW_REVID(1, 1, 1, 0, 0, 0, 0),HasHltClk,}, { \"RTL-8139A rev G\ { \"RTL-8139B\HW_REVID(1, 1, 1, 1, 0, 0, 0),HasLWake, }, { \"RTL-8130\HW_REVID(1, 1, 1, 1, 1, 0, 0),HasLWake, }, { \"RTL-8139C\HW_REVID(1, 1, 1, 0, 1, 0, 0),HasLWake, }, { \"RTL-8100\HW_REVID(1, 1, 1, 1, 0, 1, 0),HasLWake, }, { \"RTL-8100B/8139D\ { \"RTL-8101\HW_REVID(1, 1, 1, 0, 1, 1, 1),HasLWake, }, };
在这个表格中,RTL_8139B以下(包括RTL_8139B)的板卡都属于较新的版本。新老版本之间有不同的唤醒方式。先看新版本的: #define LWAKE 0x10 #define Cfg1_PM_Enable 0x01 u8 new_tmp8, tmp8;
enum Config4Bits {
LWPTN = (1 << 2), /* not on 8139, 8139A */ };
enum Cfg9346Bits {
Cfg9346_Lock = 0x00, Cfg9346_Unlock = 0xC0, };
new_tmp8 = tmp8 = ioread8( ioaddr + Config1 );
if( (rtl_chip_info[tp->chipset].flags & HasLWake) && (tmp8 & LWAKE)) new_tmp8 &= ~LWAKE; new_tmp8 |= Cfg1_PM_Enable; if (new_tmp8 != tmp8) {
iowrite8( Cfg9346_Unlock, ioaddr + Cfg9346 ); iowrite8( tmp8, ioaddr + Config1 );
iowrite8( Cfg9346_Lock, ioaddr + Cfg9346 ); }
if (rtl_chip_info[tp->chipset].flags & HasLWake) { tmp8 = ioread8( ioaddr + Config4 ); if (tmp8 & LWPTN) {
iowrite8( Cfg9346_Unlock, ioaddr + Cfg9346 ); iowrite8( tmp8 & ~LWPTN, ioaddr + Config4 ); iowrite8( Cfg9346_Lock, ioaddr + Cfg9346 ); } }
基本的一个流程是:如果板卡版本本身支持了HasLWake,而Config1中读出的值带有LWAKE,把Config1的值写回,并把Config4 中的LWPTN去除。而我的网卡中从Config1, Config4读取的值分别为0x8d, 0x88,所以,无需做任何操作。 下面是旧版本的唤醒方式: enum Config1Bits {
Cfg1_PM_Enable = 0x01, Cfg1_VPD_Enable = 0x02, Cfg1_PIO = 0x04, Cfg1_MMIO = 0x08,
LWAKE = 0x10, /* not on 8139, 8139A */ Cfg1_Driver_Load = 0x20, Cfg1_LED0 = 0x40, Cfg1_LED1 = 0x80,
SLEEP = (1 << 1), /* only on 8139, 8139A */ PWRDN = (1 << 0), /* only on 8139, 8139A */ };
tmp8 = ioread8( ioaddr + Config1 ); tmp8 &= ~(SLEEP | PWRDN); iowrite8( tmp8, ioaddr + Config1 );
下面是一个板卡的复位操作: enum ChipCmdBits { CmdReset = 0x10, CmdRxEnb = 0x08,
CmdTxEnb = 0x04, RxBufEmpty = 0x01, };
static void rtl8139_chip_reset (void __iomem *ioaddr) {
int i;
/* Soft reset the chip. */
iowrite8( CmdReset, ioaddr + ChipCmd ); /* Check that the chip has finished the reset. */ for (i = 1000; i > 0; i--) { barrier();
if ((ioread8( ioaddr + ChipCmd ) & CmdReset) == 0) break; udelay (10); } }
写一个CmdReset命令到ChipCmd位置,等待该命令消失,即可。 关于网卡的寄存器操作,还有一些,再进行过程中遇到时再介绍。
2. 网络设备的初始化
网络接口是字符设备,块设备之后的第三类标准Linux设备。 网络驱动程序和其它内核模块一样,当被装载到正在运行的内核中时,它要请求资源并提供一些功能设施。网络驱动程序对每个新检测到的接口,会向全局的网络设备链表中插入一个数据结构。每个接口由一个net_device结构描述,这是一个很庞大的结构,在下面的描述中,我们会看到一些这个结构的成员。
在8139too网卡的驱动程序中,我们看到alloc_etherdev动态分配了该结构,这是为以太网接口封装的一个分配函数:
struct net_device *alloc_etherdev(int sizeof_priv) {
return alloc_netdev(sizeof_priv, \"eth%d\}
EXPORT_SYMBOL(alloc_etherdev);
真正用来实现网络设备接口分配的函数是alloc_netdev alloc_ehterdev的封装为以太网设备接口分配了形如“eth%d”的名字,同时,指定了一个ether_setup的初始化函数: void ether_setup(struct net_device *dev) {
dev->change_mtu = eth_change_mtu; dev->hard_header = eth_header;
dev->rebuild_header = eth_rebuild_header; dev->set_mac_address = eth_mac_addr;
dev->hard_header_cache = eth_header_cache;
dev->header_cache_update= eth_header_cache_update; dev->hard_header_parse = eth_header_parse;
dev->type = ARPHRD_ETHER;
dev->hard_header_len = ETH_HLEN; dev->mtu = 1500; /* eth_mtu */ dev->addr_len = ETH_ALEN;
dev->tx_queue_len = 1000; /* Ethernet wants good queues */ dev->flags = IFF_BROADCAST|IFF_MULTICAST;
memset(dev->broadcast,0xFF, ETH_ALEN); }
EXPORT_SYMBOL(ether_setup);
它为表示以太网设备接口的net_device结构进行了部分初始化,因为对以太网设备来讲,很多操作与属性是固定的,内核可以帮助完成。
alloc_netdev分配sizeof(dev) + sizeof_priv的内核页,并调用初始化函数。
这些都是在PCI的探测函数中做的事情,在完成了网络设备接口的分配后,我们要访问PCI设备的一些资源。在能够访问PCI设备的任何资源之前,我们必须激活PCI设备: /* enable device (incl. PCI PM wakeup and hotplug setup) */ rc = pci_enable_device (pdev); if (rc) goto err_out; 激活PCI设备后,我们可以从PCI设备的配置空间读取6个I/O地址区域,在我们的例子程序中,是从8139
网卡的第1个I/O地址区域读取I/O内存空间,然后调用 pci_request_regions(pdev, \"8139too\");
进行I/O内存分配。该函数实际调用request_mem_region函数。 接下来,进行一系例的板卡相关的初始化操作。
最后,接着ether_setup的操作,我们要对net_device作一些自己的初始化操作: dev->open = rtl8139_open;
dev->hard_start_xmit = rtl8139_start_xmit; dev->get_stats = rtl8139_get_stats; dev->stop = rtl8139_close;
open函数会在网络设备接口被注册的时候被调用,hard_start_xmit在有数据需要传输的时候被调用,get_stats返回网络接口的统计信息。以上三个成员如果没有被初始化,则在注册网络接口的时候会造成内核崩溃。最后一个stop会在网络接口被停掉的时候被调用。
完成了这些初始化操作之后,我们就可以注册网络设备接口了,一旦注册完毕,网络设备接口就可以被使用了: i = register_netdev(dev); if(i) return i;
register_netdev首先为网络设备接口分配一个名称(比如把eth%d替换为eth0)。然后将dev插入到一个叫做dev_base的网络设备全局链表中。 下面是整个8139too网卡设备初始化代码: /* 8139_init.h
* helinqiang@hotmail.com * 2006-3-11 */
#ifndef RTL_8139_INIT_H #define RTL_8139_INIT_H
#include #include #define DRV_NAME \"8139too\" #define DRV_VERSION \"0.9.27\" #define RTL8139_DRIVER_NAME DRV_NAME \" Fast Ethernet driver \" DRV_VERSION typedef enum{ RTL8139 = 0, RTL8129, }board_t; static struct pci_device_id rtl8139_pci_tbl[] = { {0x10ec, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x10ec, 0x8138, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x1113, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x1500, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x4033, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x1186, 0x1300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x1186, 0x1340, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x13d1, 0xab06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x1259, 0xa117, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x1259, 0xa11e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x14ea, 0xab06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x14ea, 0xab07, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x11db, 0x1234, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x1432, 0x9130, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x02ac, 0x1012, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x018a, 0x0106, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x126c, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x1743, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x021b, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, #ifdef CONFIG_SH_SECUREEDGE10 /* Bogus 8139 silicon reports 8129 without external PROM :-( */ {0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, #endif #ifdef CONFIG_8139TOO_8129 {0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8129 }, #endif /* some crazy cards report invalid vendor ids like * 0x0001 here. The other ids are valid and constant, * so we simply don't match on the main vendor id. */ {PCI_ANY_ID, 0x8139, 0x10ec, 0x8139, 0, 0, RTL8139 }, {PCI_ANY_ID, 0x8139, 0x1186, 0x1300, 0, 0, RTL8139 }, {PCI_ANY_ID, 0x8139, 0x13d1, 0xab06, 0, 0, RTL8139 }, {0,} }; MODULE_DEVICE_TABLE(pci, rtl8139_pci_tbl); static int __devinit rtl8139_init_one(struct pci_dev *pdev, const struct pci_device_id *id); static void __devexit rtl8139_remove_one(struct pci_dev *pdev); static int rtl8139_open( struct net_device *dev ); static struct net_device_stats *rtl8139_get_stats (struct net_device *dev); static int rtl8139_start_xmit (struct sk_buff *skb, struct net_device *dev); static int rtl8139_close( struct net_device *dev ); static void rtl8139_chip_reset (void __iomem *ioaddr); static int __devinit rtl8139_init_board (struct pci_dev *pdev, struct net_device **dev_out); static void __rtl8139_cleanup_dev (struct net_device *dev); static struct pci_driver rtl8139_pci_driver = { .name = DRV_NAME, .id_table = rtl8139_pci_tbl, .probe = rtl8139_init_one, .remove = __devexit_p(rtl8139_remove_one), }; /* Bits in Config4 */ enum Config4Bits { LWPTN = (1 << 2), /* not on 8139, 8139A */ }; enum chip_flags { HasHltClk = (1 << 0), HasLWake = (1 << 1), }; enum ChipCmdBits { CmdReset = 0x10, CmdRxEnb = 0x08, CmdTxEnb = 0x04, RxBufEmpty = 0x01, }; enum Cfg9346Bits { Cfg9346_Lock = 0x00, Cfg9346_Unlock = 0xC0, }; typedef enum { CH_8139 = 0, CH_8139_K, CH_8139A, CH_8139A_G, CH_8139B, CH_8130, CH_8139C, CH_8100, CH_8100B_8139D, CH_8101, } chip_t; struct rtl8139_private{ struct pci_dev* pci_dev; void __iomem* mmio_addr; unsigned long regs_len; unsigned int chipset; struct net_device_stats stats; }; /* Symbolic offsets to registers. */ enum RTL8139_registers { MAC0 = 0, /* Ethernet hardware address. */ MAR0 = 8, /* Multicast filter. */ TxStatus0 = 0x10, /* Transmit status (Four 32bit registers). */ TxAddr0 = 0x20, /* Tx descriptors (also four 32bit). */ RxBuf = 0x30, ChipCmd = 0x37, RxBufPtr = 0x38, RxBufAddr = 0x3A, IntrMask = 0x3C, IntrStatus = 0x3E, TxConfig = 0x40, RxConfig = 0x44, Timer = 0x48, /* A general-purpose counter. */ RxMissed = 0x4C, /* 24 bits valid, write clears. */ Cfg9346 = 0x50, Config0 = 0x51, Config1 = 0x52, FlashReg = 0x, MediaStatus = 0x58, Config3 = 0x59, Config4 = 0x5A, /* absent on RTL-8139A */ HltClk = 0x5B, MultiIntr = 0x5C, TxSummary = 0x60, BasicModeCtrl = 0x62, BasicModeStatus = 0x, NWayAdvert = 0x66, NWayLPAR = 0x68, NWayExpansion = 0x6A, /* Undocumented registers, but required for proper operation. */ FIFOTMS = 0x70, /* FIFO Control and test. */ CSCR = 0x74, /* Chip Status and Configuration Register. */ PARA78 = 0x78, PARA7c = 0x7c, /* Magic transceiver parameter register. */ Config5 = 0xD8, /* absent on RTL-8139A */ }; #define HW_REVID(b30, b29, b28, b27, b26, b23, b22) \\ (b30<<30 | b29<<29 | b28<<28 | b27<<27 | b26<<26 | b23<<23 | b22<<22) #define HW_REVID_MASK HW_REVID(1, 1, 1, 1, 1, 1, 1) /* directly indexed by chip_t, above */ const static struct { const char *name; u32 version; /* from RTL8139C/RTL8139D docs */ u32 flags; } rtl_chip_info[] = { { \"RTL-8139\ { \"RTL-8139 rev K\{ \"RTL-8139A\ { \"RTL-8139A rev G\{ \"RTL-8139B\{ \"RTL-8130\{ \"RTL-8139C\{ \"RTL-8100\ { \"RTL-8100B/8139D\ { \"RTL-8101\}; /* Bits in Config1 */ enum Config1Bits { Cfg1_PM_Enable = 0x01, Cfg1_VPD_Enable = 0x02, Cfg1_PIO = 0x04, Cfg1_MMIO = 0x08, LWAKE = 0x10, /* not on 8139, 8139A */ Cfg1_Driver_Load = 0x20, Cfg1_LED0 = 0x40, Cfg1_LED1 = 0x80, SLEEP = (1 << 1), /* only on 8139, 8139A */ PWRDN = (1 << 0), /* only on 8139, 8139A */ }; #endif //RTL_8139_INIT_H /* 8139_init.c * helinqiang@hotmail.com * 2006-3-11 */ #include \"8139_init.h\" #include #include MODULE_AUTHOR(\"Linqiang He, Hangzhou China\"); MODULE_LICENSE(\"Dual BSD/GPL\"); static int __init rtl8139_init_module(void) { /* when we're a module, we always print a version message, * even if no 8139 board is found. */ #ifdef MODULE printk (KERN_INFO RTL8139_DRIVER_NAME \"\\n\"); #endif return pci_module_init(&rtl8139_pci_driver); } static void __exit rtl8139_cleanup_module (void) { pci_unregister_driver(&rtl8139_pci_driver); } module_init(rtl8139_init_module); module_exit(rtl8139_cleanup_module); int __devinit rtl8139_init_one(struct pci_dev *pdev, const struct pci_device_id *id) { struct net_device *dev = NULL; int i = rtl8139_init_board (pdev, &dev); if (i < 0) return i; dev->open = rtl8139_open; dev->hard_start_xmit = rtl8139_start_xmit; dev->get_stats = rtl8139_get_stats; dev->stop = rtl8139_close; i = register_netdev(dev); if(i) return i; pci_set_drvdata( pdev, dev ); return 0; } void __devexit rtl8139_remove_one (struct pci_dev *pdev) { struct net_device *dev = pci_get_drvdata (pdev); unregister_netdev (dev); __rtl8139_cleanup_dev (dev); pci_disable_device (pdev); } static int __devinit rtl8139_init_board (struct pci_dev *pdev, struct net_device **dev_out) { void __iomem *ioaddr; struct rtl8139_private *tp; u8 tmp8; int rc, disable_dev_on_err = 0; unsigned int i; unsigned long mmio_start, mmio_end, mmio_flags, mmio_len; u32 version; *dev_out = alloc_etherdev (sizeof (*tp)); if (*dev_out == NULL) { printk (KERN_ERR \"%s: Unable to alloc new net device\\n\return -ENOMEM; } SET_MODULE_OWNER(*dev_out); SET_NETDEV_DEV(*dev_out, &pdev->dev); tp = netdev_priv(*dev_out); tp->pci_dev = pdev; /* enable device (incl. PCI PM wakeup and hotplug setup) */ rc = pci_enable_device (pdev); if (rc) goto err_out; mmio_start = pci_resource_start (pdev, 1); mmio_end = pci_resource_end (pdev, 1); mmio_flags = pci_resource_flags (pdev, 1); mmio_len = pci_resource_len (pdev, 1); rc = pci_request_regions(pdev, \"8139too\"); if (rc) goto err_out; disable_dev_on_err = 1; /* enable PCI bus-mastering */ pci_set_master (pdev); /* ioremap MMIO region */ ioaddr = pci_iomap(pdev, 1, 0); (*dev_out)->base_addr = (long) ioaddr; tp->mmio_addr = ioaddr; tp->regs_len = mmio_len; /* Bring old chips out of low-power mode. */ iowrite8('R', ioaddr + HltClk ); /* check for missing/broken hardware */ if ( ioread32( ioaddr + TxConfig ) == 0xFFFFFFFF ){ printk (KERN_ERR \"%s: Chip not responding, ignoring board\\n\rc = -EIO; goto err_out; } /* identify chip attached to board */ version = ioread32( ioaddr + TxConfig ) & HW_REVID_MASK; for (i = 0; i < ARRAY_SIZE (rtl_chip_info); i++) if (version == rtl_chip_info[i].version) { tp->chipset = i; goto match; } tp->chipset = 0; match: printk(KERN_INFO \"chipset id (%d) == index %d, '%s'\\n\version, i, rtl_chip_info[i].name); if (tp->chipset >= CH_8139B) { u8 new_tmp8 = tmp8 = ioread8( ioaddr + Config1 ); printk(KERN_ERR \"tmp81: %x\\n\ if ((rtl_chip_info[tp->chipset].flags & HasLWake) && (tmp8 & LWAKE)) new_tmp8 &= ~LWAKE; new_tmp8 |= Cfg1_PM_Enable; if (new_tmp8 != tmp8) { iowrite8( Cfg9346_Unlock, ioaddr + Cfg9346 ); iowrite8( tmp8, ioaddr + Config1 ); iowrite8( Cfg9346_Lock, ioaddr + Cfg9346 ); } if (rtl_chip_info[tp->chipset].flags & HasLWake) { tmp8 = ioread8( ioaddr + Config4 ); printk(KERN_ERR \"tmp8: %x\\n\if (tmp8 & LWPTN) { iowrite8( Cfg9346_Unlock, ioaddr + Cfg9346 ); iowrite8( tmp8 & ~LWPTN, ioaddr + Config4 ); iowrite8( Cfg9346_Lock, ioaddr + Cfg9346 ); } } }else{ tmp8 = ioread8( ioaddr + Config1 ); tmp8 &= ~(SLEEP | PWRDN); iowrite8( tmp8, ioaddr + Config1 ); } rtl8139_chip_reset (ioaddr); return 0; err_out: __rtl8139_cleanup_dev (*dev_out); if (disable_dev_on_err) pci_disable_device (pdev); return rc; } static int rtl8139_open( struct net_device *dev) { return 0; } static struct net_device_stats *rtl8139_get_stats (struct net_device *dev) { struct rtl8139_private *tp = netdev_priv(dev); return &tp->stats; } static int rtl8139_start_xmit (struct sk_buff *skb, struct net_device *dev) { struct rtl8139_private *tp = netdev_priv(dev); dev_kfree_skb(skb); tp->stats.tx_dropped++; return 0; } static int rtl8139_close( struct net_device *dev ) { return 0; } static void rtl8139_chip_reset (void __iomem *ioaddr) { int i; /* Soft reset the chip. */ iowrite8( CmdReset, ioaddr + ChipCmd ); /* Check that the chip has finished the reset. */ for (i = 1000; i > 0; i--) { barrier(); if ((ioread8( ioaddr + ChipCmd ) & CmdReset) == 0) break; udelay (10); } } static void __rtl8139_cleanup_dev (struct net_device *dev) { struct rtl8139_private *tp = netdev_priv(dev); struct pci_dev *pdev; pdev = tp->pci_dev; if (tp->mmio_addr) pci_iounmap (pdev, tp->mmio_addr); /* it's ok to call this even if we have no regions to free */ pci_release_regions (pdev); free_netdev(dev); pci_set_drvdata (pdev, NULL); 3.一个简单的网络流量统计程序 前面讲到,register_netdev首先为网络设备接口分配一个名称,然后将dev插入到一个叫做dev_base的网络设备全局链表中。由此我们不难想到,通过访问dev_base,就可以遍历到系统中的所有网络设备,而每一个网络设备接口都有一个net_device结构来表示。该结构中有一个成员函数指针: struct net_device_stats* (*get_stats)(struct net_device *dev); 它返回一个struct net_device_stats结构,该结构保存了所在网络设备接口的详细的流量统计信息: struct net_device_stats { unsigned long rx_packets; /* total packets received */ unsigned long tx_packets; /* total packets transmitted */ unsigned long rx_bytes; /* total bytes received */ unsigned long tx_bytes; /* total bytes transmitted */ unsigned long rx_errors; /* bad packets received */ unsigned long tx_errors; /* packet transmit problems */ unsigned long rx_dropped; /* no space in linux buffers */ unsigned long tx_dropped;/* no space available in linux */ unsigned long multicast; /* multicast packets received */ unsigned long collisions; /* detailed rx_errors: */ unsigned long rx_length_errors; unsigned long rx_over_errors; /* receiver ring buff overflow */ unsigned long rx_crc_errors;/* recved pkt with crc error */ unsigned long rx_frame_errors;/* recv'd frame alignment error */ unsigned long rx_fifo_errors; /* recv'r fifo overrun */ unsigned long rx_missed_errors;/* receiver missed packet */ /* detailed tx_errors */ unsigned long tx_aborted_errors; unsigned long tx_carrier_errors; unsigned long tx_fifo_errors; unsigned long tx_heartbeat_errors; unsigned long tx_window_errors; /* for cslip etc */ unsigned long rx_compressed; unsigned long tx_compressed; }; OK,有了这些基础知识,我们就可以写一个网络流量统计的内核模块了,这是一个最简单的程序,它每次被插入到内核中时,会在日志文件中打出当前系统上的所有网络设备接口的传输的和收到的字节数和包数。我们可以把它进一步改造,输出到/proc文件系统,再通过用户空间的一个程序计算单位时间的流量,就成为一个真正的网络流量统计程序了。 下面是代码: /* netbase.c * helinqiang@hotmail.com * 2006-03-14 */ #include #include MODULE_AUTHOR(\"Linqiang He, Hangzhou China\"); MODULE_LICENSE(\"Dual BSD/GPL\"); static int __init netbase_init_module(void) { struct net_device *dev = dev_base; struct net_device_stats *stats; while( dev != NULL ){ printk(KERN_INFO \"dev name: %s\\n\ stats = dev->get_stats(dev); printk(KERN_INFO \"\transmitted bytes: %lu\\n\stats->tx_bytes); printk(KERN_INFO \"\received bytes: %lu\\n\stats->rx_bytes); printk(KERN_INFO \"\transmitted packets: %lu\\n\stats->tx_packets); printk(KERN_INFO \"\received packets: %lu\\n\stats->rx_packets); dev = dev->next; } return 0; } static void __exit netbase_exit_module(void) { } module_init(netbase_init_module); module_exit(netbase_exit_module); 下面是在我的电脑上的某一时刻的输出结果: Mar 14 21:22:34 localhost kernel: dev name: lo Mar 14 21:22:34 localhost kernel: transmitted bytes: 4660 Mar 14 21:22:34 localhost kernel: received bytes: 4660 Mar 14 21:22:34 localhost kernel: transmitted packets: 61 Mar 14 21:22:34 localhost kernel: received packets: 61 Mar 14 21:22:34 localhost kernel: dev name: sit0 Mar 14 21:22:34 localhost kernel: transmitted bytes: 0 Mar 14 21:22:34 localhost kernel: received bytes: 0 Mar 14 21:22:34 localhost kernel: transmitted packets: 0 Mar 14 21:22:34 localhost kernel: received packets: 0 Mar 14 21:22:34 localhost kernel: dev name: eth0 Mar 14 21:22:34 localhost kernel: transmitted bytes: 437071 Mar 14 21:22:34 localhost kernel: received bytes: 40403 Mar 14 21:22:34 localhost kernel: transmitted packets: 2607 Mar 14 21:22:34 localhost kernel: received packets: 17773 4. net_device结构体详解 结构体net_device代表了一个网络设备接口,它是我们理解网络设备驱动程序的关键。这里,我们选择其中的一些重要成员,一一作详细分析,并结合以太网设备,看看Linux内核是如何为以太网设备提供结构体中某些成员的缺省值的。 在Linux内核源代码中是这样为这个结构体作注释的:实际上,这个结构体是一个很大的错误,它把I/O数据和更高层的数据混合在一起,而且它几乎必须知道INET模块中的每个数据结构。 毫无疑问,这是一个巨型结构体。但我们为编写网络设备驱动程序,只需要了解其中的一部分,下面选择其中的一些作分析,并给出以太网设备的缺省值。 unsigned short flags; void (*set_multicast_list)(struct net_device *dev); 这是一个接口标志,包含了很多值的位掩码。在以太网的缺省初始化函数中,该标志被设置为:IFF_BROADCAST|IFF_MULTICAST,表示以太网卡是可广播的,并且是能够进行组播发送的。另外,该标志接口还有一些只读标志,如IFF_UP,当接口被激活并可以开始传输数据包时,内核设置该标志。而IFF_PROMISC被设置或清除时,会调用set_multicast_list函数通知板卡上的硬件过滤器。 unsigned short hard_header_len; unsigned short type; hard_header_len是硬件头的长度,在以太网设备的初始化函数中,该成员被赋为ETH_HLEN,即以太网头的长度,该值为14,下面是以太网头的定义: struct ethhdr { unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ unsigned char h_source[ETH_ALEN]; /* source ether addr */ unsigned short h_proto; /* packet type ID field */ } __attribute__((packed)); ETH_ALEN被定义为6,即以太网MAC地址的长度。h_proto保存type的值。type是接口的硬件类型,以太网设备的初始化函数中将其赋值为ARPHRD_ETHER,即10Mb以太网。 int (*hard_header) (struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr,void *saddr, unsigned len); 该函数在数据被传输之前被调用,它根据先前检索到的源和目标地址建立硬件头。以太网设备的默认函数是eth_header: int eth_header(struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len) { struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN); /* * Set the protocol type. For a packet of * type ETH_P_802_3 we put the length * in here instead. It is up to the 802.2 * layer to carry protocol information. */ if(type!=ETH_P_802_3) eth->h_proto = htons(type); else eth->h_proto = htons(len); //Set the source hardware address. if(!saddr) saddr = dev->dev_addr; memcpy(eth->h_source,saddr,dev->addr_len); //Anyway, the loopback-device should never //use this function... if (dev->flags & (IFF_LOOPBACK|IFF_NOARP)){ memset(eth->h_dest, 0, dev->addr_len); return ETH_HLEN; } if(daddr){ memcpy(eth->h_dest,daddr,dev->addr_len); return ETH_HLEN; } return -ETH_HLEN; } 它根据传入的参数建立以太网头,如果传入的源地址为空,则使用设备的地址作为源地址。与之相关的还有一个int (*rebuild_header)(struct sk_buff *skb)函数,在以太网设备中,它是用于在ARP地址解析完成以后,重新建立以太网头,主要是更新目标MAC地址,因为在前一次建立时,由于没有经过 ARP协议,有可能目标MAC地址是错误的(未完,待续)。 5. net_device结构体详解(续) int (*set_mac_address)(struct net_device *dev, void *addr); 改变网卡的mac地址。实际上,许多硬件设备接口根本不支持这种功能,就算支持,也需要硬件厂商提供的工具修改EPROM中的硬件地址。一般我们在某一操作系统下所谓的修改MAC地址,只是修改了操作系统在安装网卡时从网卡EPROM中读出来的值,如果系统被重装,则MAC地址又恢复为原来的值。以太网设备的默认函数是eth_mac_addr,它只是简单地修改了dev->dev_addr的值。 int (*hard_header_cache)(struct neighbour *neigh,struct hh_cache *hh); void (*header_cache_update)(struct hh_cache *hh,struct net_device *dev, unsigned char * haddr); int (*hard_header_parse)(struct sk_buff *skb,unsigned char *haddr); 这三个函数在以太网设备接口中都有默认的值,hard_header_cache把硬件地址缓存到struct hh_cache中;header_cache_update在地址发生变化中,更新hh_cache结构中的目标地址;而 hard_header_parse则从skb中的数据包中获得源地址,并将其复制到位于haddr的缓 冲区。 int (*change_mtu)(struct net_device *dev, int new_mtu); 下面是以太网设备接口的change_mtu的实现: static int eth_change_mtu(struct net_device *dev, int new_mtu) { if ((new_mtu < 68) || (new_mtu > 1500)) return -EINVAL; dev->mtu = new_mtu; return 0; } 在net_device中,还有很多网络驱动程序必须涉及的重要成员,随着我们的8139too网卡驱动程序的进一步深入,在涉及时再作详细分析 6.分配中断号 & 为网卡驱动设置MAC地址 PCI设备的中断其实很容易处理。在Linux的引导阶段,计算机固件已经为设备分配了一个唯一的中断号。中断号保存在配置寄存器60 (PCI_INTERRUPT_LINE)中,该寄存器为一个字节宽。所以驱动程序无需检测中断号。只需从配置寄存器中读取即可: result = pci_read_config_byte(pci_dev, PCI_INTERRUPT_LINE, &myirq); 但事实上,留给具体的PCI驱动程序编写者的事情可能更为简单。在我们前面讲到过的8139too网卡的初始化程序的rtl8139_init_one函数(PCI设备注册成功即被调用的函数,注意,不是网络设备)的第一行加上以下语句: printk(KERN_INFO \"the irq: %d\\n\ OK,我们能得到结果: the irq: 10 也就是说PCI设备驱动程序在注册自己时,已经自动完成中断号的获取。 再回到我们前面讲过的8139too网卡的初始化驱动程序,编译完成,insmod后,我们运行系统的ifconfig命令: ifconfig eth0 得到结果: eth0 Link encap:Ethernet HWaddr 00:00:00:00:00:00 inet addr:192.168.0.100 Bcast:192.168.0.255 Mask:255.255.255.0 inet6 addr: fe80::200:ff:fe00:0/ Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:7 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) Base address:0x4800 怎么样?五脏俱全?没错,由于以太网设备接口本身已由内核提供了很多缺省成员值,已经能够正确显示MTU,并且有了传输队列的长度。但它的MAC地址: HWaddr 00:00:00:00:00:00似乎不大对。这是因为代表网络设备接口的net_device结构体的成员dev_addr没有被正确设置。网络驱动程序还需要从网卡设备的EPROM中读取MAC地址填入到dev_addr字符数组中才行。 前文已经有讲到,8139too网卡的设备内存的头6个字节即是MAC地址,直接读取即可。但我不知道为什么,8139too网卡的驱动程序采用了一个非常复杂的从EPROM读取信息的函数,也许是为了安全,或者通用。下面的代码插入到rtl8139_init_one函数中注册网络设备之前的位置: int i, addr_len; void __iomem *ioaddr; struct rtl8139_private *tp; tp = netdev_priv(dev); ioaddr = tp->mmio_addr; addr_len = read_eeprom (ioaddr, 0, 8) == 0x8129 ? 8 : 6; for (i = 0; i < 3; i++) ((u16 *) (dev->dev_addr))[i] = le16_to_cpu (read_eeprom (ioaddr, i + 7, addr_len)); read_eeprom的实现可直接查阅8139too.c源代码,这里不列出了。其基本的实现原理是通过location | (EE_READ_CMD << addr_len)形成一个完整的读命令,其中EE_READ_CMD是5。然后通过操作网卡设备内存的Cfg9346位置完成一个读操作,每次读16 位。addr_len在不同版本的设备上是不同的,所以需要先读location=0的位置上的16位值,如果是0x8129,则addr_len是8,否则是6。然后通过对location为7,8,9的位置的读取,形成一个完整的MAC地址。 函数le16_to_cpu完成一个字节序的转换,把PCI设备的固有字节序(始终是小端先序的)转换成CPU字节序。 现在再来看看ifconfig的输出: eth0 Link encap:Ethernet HWaddr 00:02:3F:AC:41:9D inet addr:192.168.0.100 Bcast:192.168.0.255 Mask:255.255.255.0 inet6 addr: fe80::202:3fff:feac:419d/ Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:43 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) Base address:0x4800 OK,现在可以了。 7. 中断处理初步 大多数硬件接口通过中断处理例程来控制。接口在两种可能的事件下中断处理器:新数据包到达,或者外发数据包的传输已经完成。网络接口还能产生中断以通知错误的产生、连接状态等等情况。 要捕获并处理中断,仅仅向硬件设备写入是不够的,还必须要在系统中安装一个软件处理例程。如果没有通知Linux内核等待用户的中断,那么内核只会简单应答并忽略该中断。内核模块在使用中断前要先请求一个中断通道,然后在使用后释放该通道,下面是请求和释放函数的原型: int request_irq( unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long flags, const char *dev_name, void *dev_id ); void free_irq( unsigned int irq, void *dev_id); 在8139too网卡的设备驱动的初始化过程中,我们在rtl8139_open函数(这是网络设备注册成功后,即被调用的初始化函数)中加入以下语句: int retval; retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev); if (retval) return retval; dev->irq为要申请的中断号,这是我们从PCI设备接口中直接获得的;rtl8139_interrupt为要安装的中断处理例程; SA_SHIRQ是一个标志,表示中断可以在设备之间共享;dev->name是用来在/proc/interrupts中显示中断的拥有者; dev表示本设备用于共享的中断信号线。看一下/proc/interrupts的内容: CPU0 0: 2127786 XT-PIC timer 1: 13977 XT-PIC i8042 2: 0 XT-PIC cascade 7: 4 XT-PIC parport0 8: 1 XT-PIC rtc 9: 11965 XT-PIC acpi, uhci_hcd:usb2 10: 122101 XT-PIC Intel 82801CA-ICH3, Intel 82801CA-ICH3 Modem, uhci_hcd:usb1, yenta, yenta, ohci1394, nvidia 12: 110 XT-PIC i8042 14: 23729 XT-PIC ide0 15: 18471 XT-PIC ide1 NMI: 0 ERR: 0 10号中断线的共享使用率有点高,但是没有看到我们的eth0设备,对了,还没有insmod我们的网卡驱动,insmod后: CPU0 0: 2157753 XT-PIC timer 1: 14051 XT-PIC i8042 2: 0 XT-PIC cascade 7: 4 XT-PIC parport0 8: 1 XT-PIC rtc 9: 13359 XT-PIC acpi, uhci_hcd:usb2 10: 123908 XT-PIC Intel 82801CA-ICH3, Intel 82801CA-ICH3 Modem, uhci_hcd:usb1, yenta, yenta, ohci1394, nvidia, eth0 12: 110 XT-PIC i8042 14: 23875 XT-PIC ide0 15: 18741 XT-PIC ide1 NMI: 0 ERR: 0 现在有了。然后不要忘了,在一个内核模块中,注册与注销,分配与释放必须成对出现,在rtl8139_close函数中: free_irq (dev->irq, dev); 下面实现我们的中断处理例程,这里只是简单地在系统log里打印一点信息,然后返回说已经处理了: static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance, struct pt_regs *regs) { printk(KERN_INFO \"interrupt!\\n\"); return IRQ_HANDLED; } 这个中断到底有没有效果?下面是insmod后,系统日志中的一段,能说明问题: Mar 18 20:59:33 localhost kernel: interrupt! Mar 18 20:59:34 localhost last message repeated 59 times Mar 18 20:59:34 localhost kernel: get stats! Mar 18 20:59:34 localhost kernel: interrupt! Mar 18 20:59:35 localhost last message repeated 62 times Mar 18 20:59:35 localhost kernel: get stats! Mar 18 20:59:35 localhost kernel: interrupt! Mar 18 20:59:36 localhost last message repeated 65 times Mar 18 20:59:36 localhost kernel: get stats! Mar 18 20:59:36 localhost kernel: interrupt! Mar 18 20:59:37 localhost last message repeated 58 times Mar 18 20:59:37 localhost kernel: get stats! 8. 一个简化的pcnet32驱动初始化程序 从8139too的驱动转换到pcnet32驱动程序,重新开始PCI的初始化过程是一件比较枯燥的事情。由于pcnet32驱动程序提供对非PCI设备的支持,同时,它需要确定当前系统中的网卡的版本号。而我的调试过程,是在一块已确定版本的网卡(AMD 97C970A)上进行的,所以删去了原驱动程序的非PCI支持部分和对版本号的探测部分。经简化的驱动程序可以在vmwre上正常insmod和 rmmod。源代码如下: /* pcnet32.h * helinqiang@hotmail.com * 2006-04-06 */ #ifndef PCNET32_H_INCLUDED #define PCNET32_H_INCLUDED #include #define DRV_NAME \"pcnet32\" #define DRV_VERSION \"1.30j\" #define DRV_RELDATE \"29.04.2005\" #define PFX DRV_NAME \": \" static const char *version = DRV_NAME \".c:v\" DRV_VERSION \" \" DRV_RELDATE \" tsbogend@alpha.franken.de\\n\"; #define PCNET32_WIO_RDP 0x10 #define PCNET32_WIO_RAP 0x12 #define PCNET32_WIO_RESET 0x14 #define PCNET32_WIO_BDP 0x16 #define PCNET32_DWIO_RDP 0x10 #define PCNET32_DWIO_RAP 0x14 #define PCNET32_DWIO_RESET 0x18 #define PCNET32_DWIO_BDP 0x1c #define PCNET32_TOTAL_SIZE 0x20 struct pcnet32_access{ u16 (*read_csr)(unsigned long, int); void (*write_csr)(unsigned long, int, u16); u16 (*read_bcr)(unsigned long, int); void (*write_bcr)(unsigned long, int, u16); u16 (*read_rap)(unsigned long); void (*write_rap)(unsigned long, u16); void (*reset)(unsigned long); }; static u16 pcnet32_wio_read_csr( unsigned long addr, int index ) { outw( index, addr + PCNET32_WIO_RAP ); return inw( addr + PCNET32_WIO_RDP ); } static void pcnet32_wio_write_csr( unsigned long addr, int index, u16 val ) { outw( index, addr + PCNET32_WIO_RAP ); outw( val, addr + PCNET32_WIO_RDP ); } static u16 pcnet32_wio_read_bcr( unsigned long addr, int index ) { outw( index, addr + PCNET32_WIO_RAP ); return inw( addr + PCNET32_WIO_BDP ); } static void pcnet32_wio_write_bcr( unsigned long addr, int index, u16 val ) { outw( index, addr + PCNET32_WIO_RAP ); outw( val, addr + PCNET32_WIO_BDP ); } static u16 pcnet32_wio_read_rap( unsigned long addr ) { return inw( addr + PCNET32_WIO_RAP ); } static void pcnet32_wio_write_rap( unsigned long addr, u16 val ) { outw( val, addr + PCNET32_WIO_RAP ); } static void pcnet32_wio_reset( unsigned long addr ) { inw( addr + PCNET32_WIO_RESET ); } static int pcnet32_wio_check( unsigned long addr ) { outw( 88, addr + PCNET32_WIO_RAP ); return ( inw(addr + PCNET32_WIO_RAP) == 88 ); } static struct pcnet32_access pcnet32_wio = { .read_csr = pcnet32_wio_read_csr, .write_csr = pcnet32_wio_write_csr, .read_bcr = pcnet32_wio_read_bcr, .write_bcr = pcnet32_wio_write_bcr, .read_rap = pcnet32_wio_read_rap, .write_rap = pcnet32_wio_write_rap, .reset = pcnet32_wio_reset }; static u16 pcnet32_dwio_read_csr( unsigned long addr, int index ) { outl( index, addr + PCNET32_DWIO_RAP ); return ( inl(addr + PCNET32_DWIO_RDP) & 0xffff ); } static void pcnet32_dwio_write_csr( unsigned long addr, int index, u16 val ) { outl( index, addr + PCNET32_DWIO_RAP ); outl( val, addr + PCNET32_DWIO_RDP ); } static u16 pcnet32_dwio_read_bcr( unsigned long addr, int index ) { outl( index, addr + PCNET32_DWIO_RAP ); return( inl(addr + PCNET32_DWIO_BDP) & 0xffff); } static void pcnet32_dwio_write_bcr( unsigned long addr, int index, u16 val ) { outl( index, addr + PCNET32_DWIO_RAP ); outl( val, addr + PCNET32_DWIO_BDP ); } static u16 pcnet32_dwio_read_rap( unsigned long addr ) { return ( inl(addr + PCNET32_DWIO_RAP) & 0xffff ); } static void pcnet32_dwio_write_rap( unsigned long addr, u16 val ) { outl( val, addr + PCNET32_DWIO_RAP ); } static void pcnet32_dwio_reset( unsigned long addr ) { inl( addr + PCNET32_DWIO_RESET ); } static int pcnet32_dwio_check( unsigned long addr ) { outl( 88, addr + PCNET32_DWIO_RAP ); return ( (inl(addr + PCNET32_DWIO_RAP) & 0xffff ) == 88 ); } static struct pcnet32_access pcnet32_dwio = { .read_csr = pcnet32_dwio_read_csr, .write_csr = pcnet32_dwio_write_csr, .read_bcr = pcnet32_dwio_read_bcr, .write_bcr = pcnet32_dwio_write_bcr, .read_rap = pcnet32_dwio_read_rap, .write_rap = pcnet32_dwio_write_rap, .reset = pcnet32_dwio_reset }; #define PCNET32_DMA_MASK 0xffffffff #define PCNET32_MSG_DEFAULT (NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK) static int __devinit pcnet32_probe_pci(struct pci_dev *pdev, const struct pci_device_id *ent); static void __devexit pcnet32_remove_one(struct pci_dev *pdev); static int __devinit pcnet32_probe1( unsigned long ioaddr, int shared, struct pci_dev *pdev ); static int pcnet32_open( struct net_device * ); static int pcnet32_start_xmit( struct sk_buff *, struct net_device * ); static struct net_device_stats *pcnet32_get_stats(struct net_device * ); struct pcnet32_private{ //struct pcnet32_rx_head rx_ring[RX_RING_SIZE]; //struct pcnet32_tx_head tx_ring[TX_RING_SIZE]; //struct pcnet32_init_block init_block; dma_addr_t dma_addr; struct pci_dev *pci_dev; const char *name; //struct sk_buff *tx_skbuff[TX_RING_SIZE]; //struct sk_buff *rx_skbuff[RX_RING_SIZE]; //dma_addr_t tx_dma_addr[TX_RING_SIZE]; //dma_addr_t rx_dma_addr[RX_RING_SIZE]; struct pcnet32_access a; spinlock_t lock; unsigned int cur_rx, cur_tx; unsigned int dirty_rx, dirty_tx; struct net_device_stats stats; char tx_full; int options; unsigned int shared_irq:1, dxsuflo:1, mii:1; struct net_device *next; //struct mii_if_info mii_if; //struct timer_list watchdog_timer; //struct timer_list blink_timer; u32 msg_enable; }; #endif //PCNET32_H_INCLUDED /* pcnet32.c * helinqiang@hotmail.com * 2006-04-06 */ #include #include #include \"pcnet32.h\" static int cards_found = 0; static int pcnet32_debug = 0; static int tx_start = 1; static int debug = -1; static int tx_start_pt = -1; static struct pci_device_id pcnet32_pci_tbl[] = { { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_LANCE_HOME, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_LANCE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, { PCI_VENDOR_ID_TRIDENT, PCI_DEVICE_ID_AMD_LANCE, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_NETWORK_ETHERNET<<8, 0xffff00, 0 }, { 0, } }; static struct pci_driver pcnet32_driver = { .name = DRV_NAME, .probe = pcnet32_probe_pci, .remove = __devexit_p(pcnet32_remove_one), .id_table = pcnet32_pci_tbl, }; static int __devinit pcnet32_probe_pci(struct pci_dev *pdev, const struct pci_device_id *ent) { unsigned long ioaddr; int err; err = pci_enable_device( pdev ); if( err < 0 ){ if( pcnet32_debug & NETIF_MSG_PROBE ) printk(KERN_ERR PFX \"failed to enable device --err=%d\\n\ return err; } pci_set_master( pdev ); ioaddr = pci_resource_start(pdev, 0); if( !ioaddr ){ if( pcnet32_debug & NETIF_MSG_PROBE ) printk(KERN_ERR PFX \"card has no PCI IO resource, aborting\\n\"); return -ENODEV; } if( !pci_dma_supported(pdev, PCNET32_DMA_MASK) ){ if( pcnet32_debug & NETIF_MSG_PROBE ) printk(KERN_ERR PFX \"architecture dos not\" \"support 32bit PCI busmaster DMA\\n\"); return -ENODEV; } if( request_region(ioaddr, PCNET32_TOTAL_SIZE, \"pcnet32_probe_pci\") == NULL ){ if( pcnet32_debug & NETIF_MSG_PROBE ) printk(KERN_ERR PFX \"io address range already allocated\\n\"); return -EBUSY; } err = pcnet32_probe1( ioaddr, 1, pdev ); if( err < 0 ) pci_disable_device( pdev ); return err; } static int __devinit pcnet32_probe1( unsigned long ioaddr, int shared, struct pci_dev *pdev ) { struct pcnet32_private *lp; dma_addr_t lp_dma_addr; struct pcnet32_access *a = NULL; int i; int fdx, mii, fset, dxsuflo; int chip_version; struct net_device *dev; char *chipname; u8 promaddr[6]; int ret = -ENODEV; pcnet32_wio_reset( ioaddr ); if( pcnet32_wio_read_csr(ioaddr, 0)==4 && pcnet32_wio_check(ioaddr) ) a = &pcnet32_wio; else{ pcnet32_dwio_reset( ioaddr ); if( pcnet32_dwio_read_csr(ioaddr, 0)==4 && pcnet32_dwio_check(ioaddr) ) a = &pcnet32_dwio; else goto err_release_region; } chip_version = a->read_csr(ioaddr, 88) | (a->read_csr(ioaddr, ) << 16); printk(KERN_INFO \" PCnet chip version is %#x.\\n\chip_version); fdx = mii = fset = dxsuflo = 0; chip_version = ( chip_version >> 12 ) & 0xffff; chipname = \"PCnet/PCI II 79C970A\"; fdx = 1; dev = alloc_etherdev(0); if(!dev){ if( pcnet32_debug & NETIF_MSG_PROBE ) printk(KERN_ERR PFX \"Memory allocation failed.\\n\"); ret = -ENOMEM; goto err_release_region; } SET_NETDEV_DEV( dev, &pdev->dev ); for( i = 0; i < 3; i ++ ){ unsigned int val; val = a->read_csr( ioaddr, i+12 ) & 0xffff; dev->dev_addr[2*i] = val &0x0ff; dev->dev_addr[2*i+1] = (val >> 8) & 0x0ff; } for( i = 0; i < 6; i ++ ) promaddr[i] = inb( ioaddr + i ); if( pcnet32_debug & NETIF_MSG_PROBE ) printk( KERN_INFO PFX \"%s at %#3lx,\chipname, ioaddr ); if( pcnet32_debug & NETIF_MSG_PROBE ) for( i = 0; i < 6; i ++ ) printk( KERN_INFO \" %2.2x\ dev->base_addr = ioaddr; if( (lp = pci_alloc_consistent(pdev, sizeof(*lp), &lp_dma_addr)) == NULL ){ if( pcnet32_debug & NETIF_MSG_PROBE ) printk(KERN_ERR PFX \"Consistent memory allocation failed.\\n\"); ret = -ENOMEM; goto err_free_netdev; } memset( lp, 0, sizeof(*lp) ); lp->dma_addr = lp_dma_addr; lp->pci_dev = pdev; spin_lock_init( &lp->lock ); SET_MODULE_OWNER( dev ); SET_NETDEV_DEV( dev, &pdev->dev ); dev->priv = lp; lp->name = chipname; lp->shared_irq = shared; lp->dxsuflo = dxsuflo; lp->mii = mii; lp->msg_enable = pcnet32_debug; lp->a = *a; dev->irq = pdev->irq; if( pcnet32_debug & NETIF_MSG_PROBE ) printk(\" assigned IRQ %d. \\n\ dev->open = &pcnet32_open; dev->hard_start_xmit = &pcnet32_start_xmit; //dev->stop = &pcnet32_close; dev->get_stats = &pcnet32_get_stats; //dev->set_multicast_list = &pcnet32_set_multicast_list; //dev->do_ioctl = &pcnet32_ioctl; //dev->ethtool_ops = &pcnet32_ethtool_ops; //dev->tx_timeout = pcnet32_tx_timeout; //dev->watchdog_timeo = (5*HZ); register_netdev( dev ); pci_set_drvdata( pdev, dev ); if( pcnet32_debug & NETIF_MSG_PROBE ) printk(KERN_INFO \"%s: registered as %s\\n\lp->name ); cards_found ++; return 0; err_free_netdev: free_netdev( dev ); err_release_region: release_region( ioaddr, PCNET32_TOTAL_SIZE ); return ret; } static void __devexit pcnet32_remove_one(struct pci_dev *pdev) { struct net_device *dev = pci_get_drvdata(pdev); struct pcnet32_private *lp = dev->priv; unregister_netdev( dev ); release_region( dev->base_addr, PCNET32_TOTAL_SIZE ); pci_free_consistent( lp->pci_dev, sizeof(*lp), lp, lp->dma_addr ); free_netdev( dev ); pci_disable_device( pdev ); pci_set_drvdata(pdev, NULL ); } static int pcnet32_open( struct net_device *dev ) { return 0; } static int pcnet32_start_xmit( struct sk_buff *skb, struct net_device *dev ) { struct pcnet32_private *lp = dev->priv; dev_kfree_skb( skb ); lp->stats.tx_dropped ++; return 0; } static struct net_device_stats *pcnet32_get_stats( struct net_device *dev ) { struct pcnet32_private *lp = dev->priv; return &lp->stats; } static int __init pcnet32_init_module(void) { printk(KERN_INFO \"%s\ pcnet32_debug = netif_msg_init(debug, PCNET32_MSG_DEFAULT); if( (tx_start_pt >= 0) && (tx_start_pt <= 3) ) tx_start= tx_start_pt; return pci_module_init(&pcnet32_driver); } static void __exit pcnet32_cleanup_module(void) { pci_unregister_driver(&pcnet32_driver); } module_init(pcnet32_init_module); module_exit(pcnet32_cleanup_module); 9. AMD 79C970A硬件的初始化过程 PCNET32网卡驱动程序的初始化过程,包含了以下内容的初始化: PCI设备初始化(我们忽略非PCI设备的情况)。这是一个一般性的步骤,代码对所有PCI设备均适用(只有设备相关的一些微小差异),通过PCI设备的初始化步骤,我们得到了79C970A网卡的I/O地址范围是:0x1400-0x147f, 并申请保留0x1400开始的32字节的I/O地址空间,以后,我们对设备的操作都通过这32字节的I/O地址范围。 以太网网络设备的初始化,这也是一个一般性的步骤,具有一定的通用性和普遍性,甚至内核已经为以太网的初始化提供一些通用函数供使用。以太网网络设备的初始化主要工作是把描述设备的结构struct net_device注册到一个内核空间的全局链表中,并为其注册各种操作函数。struct net_device中的priv指针需要重点关注,它是一个私有数据指针,各个设备间可能存在着较大的差异。 硬件的初始化。在我的试验环境中,是指对AMD 79C970A芯片的初始化,通过操作芯片的I/O地址实现,完成硬件级的设备复位,启用,禁止,中断等各种操作。 以上三部分内容的初始化在PCNET32驱动程序中并没有明显的界线,很多代码都是在一起的。下面主要描述一下AMD 79C970A芯片的初始化过程。PCI设备的初始化不再赘述,PCNET32网卡设备的priv指针另外专门描述。 硬件级的初始化过程,开始于pcnet32_probe1函数,这时,PCI设备相关的初始化工作已基本完成。第一步工作便是复位硬件,并确定I/O端口的宽度(16位还是32位),下面是相关代码: /* reset the chip */ pcnet32_wio_reset(ioaddr); /* NOTE: 16-bit check is first, otherwise some older PCnet chips fail */ if (pcnet32_wio_read_csr(ioaddr, 0) == 4 && pcnet32_wio_check(ioaddr)){ a = &pcnet32_wio; }else{ pcnet32_dwio_reset(ioaddr); if (pcnet32_dwio_read_csr(ioaddr, 0) == 4 && pcnet32_dwio_check(ioaddr)){ a = &pcnet32_dwio; }else goto err_release_region; } reset操作就是从端口0x1414(16位宽度的端口)或者端口0x1418(32位宽度的端口)读取相应宽度的数据。代码先尝试16位宽度的端口,如果失败,则尝试32位宽度的端口, 接下来读取芯片的版本: chip_version = a->read_csr(ioaddr, 88) | (a->read_csr(ioaddr,) << 16); 版本总共28位,低12位固定为0x003,否则为不可识别版本,79C970A的版本为: 0x2621003。 接下来读取网卡的MAC地址: for (i = 0; i < 3; i++) { unsigned int val; val = a->read_csr(ioaddr, i+12) & 0x0ffff; /* There may be endianness issues here. */ dev->dev_addr[2*i] = val & 0x0ff; dev->dev_addr[2*i+1] = (val >> 8) & 0x0ff; } 将网卡切换到32位模式: a->write_bcr(ioaddr, 20, 2); 将私有数据中的init_blok的地址存入硬件中: a->write_csr(ioaddr, 1, (lp->dma_addr + offsetof(struct pcnet32_private, init_block)) & 0xffff); a->write_csr(ioaddr, 2, (lp->dma_addr + offsetof(struct pcnet32_private, init_block)) >> 16); 在pcnet32_probe1函数的最后: /* enable LED writes */ a->write_bcr(ioaddr, 2, a->read_bcr(ioaddr, 2) | 0x1000); 接下来,在pcnet32_open函数中,上述的很多操作被重新执行了,并最终初始化完毕,启动硬件,这里不再详细描述。 10. PCNET32驱动程序的pcnet32_private结构 一个网络设备,在其驱动程序中用一个net_device结构描述,该结构有一个重要的成员priv,它是一个指针,指向驱动程序自己定义的私有数据。不同的网络设备驱动程序,其私有数据各不相同。下面是PCNET32驱动程序的私有数据: struct pcnet32_private { struct pcnet32_rx_head rx_ring[RX_RING_SIZE]; struct pcnet32_tx_head tx_ring[TX_RING_SIZE]; struct pcnet32_init_block init_block; dma_addr_t dma_addr; struct pci_dev *pci_dev; const char *name; struct sk_buff *tx_skbuff[TX_RING_SIZE]; struct sk_buff *rx_skbuff[RX_RING_SIZE]; dma_addr_t tx_dma_addr[TX_RING_SIZE]; dma_addr_t rx_dma_addr[RX_RING_SIZE]; struct pcnet32_access a; spinlock_t lock; unsigned int cur_rx, cur_tx; unsigned int dirty_rx, dirty_tx; struct net_device_stats stats; char tx_full; int options; unsigned int dxsuflo:1, mii:1; struct net_device *next; struct mii_if_info mii_if; struct timer_list watchdog_timer; struct timer_list blink_timer; }; 下面逐个分析该结构的重要成员。 tx_skbuff和rx_skbuff。这是两个sk_buff的数组,其长度分别为TX_RING_SIZE(16)和RX_RING_SIZE (32)。sk_buff是一个套接字缓冲区,在Linux内核中处于网络子系统的核心地位,由它保存发送和接收的数据,关于该结构的详细描述参阅《Linux设备驱动程序》第三版512页。其中tx_skbuff是发送缓冲区数组,总共有16个,而rx_skbuff是接收缓冲区数组,总共有32 项。 tx_dma_addr和rx_dma_addr。对于分配好的sk_buff缓冲区,我们都要把它们映射成为DMA缓冲区,这两个数组用于记录相应的DMA缓冲区的地址。 rx_ring和tx_ring。这是pcnet32_rx_head和pcnet32_tx_head的结构体,它们用于记录对应的sk_buff的当前状态,可以称为描述符。这两个结构体的定义如下: struct pcnet32_rx_head { u32 base; s16 buf_length; s16 status; u32 msg_length; u32 reserved; }; struct pcnet32_tx_head { u32 base; s16 length; s16 status; u32 misc; u32 reserved; }; 对于tx_skbuff和rx_skbuff数组,在使用逻辑上是呈环形的,即用完最后1个后,重新使用第1个,所以,在源代码中会看到很多的ring命名。下面是对rx ring和tx ring的初始化函数: static int pcnet32_init_ring(struct net_device *dev) { struct pcnet32_private *lp = dev->priv; int i; lp->tx_full = 0; lp->cur_rx = lp->cur_tx = 0; lp->dirty_rx = lp->dirty_tx = 0; for (i = 0; i < RX_RING_SIZE; i++) { struct sk_buff *rx_skbuff = lp->rx_skbuff[i]; if (rx_skbuff == NULL) { if (!(rx_skbuff = lp->rx_skbuff[i] = dev_alloc_skb (PKT_BUF_SZ))) { printk(KERN_ERR \"%s: pcnet32_init_ring dev_alloc_skb failed.\\n\ dev->name); return -1; } skb_reserve (rx_skbuff, 2); } rmb(); if (lp->rx_dma_addr[i] == 0) lp->rx_dma_addr[i] = pci_map_single(lp->pci_dev, rx_skbuff->data, PKT_BUF_SZ-2, PCI_DMA_FROMDEVICE); lp->rx_ring[i].base = (u32)le32_to_cpu(lp->rx_dma_addr[i]); lp->rx_ring[i].buf_length = le16_to_cpu(2-PKT_BUF_SZ); wmb(); /* Make sure owner changes after all others are visible */ lp->rx_ring[i].status = le16_to_cpu(0x8000); } for (i = 0; i < TX_RING_SIZE; i++) { lp->tx_ring[i].status = 0; /* CPU owns buffer */ wmb(); /* Make sure adapter sees owner change */ lp->tx_ring[i].base = 0; lp->tx_dma_addr[i] = 0; } lp->init_block.tlen_rlen = le16_to_cpu(TX_RING_LEN_BITS | RX_RING_LEN_BITS); for (i = 0; i < 6; i++) lp->init_block.phys_addr[i] = dev->dev_addr[i]; lp->init_block.rx_ring = (u32)le32_to_cpu(lp->dma_addr + offsetof(struct pcnet32_private, rx_ring)); lp->init_block.tx_ring = (u32)le32_to_cpu(lp->dma_addr + offsetof(struct pcnet32_private, tx_ring)); wmb(); /* Make sure all changes are visible */ return 0; } 该初始化函数中,对接收缓冲区数组,都是预先分配好,并置好了描述符的相应状态。而对于发送缓冲区,并没有分配,只是初始化了描述符,等到使用时再进行分配。 该初始化函数中,还有一些pcnet32_private中的成员,我们还未描述到,下面一一描述。 cur_rx,cur_tx,dirty_rx和dirty_tx。这是四个关于套接字缓冲区的统计变量,记录当前缓冲区的使用情况。 init_block。这是一个pcnet32_init_block的结构,该结构定义如下: struct pcnet32_init_block { u16 mode; u16 tlen_rlen; u8 phys_addr[6]; u16 reserved; u32 filter[2]; /* Receive and transmit ring base, along with extra bits. */ u32 rx_ring; u32 tx_ring; }; phys_addr记录mac地址,rx_ring和tx_ring记录缓冲区数组的首地址。 其它几个成员,在用到时再具体描述。 11. 让网卡具备数据发送功能 先给出源代码,明天开始再进行代码分析。这是两个函数,分别是中断处理和数据发送: static irqreturn_t pcnet32_interrupt( int irq, void *dev_id, struct pt_regs *regs ) { struct net_device *dev = dev_id; struct pcnet32_private *lp; unsigned long ioaddr; u16 csr0, rap; int boguscnt = max_interrupt_work; int must_restart; if( !dev ){ printk(KERN_DEBUG \"%s(): irq %d for unknown device\\n\ __FUNCTION__, irq ); return IRQ_NONE; } ioaddr = dev->base_addr; lp = dev->priv; spin_lock( &lp->lock ); rap = lp->a.read_rap( ioaddr ); while( (csr0 = lp->a.read_csr(ioaddr, 0)) & 0x8f00 && --boguscnt >= 0 ){ if( csr0 == 0xffff ) break; } lp->a.write_csr( ioaddr, 0, csr0 & ~0x004f ); must_restart = 0; printk(KERN_INFO \"%s: interrupt csr0=%#2.2x new csr=%#2.2x.\\n\ dev->name, csr0, lp->a.read_csr( ioaddr, 0 ) ); if( csr0 & 0x0200 ){ unsigned int dirty_tx = lp->dirty_tx; int delta; while( dirty_tx != lp->cur_tx ){ int entry = dirty_tx & TX_RING_MOD_MASK; int status = (short)le16_to_cpu( lp->tx_ring[entry].status ); if( status < 0 ) break; lp->tx_ring[entry].base = 0; if( status & 0x4000 ){ int err_status = le32_to_cpu( lp->tx_ring[entry].misc ); lp->stats.tx_errors++; printk(KERN_ERR \"%s: Tx error status=%04x err status=%08x\\n\ dev->name, status, err_status ); if( err_status & 0x04000000 ) lp->stats.tx_aborted_errors++; if( err_status & 0x08000000 ) lp->stats.tx_carrier_errors++; if( err_status & 0x10000000 ) lp->stats.tx_window_errors++; if( err_status & 0x40000000 ) lp->stats.tx_fifo_errors++; if( !lp->dxsuflo ){ printk( KERN_ERR, \"%s: Tx FIFO error! CSR0=%4.4x\\n\ dev->name, csr0 ); must_restart = 1; } }else{ if( status & 0x1800 ) lp->stats.collisions++; lp->stats.tx_packets++; } if( lp->tx_skbuff[entry] ){ pci_unmap_single( lp->pci_dev, lp->tx_dma_addr[entry], lp->tx_skbuff[entry]->len, PCI_DMA_TODEVICE); dev_kfree_skb_irq( lp->tx_skbuff[entry] ); lp->tx_skbuff[entry] = NULL; lp->tx_dma_addr[entry] = 0; } dirty_tx ++; } delta = (lp->cur_tx - dirty_tx)&(TX_RING_MOD_MASK + TX_RING_SIZE); if( delta > TX_RING_SIZE ){ printk( KERN_ERR \"%s: out-of_svnc dirty pointer. %d vs. %d.\" \"full=%d.\\n\ dev->name, dirty_tx, lp->cur_tx, lp->tx_full ); dirty_tx += TX_RING_SIZE; delta -= TX_RING_SIZE; } if( lp->tx_full && netif_queue_stopped(dev) && delta < TX_RING_SIZE -2 ){ lp->tx_full = 0; netif_wake_queue(dev); } lp->dirty_tx = dirty_tx; } lp->a.write_csr( ioaddr, 0, 0x0040 ); lp->a.write_rap( ioaddr, rap ); printk(KERN_INFO \"%s: exiting interrupt, csr0=%#4.4x.\\n\ dev->name, lp->a.read_csr( ioaddr, 0 ) ); spin_unlock( &lp->lock ); return IRQ_HANDLED; } static int pcnet32_start_xmit( struct sk_buff *skb, struct net_device *dev ) { struct pcnet32_private *lp = dev->priv; unsigned long ioaddr = dev->base_addr; u16 status; int entry; unsigned long flags; spin_lock_irqsave( &lp->lock, flags ); status = 0x8300; entry = lp->cur_tx & TX_RING_MOD_MASK; printk(KERN_INFO \"entry: %d\\n\ lp->tx_ring[entry].length = le16_to_cpu( -skb->len ); lp->tx_ring[entry].misc = 0x00000000; lp->tx_skbuff[entry] = skb; lp->tx_dma_addr[entry] = pci_map_single( lp->pci_dev, skb->data, skb->len, PCI_DMA_FROMDEVICE ); lp->tx_ring[entry].base = (u32)le32_to_cpu( lp->tx_dma_addr[entry] ); wmb(); lp->tx_ring[entry].status = le16_to_cpu( status ); lp->cur_tx++; lp->stats.tx_bytes += skb->len; lp->a.write_csr( ioaddr, 0, 0x0048 ); dev->trans_start = jiffies; if( lp->tx_ring[(entry+1) & TX_RING_MOD_MASK].base != 0 ){ lp->tx_full = 1; netif_stop_queue( dev ); } spin_unlock_irqrestore( &lp->lock, flags ); return 0; } 12. 网卡数据发送的基本流程 发送函数把数据准备好(放在私有数据结构中的特定变量中),并触发硬件发送。 1、从发送缓冲区环中选择一个空的缓冲区,包括tx_ring, tx_skbuff, tx_dma_addr。 2、设置tx_ring的成员,length设为待发送数据长度取反,misc设零,base设为tx_dma_addr,status设为缺省的0x8300。 3、tx_skbuff指向待发送数据套接字缓冲区skb。 4、将skb映射成的dma地址存放在tx_dma_addr中。 5、cur_tx ++; 6、写网卡硬件,触发一个立即发送的信号。 7、检查环是否已用完,如果是,暂停后续发送。 中断处理函数中,其主要工作是把已发送过的缓冲区回收,使其重新可用。 1、如果dirty_tx < cur_tx,则说明有已送完的缓冲区还没回收掉,循环处理。 2、检查tx_ring的成员status,如果<0 不用处理,退出。 3、如果(status & 0x4000 != 0),则发送出错,处理处理。 4、回收已发送掉的缓冲区。 5、dirty_tx ++; 6、如果前面已经因为环用完,而发送被暂停,而现在已有空余缓冲区,则继续发送。 13. 网卡数据接收过程简述 当网卡设备上有新的数据到达时,硬件会产生一个中断,驱动程序处理这个中断,就可以接收数据。其接收过程大致可以分为以下几步: 1、检查rx_ring[entry].status,大于等于零,表示有数据到达,需要处理。 2、取status的高8位,如果高8位不等于0x03,表示有错误发生。 3、否则从rx_ring[entry].msg_length中可以取到数据包的长度,并判断长度是否在合理的范围内。 4、如果长度值超过一个阀值(可以通过参数设定),则取下rx_ring的接收缓冲区作为传给上层的缓冲区,同时,还给rx_ring一个空的缓冲区,如果没有超过阀值,则把rx_ring的接收缓冲区数据拷贝出来。 5、更新统计信息,把接收缓冲区传给接收层。 14. 构建以太网头 上层传给网卡驱动程序的待发送数据包是不包括以太网头的,以太网头需要在网卡驱动程序中进行构建。一个完整的以太网头包括源MAC地址,目的MAC地址,和协议类型。这里问题的关键就是:如何获得目的MAC地址?即知道了目的IP地址后,如何将这个IP地址和一个MAC地址关联起来?通常处理这个问题是通过地址解析协议(ARP)来>完成。 继续以前文的ping程序为例,主机172.16.48.2要向172.16.48.1发回显请求icmp包之前,会先向MAC地址ff:ff:ff: ff:ff:ff广播一个ARP请求包,询问172.16.48.1的MAC地址。当172.16.48.1收到这个包时,会向172.16.48.2发一个ARP应答包,告知自己的MAC地址。这样,48.2就可以为回显请求的ICMP包构建以太网头了。 然后,为了防止48.1的MAC地址会变化,每隔一定的时间,48.2都会向48.1发ARP请求包,询问48.1的MAC地址。 在网卡驱动程序中,代表网络接口的结构体struct net_device共有5个成员函数用于构建以太网头。它们分别是: int (*rebuild_header)(struct sk_buff *skb ); 该函数用来在传输数据包之前,完成ARP解析之后,重新建立硬件头。在我们的测试程序中,发现这个函数并没有被用到,设为 NULL也不影响驱动正常工作。 int (*hard_header)(... .../*省略*/); 该函数根据ARP协议得到的MAC地址构建以太网头。从测试情况来看,每次ARP应答包收到时,这个函数都会被调用,同时,在没有ARP应答包时,也会被调用几次,但不是每次发送包都被调用,有待深究。 int (*header_cache)(... .../*省略*/); 将ARP应答包中得到的结构填充hh_cache结构,从测试来看,它是作一个缓冲,每次有一个新的MAC地址得到时,将其作一个缓冲,保存下来。 int (*header_cache_update)(....../*省略*/); 目标主机的MAC址发生变化时,更新hh_cache结构,从而更新缓存。测试中没有测到这样的情况。 int (*hard_header_parse)(... .../*省略*/); 测试中没有用到,用途不明。 15.对组播的支持 对组播数据包的支持由如下几项组成:若干设备标志,一个设备函数和一个数据结构。下面分别对其进行介绍。 net_device结构体的成员flags是一个unsigned short型的数据类型,保存网络接口的一组设备标志。对于以太网网络设备,内核提供的缺省的初始化函数中,这样给flags赋值: dev->flags = IFF_BROADCAST|IFF_MULTICAST; memset(dev->broadcast,0xFF, ETH_ALEN); 即缺省状态下,以太网卡是可广播的,同时,它能够进行组播发送。并把接口的广播地 址设置为ff:ff:ff:ff:ff:ff。 相关的标志还有:IFF_ALLMULTI,这个标志告诉驱动程序检索来自网络的所有组播数据包。IFF_PROMISC,这个标志设置接口为混杂模式,使接口接收所有数据包。 一个设备函数,即函数void (*dev->set_multicast_list)(struct net_device *dev)。当与设备相关的机器地址清单发生变化时,调用这个设备函数。该函数还在dev->flags被修改时调用, pcnet32网卡驱动程序中,该函数的执行流程是: 1、如果接口当前处于混杂模式,设置私有数据的init_block的模式,使网卡不再使用硬件过滤器来过滤数据包。 2、否则,设置相应的init_block的模式,同时,载入新的组播地址列表。 3、复位网卡。 一个数据结构是struct dev_mc_list *dev->mc_list。这是与设备关联的所有组播地址形成的一个链表。这些组播地址在pcnet32网卡驱动程序中,通过pcnet32_load_multicast函数,以一定的形式把它们保存在init_block的filter中。 16. 关于网络设备 在基本完成了整个初始化过程以后,我们需要再回到网络设备上来,看看整个TCP/IP协议究竟是如果跟网络设备相关联,并最终一起完成各种复杂工作的。 在网卡驱动相关的分析中,我们提到,代表一个网络设备接口的是一个结构体struct net_device。而在my_inet模块的初始化过程中,mydevinet_init的工作是为MY_PF_INET域的工作找到可用的网络设备,并进行必要的初始化,在mydevinet_init中有这么一行代码: register_netdevice_notifier(&myip_netdev_notifier); 它是把一个结构体struct notifier_block myip_netdev_notifier注册到一个系统全局的链表netdev_chain中,结构体myip_netdev_notifier含有一个优先级,在netdev_chain中,它跟其它的notifier_block按优先级的先后次序排列,同时它有一个notifier_call的回调函数指针,在它被注册到netdev_chain中后,网络设备上有事件发生,该回调函数就会被调用到。关于linux的notify的实现原理细节,我们以后进行专门的分析。 关于网络设备会发出的通知的类型,在include/linux/notifier.h中有完整的定义: #define NETDEV_UP 0x0001 //设备开启 #define NETDEV_DOWN 0x0002 //设备关闭 #define NETDEV_REBOOT 0x0003 //告诉协议栈一个网络接口探测到硬件崩溃并重启了。 #define NETDEV_CHANGE 0x0004 //设备状态改变 #define NETDEV_REGISTER 0x0005 //设备注册 #define NETDEV_UNREGISTER 0x0006 //设备注销 #define NETDEV_CHANGEMTU 0x0007 //设备改变MTU #define NETDEV_CHANGEADDR 0x0008 //设备改变地址 #define NETDEV_GOING_DOWN 0x0009 //设备准备关闭 #define NETDEV_CHANGENAME 0x000A //设备改变名字 #define NETDEV_FEAT_CHANGE 0x000B //... ... 当我们把my_inet作为一个模块insmod到内核中时,我们马上会收到设备注册和设备开启两个通知。对于每一个已开启的网络设备, myip_netdev_notifier提供的回调函数myinetdev_event会被两次调用到。我们可以在这里完成一些我们希望做的初始化工作。 协议栈跟网络设备的关联主要是通过struct net_device结构体中的六个协议相关的指针:atalk_ptr,ip_ptr,dn_ptr,ip6_ptr,ec_ptr, ax25_ptr。我们重点关注的是ipv4的实现,所 以只处理指针ip_ptr。ip_ptr指向的是一个结构体struct in_device,其成员struct in_ifaddr ifa_list是一个IP地址列表,对于一个linux网络设备来讲,它可以拥有多大256个ip地址,每一个IP地址在in_device中的表示就是一个结构体struct in_ifaddr,所有的IP地址以一个链表的形式组织在一起。下面是struct in_ifaddr结构体的定义: struct in_ifaddr { struct in_ifaddr *ifa_next; struct in_device *ifa_dev; struct rcu_head rcu_head; u32 ifa_local; u32 ifa_address; u32 ifa_mask; u32 ifa_broadcast; u32 ifa_anycast; unsigned char ifa_scope; unsigned char ifa_flags; unsigned char ifa_prefixlen; char ifa_label[IFNAMSIZ]; }; ifa_next是链表指针,ifa_dev指向它所在的in_device。ifa_local跟ifa_address都是IP地址,具体区别目前还不清楚,ifa_mask是子网掩码,ifa_broadcast是广播地址,ifa_anycast是raw协议相关的一个地址,具体不明。 ifa_scope的取值如下: enum rt_scope_t { RT_SCOPE_UNIVERSE=0, /* User defined values */ RT_SCOPE_SITE=200, RT_SCOPE_LINK=253, RT_SCOPE_HOST=2, RT_SCOPE_NOWHERE=255 }; 实际上,这个成员并非如它的字面意思所表示的scope,它是按到目的端距离进行的一种分类,nowhere表示不存在的目的端,host表示目的端是本机(回环地址),link是直连的一个目的端,site表示在一个本地封闭系统中的内部路由,而universe是除此之外的其它任何情况。 ifa_flags的取值范围如下: #define IFA_F_SECONDARY 0x01 //从属设备。 #define IFA_F_TEMPORARY IFA_F_SECONDARY //临时设备。 #define IFA_F_DEPRECATED 0x20 #define IFA_F_TENTATIVE 0x40 #define IFA_F_PERMANENT 0x80 //永久设备。 ifa_prefixlen是子网掩码的网络号的长度,ifa_label是设备和设备名,比如eth0, eth1等。 17. 环回设备接口(loopback)上如何发送和接收数据报 Linux内核用结构体struct net_device表示一个网络设备接口,该结构体的成员hard_start_xmit是一个函数指针,用于完成数据报在网络上的发送工作,其原型是: int (*hard_start_xmit)( struct sk_buff *skb, struct net_device *dev ); skb是待发送的数据缓冲区,dev是该网络设备接口本身的一个指针。环回设备接口由于是把数据报发给本机,所以其发送数据报函数比较特殊,它把skb稍加处理后,又转回给协议栈的数据报接收函数netif_rx。其发送函数的函数名是loopback_xmit。 首先,loopback_xmit调用skb_orphan把skb孤立,使它跟发送socket和协议栈不再有任何联系,也即对本机来说,这个skb的数据内容已经发送出去了,而skb相当于已经被释放掉了。skb_orphan所做的实际事情是,首先从skb->sk(发送这个skb的那个socket)的sk_wmem_alloc减去skb->truesize,也即从socket的已提交发送队列的字节数中减去这个skb,表示这个skb已经发送出去了,同时,如果有进程在这个socket上写等待,则唤醒这些进程继续发送数据报,然后把socket的引用计数减1,最后,令sk->destructor和skb->sk都为NULL,使skb完全孤立。实际上,对于环回设备接口来说,数据的发送工作至此已经全部完成,接下来,只要把这个实际上还未被释放的skb传回给协议栈的接收函数即可。 在传回去之前,还需要做一些工作,因为协议栈在发送数据报时,为数据报打以太网首部时,skb->data是指向以太网首部的开始位置的,网络设备接口传回的数据报是需要已经剥离了以太网首部的,所以令skb->data加上 ETH_HLEN(14),令skb->mac.raw指向原来data的位置,即以太网首部。然后,需要确定skb->pkt_type的值,即该数据报的类型,如果以太网首部中的目的mac地址是个组播或广播地址,则skb->pkt_type为PACKET_BROADCAST或 PACKET_MULTICAST,关于MAC地址类型的判断方法以后再详细分析。否则,如果目的mac地址不等于本接口设备的mac地址,则skb->pkt_type为PACKET_OTHERHOST,否则就为默认值PACKET_HOST。最后,设置 skb->protocol(ETH_P_IP,ETH_P_ARP或其它),设置skb->dev,再更新一下统计值,把skb交回给协议协栈即可。 struct net_device还有一些跟协议栈的处理流程关系比较密切的成员函数,下面一一介绍。 hard_header,该成员被以太网设备驱动程序用于为每一个待发送数据报构建以太网首部,系统中所有以太网设备驱动程序共享一个函数即eth_header。所有数据报在传递给该函数之前,其skb头部预留了以太网首部的空间,data成员指向网络层首部,eth_header将data成员指向以太网首部,并为以太网首部填入目的以太网地址,源以太网地址和网络层协议类型(ETH_P_IP或 ETH_P_ARP),该函数在协议栈中主要有两处被用到,一是ARP模块中,发送ARP请求或应答前,构建以太网首部用;另一处是在IP数据发送过程中,构建以太网首部用。 hard_header_cache,用于创建以太网首部的高速缓冲,每一个邻居节点都有一个struct hh_cache *hh成员,用于缓冲该邻居节点的以太网首部,有了这个缓冲,以后再向这个邻居发数IP数据的时候,不必再调用hard_header构建以太网首部,而是直接从hh中拷贝即可。 18. 数据报在链路层的发送 前面讲述IP数据报的分片与重组时,已完整描述了数据报在网络层是如何被传递的,网络层发送数据报的最后一站是ip_finish_output2,它根据skb->dst->hh是否已被创建来决定如何调用链路层的输出函数,hh实际是neighbour的hh成员,它在ARP解析完成,邻居节点被更新时进行创建,对于不需要ARP解析的设备接口(loopback等),它在第一次发送数据报时被创建。所以,不管网络层如何调用链路层的输出函数,链路层的第一个输出函数始终是dev_queue_xmit。 该函数首先检查skb_shinfo(skb)->frag_list是否有值,如果有,但是网络设备接口不支持skb的碎片列表(NETIF_F_FRAGLIST),则需要把这些碎片重组到一个完整的skb中(通过函数__skb_linearize)。第二步检查skb_shinfo(skb)->nr_frags,如果不为0,表示这个skb使用了分散/聚焦IO,如果网络设备接口不支持(NETIF_F_SG),同样需要重新线性化(通过函数__skb_linearize)。 第三步检查是关于校验和的,需要注意的是这个校验和不是IP首部的首部校验和,IP首部校验和在每个IP数据报中是必需的,由软件来完成,对IP首部以16bit为段进行反码求和得到,只覆盖到IP首部,而未覆盖到IP数据。而这里的校验和是其上层协议(比如UDP)的校验和,它覆盖到上层协议的首部和数据。 struc sk_buff有一个成员ip_summed,表示校验和的执行策略,其可能的取值有三种,CHECKSUM_HW表示由硬件来执行校验和,CHECKSUM_NONE表示完全由软件来执行校验和,CHECKSUM_UNNECESSARY表示没有必要执行校验和。对于新分配的一个skb,总是默认由软件来执行校验和,如果网络设备接口拥有以下三个标志之一,并满足其它一些相关条件,就由硬件执行校验和: NETIF_F_IP_CSUM(硬件只能执行IPv4上的TCP/UDP协议的校验和),NETIF_F_NO_CSUM(硬件不需要执行校验和,比如环回设备), NETIF_F_HW_CSUM(硬件能执行所有数据报的校验和)。如果校验和由软件执行,则在ip_generic_getfrag拷贝应用数据的时候执行,计算得到的校验和存放在skb->csum,由上层协议填写自己的协议首部时填入。否则,如果校验和由硬件执行,则上层协议在填写自己的协议首部时,为skb->csum填上自己首部中校验和所处的位置,以备硬件生成校验和时可以找到这个位置填入。 dev_queue_xmit检查校验和,只是为了作一个补救措施,即:如果 skb->ip_summed==CHECKSUM_HW(由硬件执行校验和,即当前还未生成校验和),但是网络设备接口的成员features上没有标志NETIF_F_HW_CSUM,NETIF_F_NO_CSUM或NETIF_F_IP_CSUM,即网络设备接口既没有表示不需要执行校验和,也说明自己没有执行校验和的能力,或者,如果features上有NETIF_F_IP_CSUM,但是数据报又不是IP协议的。这时候,还需要执行软件校验和,dev_queue_xmit就调用skb_checksum_help补上这个校验和,并把skb->ip_summed设为CHECKSUM_NONE。 struct net_device的成员qdisc是一个发送队列,缓冲等待网络设备进行发送的skb,如果网络设备设置了这个队列,则把skb加到这个队列中,并启动队列的发送。否则,如果网络设备处于启用状态,则直接调用网络设备的输出函数进行发送,但在发送前,还需要做一件事情,就是,如果有ETH_P_ALL数据报类型被添加到ptype_all中来,则需要把数据报复制一份给这个数据报类型的接收函数,因为该类型需要接收到所有的数据报,包括输出的数据报。 19. 数据报在链路层的接收 网络设备在接收到来自网络中其它主机的数据报,或本地环回接口的数据报之后,交给 协议栈的netif_rx函数,该函数首先要为收到的这个skb打上当前的时间戳(skb->tstamp成员),这个时间戳表示该数据到达的时间,它不是必选的,可以通过套接字选项 SO_TIMESTAMP将其打开,该选项打开时间戳时,会将链路层的全局变量netstamp_needed加1,netif_rx在检查到这个变量不为零时,为skb打上时间戳。 softnet_data是类型为struct softnet_data结构体的全局变量,每个CPU定义一个,它是链路层的数据接收队列,该结构体的定义如下: struct softnet_data { struct net_device *output_queue; struct sk_buff_head input_pkt_queue; struct list_head poll_list; struct sk_buff *completion_queue; struct net_device backlog_dev; }; input_pkt_queue是skb的队列,接收到的skb全部进入该队列等待后续处理,netif_rx首先检查该队列当前的长度input_pkt_queue.qlen,即当前排在队列中的skb的数量,当数量超过netdev_max_backlog的值时,直接丢弃新收到的包,netdev_max_backlog在协议栈中定义的缺省值为1000,可以通过文件/proc/sys/net/core/netdev_max_backlog进行修改。如果当前队列长度未达到上限,把新收到的skb加到这个队列中,在加到队列之前,要确保对这个队列的接收处理已启动,如果当前队列为空,则要先调用netif_rx_schedule启动队列的处理,再把skb加到队列中。需要注意的是softnet_data是CPU绑定的,但不是网络设备绑定的,多个网络设备收到的数据报可能存放在同一个队列中待处理。 netif_rx_schedule函数的主要作用是触发一个软中断NET_RX_SOFTIRQ,使中断处理函数net_rx_action处理接收队列中的数据报。net_rx_action开始时会记录下系统的当前时间,然后进行处理,当处理时间持续超过1个时钟嘀嗒时,它会再触发一个中断 NET_RX_SOFTIRQ,并退出,在下一个中断中继续处理。一次中断处理除了时间上有,处理的数据报的数量上也有。 softnet_data的成员poll_list中存放的是成员backlog_dev的地址,由netif_rx_schedule存入,backlog_dev的成员poll在系统初始化时被指向函数process_backlog,net_rx_action调用该函数进行实际的数据报处理,process_backlog把数据报从input_pkt_queue队列中取出,传给netif_receive_skb,由netif_receive_skb传给相应的网络层接收函数。process_backlog的处理时间也有1个时钟嘀嗒的,同时一次处理的数据报的数量不得超过 backlog_dev->quota和netdev_budget两个值中较小的那个值,backlog_dev->quota由 netif_rx_schedule初始化为全局变量weight_p的值,缺省为,netdev_budget缺省为300。从代码可以看出,process_backlog一次处理最大数据报数量为,而net_rx_action为300。weight_p和netdev_budget这两个值分别可以在文件/proc/sys/net/core/dev_weight和/proc/sys/net/core/netdev_budget中查看和修改。 netif_receive_skb是链路层接收数据报的最后一站。它根据注册在全局数组ptype_all和ptype_base里的网络层数据报类型,把数据报递交给不同的网络层协议的接收函数(INET域中主要是ip_rcv和arp_rcv)。 因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- huatuo0.com 版权所有 湘ICP备2023021991号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务