本文源自:http://www.linuxidc.com/Linux/2011-06/37493p2.htm
首先分析一下Linux网络设备的结构,如下图:
- 网络协议接口层向网络层协议提供提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接受数据。这一层的存在使得上层协议独立于具体的设备。
- 网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。
- 设备驱动功能层各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,他通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接受操作。
- 网络设备与媒介层是完成数据包发送和接受的物理实体,包括网络适配器和具体的传输媒介,网络适配器被驱动功能层中的函数物理上驱动。对于Linux系统而言,网络设备和媒介都可以是虚拟的。
网络协议接口层:
这里主要进行数据包的收发,使用函数原型为:
- dev_queue_xmit(struct sk_buff *skb);
- int netif_rx(struct sk_buff *skb);
这里使用了一个skb_buff结构体,定义于include/linux/skbuff.h中,它的含义为“套接字缓冲区”,用于在Linux网络子系统各层间传输数据。他是一个双向链表,在老的内核中会有一个list域指向sk_buff_head也就是链表头,但是在我研究的linux2.6.30.4内核中已经不存在了,如下图:
操作套接字缓冲区的函数:
1.分配
- struct sk_buff *alloc_skb(unsigned int len, int priority);
- struct sk_buff *dev_alloc_skb(unsigned int len);
分配一个缓冲区。alloc_skb函数分配一个缓冲区并初始化skb->data和skb->tail为skb->head。参数len为数据缓冲区的空间大小,通常以L1_CACHE_BYTES字节(对ARM为32)对齐,参数priority为内存分配的优先级。dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配。
2.释放
- void kfree_skb(struct sk_buff *skb);
- void dev_kfree_skb(struct sk_buff *skb);
Linux内核内部使用kfree_skb()函数,而网络设备驱动程序中则最好使用dev_kfree_skb()。
sk_buff中比较重要的成员是指向数据包中数据的指针,如下图所示:
用于寻址数据包中数据的指针,head指向已分配空间开头,data指向有效的octet开头,tail指向有效的octet结尾,而end指向tail可以到达的最大地址。如果不这样做而分配一个大小固定的缓冲区,如果buffer不够用,则要申请一个更大的buffer,拷贝进去再增加,这样降低了性能。
3.变更
- unsigned char *skb_put(struct sk_buff *skb, int len);
- unsigned char *skb_push(struct sk_buff *skb, int len);
- unsigned char *skb_pull(struct sk_buff *skb, int len);
- void skb_reserve(struct sk_buff ×skb, int len);
下图分别对应了这四个函数,看了这张图应该对这4个函数的作用了然于胸。
网络设备接口层:
网络设备接口层的主要功能是为千变万化的网络设备定义了统一,抽象的数据结构net_device结构体,以不变应万变,实现多种硬件在软件层次上的统一。
首先看打开和关闭网络设备的函数:
- int (*open)(struct net_device *dev);
- int (*close)(struct net_device *dev);
要注意的是ifconfig是interface config的缩写,通常我们在用户空间输入:
- ifconfig eth0 up
会调用这里的open函数。
在用户空间输入:
- ifconfig eth0 down
会调用这里的stop函数。
在使用ifconfig向接口赋予地址时,要执行两个任务。首先,它通过ioctl(SIOCSIFADDR)(Socket I/O Control Set Interface Address)赋予地址,然后通过ioctl(SIOCSIFFLAGS)(Socket I/O Control Set Interface Flags)设置dev->flag中的IFF_UP标志以打开接口。这个调用会使得设备的open方法得到调用。类似的,在接口关闭时,ifconfig使用ioctl(SIOCSIFFLAGS)来清理IFF_UP标志,然后调用stop函数。
- int (*hard_header)(struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len);
该方法根据先前检索到的源和目的硬件地址建立硬件头。
- int (*rebuild_header)(struct sk_buff *skb);
以太网的mac地址是固定的,为了高效,第一个包去询问mac地址,得到对应的mac地址后就会作为cache把mac地址保存起来。以后每次发包不用询问了,直接把包的地址拷贝出来。
- void (*tx_timeout)(struct net_device *dev);
如果数据包发送在超时时间内失败,这时该方法被调用,这个方法应该解决失败的问题,并重新开始发送数据。
- struct net_device_stats *(*get_stats)(struct net_device *dev);
当应用程序需要获得接口的统计信息时,这个方法被调用。
- int (*set_config)(struct net_device *dev, struct ifmap *map);
改变接口的配置,比如改变I/O端口和中断号等,现在的驱动程序通常无需该方法。
- int (*do_ioctl)(struct net_device *dev, struct ifmap *map);
用来实现自定义的ioctl命令,如果不需要可以为NULL。
- void (*set_multicast_list)(struct net_device *dev);
当设备的组播列表改变或设备标志改变时,该方法被调用。
- int (*set_mac_address)(struct net_device *dev, void *addr);
如果接口支持mac地址改变,则可以实现该函数。
设备驱动接口层:
net_device结构体的成员(属性和函数指针)需要被设备驱动功能层的具体数值和函数赋予。对具体的设置xxx,工程师应该编写设备驱动功能层的函数,这些函数型如xxx_open(),xxx_stop(),xxx_tx(),xxx_hard_header(),xxx_get_stats(),xxx_tx_timeout()等。
网络设备与媒介层:
网络设备与媒介层直接对应于实际的硬件设备。
网络设备的初始化:
通过模块的加载函数看出DM9000A的驱动是以平台驱动的形式注册进内核的,下边是模块的加载函数:
- static int __init
- dm9000_init(void)
- {
- printk(KERN_INFO "%s Ethernet Driver, V%s\n", CARDNAME, DRV_VERSION);
- return platform_driver_register(&dm9000_driver);
- }
下边是平台驱动结构体:
- static struct platform_driver dm9000_driver = {
- .driver = {
- .name = "dm9000",
- .owner = THIS_MODULE,
- },
- .probe = dm9000_probe,
- .remove = __devexit_p(dm9000_drv_remove),
- .suspend = dm9000_drv_suspend,
- .resume = dm9000_drv_resume,
- };
下面来分析probe函数,用来执行分配的内核函数是alloc_netdev,函数原型是:
- struct net_device *alloc_netdev(int sizeof_priv, const char *name, void (*setup)(struct net_device*));
这里的sizeof_priv是驱动程序私有数据区的大小;这个区成员和net_device结构一同分配给网络设备。实际上,他们都处于一大块内存中,但是驱动程序不需要知道这些。name是接口的名字,其在用户空间可见;这个名字可以使用类似printf中%d的格式,内核将用下一个可用的接口号替代%d,最后,setup是一个初始化函数,用来设置net_device结构剩余的部分。网络子系统对alloc_netdev,为不同种类的接口封装了许多函数。最常用的是alloc_etherdev,它定义在linux/etherdevice.h中:
- struct net_device *alloc_etherdev(int sizeof_priv);
该函数使用eth%d的形式指定分配给网络设备的名字。它提供了自己的初始化函数(ether_setup),用正确的值为以太网设备设置net_device中的许多成员。那么在DM9000A中这个私有数据成员是什么呢,看下边的结构:
- /* Structure/enum declaration ------------------------------- */
- typedef struct board_info {
- void __iomem *io_addr; /* Register I/O base address */
- void __iomem *io_data; /* Data I/O address */
- u16 irq; /* IRQ */
- u16 tx_pkt_cnt;
- u16 queue_pkt_len;
- u16 queue_start_addr;
- u16 dbug_cnt;
- u8 io_mode; /* 0:word, 2:byte */
- u8 phy_addr;
- u8 imr_all;
- unsigned int flags;
- unsigned int in_suspend :1;
- int debug_level;
- enum dm9000_type type;
- void (*inblk)(void __iomem *port, void *data, int length);
- void (*outblk)(void __iomem *port, void *data, int length);
- void (*dumpblk)(void __iomem *port, int length);
- struct device *dev; /* parent device */
- struct resource *addr_res; /* resources found */
- struct resource *data_res;
- struct resource *addr_req; /* resources requested */
- struct resource *data_req;
- struct resource *irq_res;
- struct mutex addr_lock; /* phy and eeprom access lock */
- struct delayed_work phy_poll;
- struct net_device *ndev;
- spinlock_t lock;
- struct mii_if_info mii;
- u32 msg_enable;
- } board_info_t;
这个struct board_info就是那个私有数据,用来保存芯片相关的一些私有信息。
下面是probe函数的实现:
- /*
- * Search DM9000 board, allocate space and register it
- */
- static int __devinit
- dm9000_probe(struct platform_device *pdev)
- {
- /*获得平台数据,这个应该在platform_device那边指定了*/
- struct dm9000_plat_data *pdata = pdev->dev.platform_data;
- struct board_info *db; /* Point a board information structure */
- struct net_device *ndev;
- const unsigned char *mac_src;
- int ret = 0;
- int iosize;
- int i;
- u32 id_val;
- /*分配以太网的网络设备*/
- ndev = alloc_etherdev(sizeof(struct board_info));
- if (!ndev) {
- dev_err(&pdev->dev, "could not allocate device.\n");
- return -ENOMEM;
- }
- /*#define SET_NETDEV_DEV(net, pdev) ((net)->dev.parent = (pdev))*/
- SET_NETDEV_DEV(ndev, &pdev->dev);
- dev_dbg(&pdev->dev, "dm9000_probe()\n");
- /*设置struct board_info为ndev的私有数据*/
- db = netdev_priv(ndev);
- memset(db, 0, sizeof(*db));
- db->dev = &pdev->dev;
- db->ndev = ndev;
- spin_lock_init(&db->lock);
- mutex_init(&db->addr_lock);
- /*提交一个任务给一个工作队列,你需要填充一个work_struct结构db->phy_poll*/
- INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);
- /*获取IO内存和中断资源*/
- db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
- db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (db->addr_res == NULL || db->data_res == NULL ||
- db->irq_res == NULL) {
- dev_err(db->dev, "insufficient resources\n");
- ret = -ENOENT;
- goto out;
- }
- /*映射到内核,并获得IO内存的虚拟地址,ioremap完成页表的建立,不同于vmalloc,但是,它实际上不分配内存*/
- iosize = res_size(db->addr_res);
- db->addr_req = request_mem_region(db->addr_res->start, iosize,
- pdev->name);
- if (db->addr_req == NULL) {
- dev_err(db->dev, "cannot claim address reg area\n");
- ret = -EIO;
- goto out;
- }
- db->io_addr = ioremap(db->addr_res->start, iosize);
- if (db->io_addr == NULL) {
- dev_err(db->dev, "failed to ioremap address reg\n");
- ret = -EINVAL;
- goto out;
- }
- iosize = res_size(db->data_res);
- db->data_req = request_mem_region(db->data_res->start, iosize,
- pdev->name);
- if (db->data_req == NULL) {
- dev_err(db->dev, "cannot claim data reg area\n");
- ret = -EIO;
- goto out;
- }
- db->io_data = ioremap(db->data_res->start, iosize);
- if (db->io_data == NULL) {
- dev_err(db->dev, "failed to ioremap data reg\n");
- ret = -EINVAL;
- goto out;
- }
- /*获得网络设备的基地址*/
- ndev->base_addr = (unsigned long)db->io_addr;
- /*获得网络设备的中断号*/
- ndev->irq = db->irq_res->start;
- /*设置默认的IO函数*/
- dm9000_set_io(db, iosize);
- /*如果平台数据不为空*/
- if (pdata != NULL) {
- /* check to see if the driver wants to over-ride the
- * default IO width */
- if (pdata->flags & DM9000_PLATF_8BITONLY)
- dm9000_set_io(db, 1);
- if (pdata->flags & DM9000_PLATF_16BITONLY)
- dm9000_set_io(db, 2);
- if (pdata->flags & DM9000_PLATF_32BITONLY)
- dm9000_set_io(db, 4);
- /* check to see if there are any IO routine
- * over-rides */
- if (pdata->inblk != NULL)
- db->inblk = pdata->inblk;
- if (pdata->outblk != NULL)
- db->outblk = pdata->outblk;
- if (pdata->dumpblk != NULL)
- db->dumpblk = pdata->dumpblk;
- db->flags = pdata->flags;
- }
- #ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
- db->flags |= DM9000_PLATF_SIMPLE_PHY;
- #endif
- /*dm9000复位*/
- dm9000_reset(db);
- /*读取Vendor ID Register,Product ID Register中的值,与0x90000A46比较,如果相等,则说明是DM9000*/
- /* try multiple times, DM9000 sometimes gets the read wrong */
- for (i = 0; i < 8; i++) {
- id_val = ior(db, DM9000_VIDL);
- id_val |= (u32)ior(db, DM9000_VIDH) << 8;
- id_val |= (u32)ior(db, DM9000_PIDL) << 16;
- id_val |= (u32)ior(db, DM9000_PIDH) << 24;
- if (id_val == DM9000_ID)
- break;
- dev_err(db->dev, "read wrong id 0x%08x\n", id_val);
- }
- if (id_val != DM9000_ID) {
- dev_err(db->dev, "wrong id: 0x%08x\n", id_val);
- ret = -ENODEV;
- goto out;
- }
- /* Identify what type of DM9000 we are working on */
- /*读取Chip Revision Register中的值*/
- id_val = ior(db, DM9000_CHIPR);
- dev_dbg(db->dev, "dm9000 revision 0x%02x\n", id_val);
- switch (id_val) {
- case CHIPR_DM9000A:
- db->type = TYPE_DM9000A;
- break;
- case CHIPR_DM9000B:
- db->type = TYPE_DM9000B;
- break;
- default:
- dev_dbg(db->dev, "ID %02x => defaulting to DM9000E\n", id_val);
- db->type = TYPE_DM9000E;
- }
- /* from this point we assume that we have found a DM9000 */
- /* driver system function */
- /*设置部分net_device字段*/
- ether_setup(ndev);
- ndev->open = &dm9000_open;
- ndev->hard_start_xmit = &dm9000_start_xmit;
- ndev->tx_timeout = &dm9000_timeout;
- ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
- ndev->stop = &dm9000_stop;
- ndev->set_multicast_list = &dm9000_hash_table;
- /*对ethtool支持的相关声明可在<linux/ethtool.h>中找到。它的核心是一个ethtool_ops类型的结构,里边包含一个全部的24个不同的方法来支持ethtool*/
- ndev->ethtool_ops = &dm9000_ethtool_ops;
- ndev->do_ioctl = &dm9000_ioctl;
- #ifdef CONFIG_NET_POLL_CONTROLLER
- ndev->poll_controller = &dm9000_poll_controller;
- #endif
- db->msg_enable = NETIF_MSG_LINK;
- db->mii.phy_id_mask = 0x1f;
- db->mii.reg_num_mask = 0x1f;
- db->mii.force_media = 0;
- db->mii.full_duplex = 0;
- db->mii.dev = ndev;
- db->mii.mdio_read = dm9000_phy_read;
- db->mii.mdio_write = dm9000_phy_write;
- /*MAC地址的源是eeprom*/
- mac_src = "eeprom";
- /* try reading the node address from the attached EEPROM */
- for (i = 0; i < 6; i += 2)
- dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i);
- /*如果从eeprom中读取的地址无效,并且私有数据不为空,从platform_device的私有数据中获取dev_addr*/
- if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {
- mac_src = "platform data";
- memcpy(ndev->dev_addr, pdata->dev_addr, 6);
- }
- /*如果地址依然无效,从PAR:物理地址(MAC)寄存器(Physical Address Register)中读取*/
- if (!is_valid_ether_addr(ndev->dev_addr)) {
- /* try reading from mac */
- mac_src = "chip";
- for (i = 0; i < 6; i++)
- ndev->dev_addr[i] = ior(db, i+DM9000_PAR);
- }
- /*查看以太网网卡设备地址是否有效*/
- if (!is_valid_ether_addr(ndev->dev_addr))
- dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "
- "set using ifconfig\n", ndev->name);
- /*将ndev保存到pdev->dev->driver_data中*/
- platform_set_drvdata(pdev, ndev);
- /*一切都初始化好后,注册网络设备*/
- ret = register_netdev(ndev);
- if (ret == 0)
- printk(KERN_INFO "%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)\n",
- ndev->name, dm9000_type_to_char(db->type),
- db->io_addr, db->io_data, ndev->irq,
- ndev->dev_addr, mac_src);
- return 0;
- out:
- dev_err(db->dev, "not found (%d).\n", ret);
- dm9000_release_board(pdev, db);
- free_netdev(ndev);
- return ret;
- }
下边看看挂起和唤醒函数:
挂起函数完成了设置挂起标志,并没有真正把设备移除而只是设置了移除标志,复位PHY,停止PHY,禁止所有中断,禁止接受引脚。
- static int
- dm9000_drv_suspend(struct platform_device *dev, pm_message_t state)
- {
- struct net_device *ndev = platform_get_drvdata(dev);
- board_info_t *db;
- if (ndev) {
- db = netdev_priv(ndev);
- db->in_suspend = 1;
- if (netif_running(ndev)) {
- netif_device_detach(ndev);
- dm9000_shutdown(ndev);
- }
- }
- return 0;
- }
唤醒函数完成了复位dm9000,初始化dm9000,标记设备为attached,清除挂起标志。
- static int
- dm9000_drv_resume(struct platform_device *dev)
- {
- struct net_device *ndev = platform_get_drvdata(dev);
- board_info_t *db = netdev_priv(ndev);
- if (ndev) {
- if (netif_running(ndev)) {
- dm9000_reset(db);
- dm9000_init_dm9000(ndev);
- netif_device_attach(ndev);
- }
- db->in_suspend = 0;
- }
- return 0;
- }
网络设备的打开与释放:
首先来看这个open函数:
- static int
- dm9000_open(struct net_device *dev)
- {
- board_info_t *db = netdev_priv(dev);
- unsigned long irqflags = db->irq_res->flags & IRQF_TRIGGER_MASK;
- if (netif_msg_ifup(db))
- dev_dbg(db->dev, "enabling %s\n", dev->name);
- /* If there is no IRQ type specified, default to something that
- * may work, and tell the user that this is a problem */
- if (irqflags == IRQF_TRIGGER_NONE)
- dev_warn(db->dev, "WARNING: no IRQ resource flags set.\n");
- irqflags |= IRQF_SHARED;
- /*注册中断处理函数*/
- if (request_irq(dev->irq, &dm9000_interrupt, irqflags, dev->name, dev))
- return -EAGAIN;
- /* Initialize DM9000 board */
- /*复位DM9000*/
- dm9000_reset(db);
- /*初始化DM9000的寄存器*/
- dm9000_init_dm9000(dev);
- /* Init driver variable */
- db->dbug_cnt = 0;
- /*检查链路载波状况*/
- mii_check_media(&db->mii, netif_msg_link(db), 1);
- /*启动发送队列*/
- netif_start_queue(dev);
- /*之前在probe函数中调用 INIT_DELAYED_WORK初始化了工作队列,并关联了一个操作函数dm9000_poll_work(),此时运行dm9000_schedule_poll来调用这个函数*/
- dm9000_schedule_poll(db);
- return 0;
- }
然后是stop函数:
- static int
- dm9000_stop(struct net_device *ndev)
- {
- board_info_t *db = netdev_priv(ndev);
- if (netif_msg_ifdown(db))
- dev_dbg(db->dev, "shutting down %s\n", ndev->name);
- /*杀死延时工作队列phy_poll*/
- cancel_delayed_work_sync(&db->phy_poll);
- /*停止发送队列*/
- netif_stop_queue(ndev);
- /*通知内核链路失去连接*/
- netif_carrier_off(ndev);
- /* free interrupt */
- free_irq(ndev->irq, ndev);
- /*关闭DM9000*/
- dm9000_shutdown(ndev);
- return 0;
- }
复位PHY,停止PHY,禁止所有中断,禁止接收引脚。
- static void
- dm9000_shutdown(struct net_device *dev)
- {
- board_info_t *db = netdev_priv(dev);
- /* RESET device */
- dm9000_phy_write(dev, 0, MII_BMCR, BMCR_RESET); /* PHY RESET */
- iow(db, DM9000_GPR, 0x01); /* Power-Down PHY */
- iow(db, DM9000_IMR, IMR_PAR); /* Disable all interrupt */
- iow(db, DM9000_RCR, 0x00); /* Disable RX */
- }