帖子
-
2415
主题
-
441
精华
-
36
可用积分
-
6081
专家积分
-
0
在线时间
-
760 小时
注册时间
-
2003-08-12
最后登录
-
2014-09-11
论坛徽章:
-
0
|
作者:kendo mail:kendo999@sohu.com kernel version:2.6.12 出处:www.skynet.org.cn 转载请注明作者及了出处。 九贱写这篇贴子时,手边上没有参考资料,拿着一本LDD3硬啃,所以,出错之处,再所难免,还请大家指正。 关于贴子的后续改正、补充(目前还没有写到网络层呢),我会发布在我的个人网站上: http://www.skynet.org.cn/viewthr ... xtra=page%3D1#pid56 写望借此贴,与各们Linux及tcp/ip的fans共勉! 一、从网卡说起 这并非是一个网卡驱动分析的专门文档,只是对网卡处理数据包的流程进行一个重点的分析。这里以Intel的e100驱动为例进行分析。 大多数网卡都是一个PCI设备,PCI设备都包含了一个标准的配置寄存器,寄存器中,包含了PCI设备的厂商ID、设备ID等等信息,驱动 程序使用来描述这些寄存器的标识符。如下: [Copy to clipboard]CODE: struct pci_device_id { __u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/ __u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */ __u32 class, class_mask; /* (class,subclass,prog-if) triplet */ kernel_ulong_t driver_data; /* Data private to the driver */ }; 这样,在驱动程序中,常常就可以看到定义一个struct pci_device_id 类型的数组,告诉内核支持不同类型的 PCI设备的列表,以e100驱动为例: #define INTEL_8255X_ETHERNET_DEVICE(device_id, ich) {\ PCI_VENDOR_ID_INTEL, device_id, PCI_ANY_ID, PCI_ANY_ID, \ PCI_CLASS_NETWORK_ETHERNET << 8, 0xFFFF00, ich } static struct pci_device_id e100_id_table[] = { INTEL_8255X_ETHERNET_DEVICE(0x1029, 0), INTEL_8255X_ETHERNET_DEVICE(0x1030, 0), INTEL_8255X_ETHERNET_DEVICE(0x1031, 3), ……/*略过一大堆支持的设备*/ { 0, } }; 在内核中,一个PCI设备,使用struct pci_driver结构来描述, struct pci_driver { struct list_head node; char *name; struct module *owner; const struct pci_device_id *id_table; /* must be non-NULL for probe to be called */ int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */ void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */ int (*suspend) (struct pci_dev *dev, pm_message_t state); /* Device suspended */ int (*resume) (struct pci_dev *dev); /* Device woken up */ int (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable); /* Enable wake event */ void (*shutdown) (struct pci_dev *dev); struct device_driver driver; struct pci_dynids dynids; }; 因为在系统引导的时候,PCI设备已经被识别,当内核发现一个已经检测到的设备同驱动注册的id_table中的信息相匹配时, 它就会触发驱动的probe函数,以e100为例: /* * 定义一个名为e100_driver的PCI设备 * 1、设备的探测函数为e100_probe; * 2、设备的id_table表为e100_id_table */ static struct pci_driver e100_driver = { .name = DRV_NAME, .id_table = e100_id_table, .probe = e100_probe, .remove = __devexit_p(e100_remove), #ifdef CONFIG_PM .suspend = e100_suspend, .resume = e100_resume, #endif .driver = { .shutdown = e100_shutdown, } }; 这样,如果系统检测到有与id_table中对应的设备时,就调用驱动的probe函数。 驱动设备在init函数中,调用pci_module_init函数初始化PCI设备e100_driver: static int __init e100_init_module(void) { if(((1 << debug) - 1) & NETIF_MSG_DRV) { printk(KERN_INFO PFX "%s, %s\n", DRV_DESCRIPTION, DRV_VERSION); printk(KERN_INFO PFX "%s\n", DRV_COPYRIGHT); } return pci_module_init(&e100_driver); } 一切顺利的话,注册的e100_probe函数将被内核调用,这个函数完成两个重要的工作: 1、分配/初始化/注册网络设备; 2、完成PCI设备的I/O区域的分配和映射,以及完成硬件的其它初始化工作; 网络设备使用struct net_device结构来描述,这个结构非常之大,许多重要的参考书籍对它都有较为深入的描述,可以参考《Linux设备驱动程序》中网卡驱动设计的相关章节。我会在后面的内容中,对其重要的成员进行注释; 当probe函数被调用,证明已经发现了我们所支持的网卡,这样,就可以调用register_netdev函数向内核注册网络设备了,注册之前,一般会调用alloc_etherdev为以太网分析一个net_device,然后初始化它的重要成员。 除了向内核注册网络设备之外,探测函数另一项重要的工作就是需要对硬件进行初始化,比如,要访问其I/O区域,需要为I/O区域分配内存区域,然后进行映射,这一步一般的流程是: 1、request_mem_region() 2、ioremap() 对于一般的PCI设备而言,可以调用: 1、pci_request_regions() 2、ioremap() pci_request_regions函数对PCI的6个寄存器都会调用资源分配函数进行申请(需要判断是I/O端口还是I/O内存),例如: [Copy to clipboard]CODE: int pci_request_regions(struct pci_dev *pdev, char *res_name) { int i; for (i = 0; i < 6; i++) if(pci_request_region(pdev, i, res_name)) goto err_out; return 0; [Copy to clipboard]CODE: int pci_request_region(struct pci_dev *pdev, int bar, char *res_name) { if (pci_resource_len(pdev, bar) == 0) return 0; if (pci_resource_flags(pdev, bar) & IORESOURCE_IO) { if (!request_region(pci_resource_start(pdev, bar), pci_resource_len(pdev, bar), res_name)) goto err_out; } else if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) { if (!request_mem_region(pci_resource_start(pdev, bar), pci_resource_len(pdev, bar), res_name)) goto err_out; } return 0; 有了这些基础,我们来看设备的探测函数: static int __devinit e100_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct net_device *netdev; struct nic *nic; int err; /*分配网络设备*/ if(!(netdev = alloc_etherdev(sizeof(struct nic)))) { if(((1 << debug) - 1) & NETIF_MSG_PROBE) printk(KERN_ERR PFX "Etherdev alloc failed, abort.\n"); return -ENOMEM; } /*设置各成员指针函数*/ netdev->open = e100_open; netdev->stop = e100_close; netdev->hard_start_xmit = e100_xmit_frame; netdev->get_stats = e100_get_stats; netdev->set_multicast_list = e100_set_multicast_list; netdev->set_mac_address = e100_set_mac_address; netdev->change_mtu = e100_change_mtu; netdev->do_ioctl = e100_do_ioctl; SET_ETHTOOL_OPS(netdev, &e100_ethtool_ops); netdev->tx_timeout = e100_tx_timeout; netdev->watchdog_timeo = E100_WATCHDOG_PERIOD; netdev->poll = e100_poll; netdev->weight = E100_NAPI_WEIGHT; #ifdef CONFIG_NET_POLL_CONTROLLER netdev->poll_controller = e100_netpoll; #endif /*设置网络设备名称*/ strcpy(netdev->name, pci_name(pdev)); /*取得设备私有数据结构*/ nic = netdev_priv(netdev); /*网络设备指针,指向自己*/ nic->netdev = netdev; /*PCIy设备指针,指向自己*/ nic->pdev = pdev; nic->msg_enable = (1 << debug) - 1; /*将PCI设备的私有数据区指向网络设备*/ pci_set_drvdata(pdev, netdev); /*激活PCI设备*/ if((err = pci_enable_device(pdev))) { DPRINTK(PROBE, ERR, "Cannot enable PCI device, aborting.\n"); goto err_out_free_dev; } /*判断I/O区域是否是I/O内存,如果不是,则报错退出*/ if(!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) { DPRINTK(PROBE, ERR, "Cannot find proper PCI device " "base address, aborting.\n"); err = -ENODEV; goto err_out_disable_pdev; } /*分配I/O内存区域*/ if((err = pci_request_regions(pdev, DRV_NAME))) { DPRINTK(PROBE, ERR, "Cannot obtain PCI resources, aborting.\n"); goto err_out_disable_pdev; } /* * 告之内核自己的DMA寻址能力,这里不是很明白,因为从0xFFFFFFFF来看,本来就是内核默认的32了 * 为什么还要调用pci_set_dma_mask来重复设置呢?可能是对ULL而非UL不是很了解吧。 */ if((err = pci_set_dma_mask(pdev, 0xFFFFFFFFULL))) { DPRINTK(PROBE, ERR, "No usable DMA configuration, aborting.\n"); goto err_out_free_res; } SET_MODULE_OWNER(netdev); SET_NETDEV_DEV(netdev, &pdev->dev); /*分配完成后,映射I/O内存*/ nic->csr = ioremap(pci_resource_start(pdev, 0), sizeof(struct csr)); if(!nic->csr) { DPRINTK(PROBE, ERR, "Cannot map device registers, aborting.\n"); err = -ENOMEM; goto err_out_free_res; } if(ent->driver_data) nic->flags |= ich; else nic->flags &= ~ich; /*设置设备私有数据结构的大部份默认参数*/ e100_get_defaults(nic); /* 初始化自旋锁,锅的初始化必须在调用 hw_reset 之前执行*/ spin_lock_init(&nic->cb_lock); spin_lock_init(&nic->cmd_lock); /* 硬件复位,通过向指定I/O端口设置复位指令实现. */ e100_hw_reset(nic); /* * PCI网卡被BIOS配置后,某些特性可能会被屏蔽掉。比如,多数BIOS都会清掉“master”位, * 这导致板卡不能随意向主存中拷贝数据。pci_set_master函数数会检查是否需要设置标志位, * 如果需要,则会将“master”位置位。 * PS:什么是PCI master? * 不同于ISA总线,PCI总线的地址总线与数据总线是分时复用的。这样做的好处是,一方面 * 可以节省接插件的管脚数,另一方面便于实现突发数据传输。在做数据传输时,由一个PCI * 设备做发起者(主控,Initiator或Master),而另一个PCI设备做目标(从设备,Target或Slave)。 * 总线上的所有时序的产生与控制,都由Master来发起。PCI总线在同一时刻只能供一对设备完成传输。 */ pci_set_master(pdev); /*添加两个内核定时器,watchdog和blink_timer*/ init_timer(&nic->watchdog); nic->watchdog.function = e100_watchdog; nic->watchdog.data = (unsigned long)nic; init_timer(&nic->blink_timer); nic->blink_timer.function = e100_blink_led; nic->blink_timer.data = (unsigned long)nic; INIT_WORK(&nic->tx_timeout_task, (void (*)(void *))e100_tx_timeout_task, netdev); if((err = e100_alloc(nic))) { DPRINTK(PROBE, ERR, "Cannot alloc driver memory, aborting.\n"); goto err_out_iounmap; } /*phy寄存器初始化*/ e100_phy_init(nic); if((err = e100_eeprom_load(nic))) goto err_out_free; memcpy(netdev->dev_addr, nic->eeprom, ETH_ALEN); if(!is_valid_ether_addr(netdev->dev_addr)) { DPRINTK(PROBE, ERR, "Invalid MAC address from " "EEPROM, aborting.\n"); err = -EAGAIN; goto err_out_free; } /* Wol magic packet can be enabled from eeprom */ if((nic->mac >= mac_82558_D101_A4) && (nic->eeprom[eeprom_id] & eeprom_id_wol)) nic->flags |= wol_magic; /* ack any pending wake events, disable PME */ pci_enable_wake(pdev, 0, 0); /*注册网络设备*/ strcpy(netdev->name, "eth%d"); if((err = register_netdev(netdev))) { DPRINTK(PROBE, ERR, "Cannot register net device, aborting.\n"); goto err_out_free; } DPRINTK(PROBE, INFO, "addr 0x%lx, irq %d, " "MAC addr %02X:%02X:%02X:%02X:%02X:%02X\n", pci_resource_start(pdev, 0), pdev->irq, netdev->dev_addr[0], netdev->dev_addr[1], netdev->dev_addr[2], netdev->dev_addr[3], netdev->dev_addr[4], netdev->dev_addr[5]); return 0; err_out_free: e100_free(nic); err_out_iounmap: iounmap(nic->csr); err_out_free_res: pci_release_regions(pdev); err_out_disable_pdev: pci_disable_device(pdev); err_out_free_dev: pci_set_drvdata(pdev, NULL); free_netdev(netdev); return err; } 执行到这里,探测函数的使命就完成了,在对网络设备重要成员初始化时,有: netdev->open = e100_open; 指定了设备的open函数为e100_open,这样,当第一次使用设备,比如使用ifconfig工具的时候,open函数将被调用。 |
|