Linux网络设备结构

  首先看一下Linux网络设备的结构,如下图:

  

  1. 网络协议接口层向网络层协议提供提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接受数据。这一层的存在使得上层协议独立于具体的设备。

  2. 网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。

  3. 设备驱动功能层各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,他通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接受操作。

  4. 网络设备与媒介层是完成数据包发送和接受的物理实体,包括网络适配器和具体的传输媒介,网络适配器被驱动功能层中的函数物理上驱动。对于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);  

  如果数据包发送在超时时间内失败,这时该方法被调用,这个方法应该解决失败的问题,并重新开始发送数据。

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()等。


网络设备与媒介层

  网络设备与媒介层直接对应于实际的硬件设备。


  下面从代码中分析dm9000的实现


网络设备的初始化

  通过模块的加载函数看出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);
}

probe函数

  下面来分析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 :;
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 = ;
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, , 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, );
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, );
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, ); 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, ); if (pdata->flags & DM9000_PLATF_16BITONLY)
dm9000_set_io(db, ); if (pdata->flags & DM9000_PLATF_32BITONLY)
dm9000_set_io(db, ); /* 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 = ; i < ; i++) {
id_val = ior(db, DM9000_VIDL);
id_val |= (u32)ior(db, DM9000_VIDH) << ;
id_val |= (u32)ior(db, DM9000_PIDL) << ;
id_val |= (u32)ior(db, DM9000_PIDH) << ; 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 = ;
db->mii.full_duplex = ;
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 = ; i < ; i += )
dm9000_read_eeprom(db, i / , 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, );
}
/*如果地址依然无效,从PAR:物理地址(MAC)寄存器(Physical Address Register)中读取*/
if (!is_valid_ether_addr(ndev->dev_addr)) {
/* try reading from mac */ mac_src = "chip";
for (i = ; i < ; 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 == )
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 ; 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 = ; if (netif_running(ndev)) {
netif_device_detach(ndev);
dm9000_shutdown(ndev);
}
}
return ;
}

  唤醒函数完成了复位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 = ;
}
return ;
}

网络设备的打开与释放

  首先来看这个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 = ;
/*检查链路载波状况*/
mii_check_media(&db->mii, netif_msg_link(db), );
/*启动发送队列*/
netif_start_queue(dev);
/*之前在probe函数中调用 INIT_DELAYED_WORK初始化了工作队列,并关联了一个操作函数dm9000_poll_work(),此时运行dm9000_schedule_poll来调用这个函数*/
dm9000_schedule_poll(db); return ;
}

  然后是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 ;
}

  复位PHY,停止PHY,禁止所有中断,禁止接收引脚。

 static void
dm9000_shutdown(struct net_device *dev)
{
board_info_t *db = netdev_priv(dev); /* RESET device */
dm9000_phy_write(dev, , 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 */
}

中断处理函数

  发生中断的情况有3种:

  1. 接收到数据
  2. 发送完数据
  3. 链路状态改变
 static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
board_info_t *db = netdev_priv(dev);
int int_status;
unsigned long flags;
u8 reg_save; dm9000_dbg(db, , "entering %s/n", __func__); /* A real interrupt coming */ /*保存中断到flags中*/
spin_lock_irqsave(&db->lock, flags); /*保存寄存器地址*/
reg_save = readb(db->io_addr); /*禁止DM9000的所有中断*/
iow(db, DM9000_IMR, IMR_PAR); /*获得DM9000的中断状态*/
int_status = ior(db, DM9000_ISR); /* Got ISR */
iow(db, DM9000_ISR, int_status); /* Clear ISR status */ if (netif_msg_intr(db))
dev_dbg(db->dev, "interrupt status %02x/n", int_status); /*检查Interrupt Status Register的第0位,看有没有接收数据*/
if (int_status & ISR_PRS)
dm9000_rx(dev); /*检查Interrupt Status Register的第1位,看有没有发送完数据*/
if (int_status & ISR_PTS)
dm9000_tx_done(dev, db); if (db->type != TYPE_DM9000E) {
/*检查Interrupt Status Register的第5位,看链路状态有没有变化*/
if (int_status & ISR_LNKCHNG) {
/* fire a link-change request */
schedule_delayed_work(&db->phy_poll, );
}
} /*重新使能相应中断*/
iow(db, DM9000_IMR, db->imr_all); /*还原原先的io地址*/
writeb(reg_save, db->io_addr);
/*还原中断状态*/
spin_unlock_irqrestore(&db->lock, flags); return IRQ_HANDLED;
}

  下面说一下DM9000A中的存储部分,DM9000A内部有一个4K Dword SRAM,其中3KB是作为发送,16KB作为接收,如下图所示。

  

  其中0x0000~0x0BFF是传说中的TX buffer(TX buffer中只能存放两个包),0x0C00~0x3FFF是RX buffer

  因此在写内存操作时,当IMR的第7位被设置,如果到达了地址的结尾比如到了3KB,则回卷到0。

  相似的方式,在读操作中,当IMR的第7位被设置如果到达了地址的结尾比如16K,则回卷到0x0C00。

  那么传说中的发送函数又是哪个呢,在probe函数里进行了初始化函数指针操作。

     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;
ndev->ethtool_ops = &dm9000_ethtool_ops;
ndev->do_ioctl = &dm9000_ioctl;

  可以看到当上层调用hard_start_xmit时,在我们的驱动程序中实际调用的是dm9000_start_xmit,下面来分析一dm9000_start_xmit的源码。

发送函数dm9000_start_xmit

 /*
* Hardware start transmission.
* Send a packet to media from the upper layer.
*/
static int
dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
unsigned long flags;
/*获得ndev的私有数据,也就是芯片相关的信息*/
board_info_t *db = netdev_priv(dev); dm9000_dbg(db, , "%s:/n", __func__);
/*只能存放两个,如果已经有两个就退出*/
if (db->tx_pkt_cnt > )
return ; spin_lock_irqsave(&db->lock, flags);
/*
*MWCMD是Memory data write command with address increment Register(F8H)
*写数据到TX SRAM
*写这个命令后,根据IO操作模式(8-bit or 16-bit)来增加写指针1或2
*/
writeb(DM9000_MWCMD, db->io_addr);
/*把数据从sk_buff中拷贝到TX SRAM中*/
(db->outblk)(db->io_data, skb->data, skb->len);
/*为了统计发送了多少个字节,这个在使用ifconfig中显示出的那个发送了多少个字节就是这里计算的*/
dev->stats.tx_bytes += skb->len;
/*待发送的计数加一*/
db->tx_pkt_cnt++;
/*如果只有一个要发送就立即发送,如果这是第二个就应该进行排队了*/
if (db->tx_pkt_cnt == ) {
/*把数据的长度填到TXPLL(发送包长度低字节)和TXPLH(发送包长度高字节)中*/
iow(db, DM9000_TXPLL, skb->len);
iow(db, DM9000_TXPLH, skb->len >> );
/*置发送控制寄存器(TX Control Register)的发送请求位TXREQ(Auto clears after sending completely),这样就可以发送出去了*/
iow(db, DM9000_TCR, TCR_TXREQ); /* Cleared after TX complete */
/*
*记下此时的时间,这里起一个时间戳的作用,之后的超时会用到。如果当前的系统时间超过设备的trans_start时间
*至少一个超时周期,网络层将最终调用驱动程序的tx_timeout。那个这个"一个超时周期"又是什么呢?这个是我们在
*probe函数中设置的,ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
*/
dev->trans_start = jiffies; /* save the time stamp */
} else {
/*如果是第二个包则把skb->len复制给队列的queue_pkt_len,然后告诉上层停止发送队列,因为发送队列已经满了*/
db->queue_pkt_len = skb->len;
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&db->lock, flags);
/*每个数据包写入网卡SRAM后都要释放skb*/
dev_kfree_skb(skb); return ;
}

  下面来看一下刚才提到的那个超时函数,发送超时函数:

 /* Our watchdog timed out. Called by the networking layer */
static void dm9000_timeout(struct net_device *dev)
{
board_info_t *db = netdev_priv(dev);
u8 reg_save;
unsigned long flags; /* Save previous register address */
reg_save = readb(db->io_addr);
spin_lock_irqsave(&db->lock, flags); netif_stop_queue(dev);
dm9000_reset(db);
dm9000_init_dm9000(dev);
/* We can accept TX packets again */
dev->trans_start = jiffies;
netif_wake_queue(dev); /* Restore previous register address */
writeb(reg_save, db->io_addr);
spin_unlock_irqrestore(&db->lock, flags);
}

  这个函数首先停止了发送队列,然后复位dm9000,初始化dm9000,重新设置了时间戳,然后唤醒发送队列,通知网络子系统可再次传输数据包。

 /*
* DM9000 interrupt handler
* receive the packet to upper layer, free the transmitted packet
*/ static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
{
/*从网络状态寄存器(Network Status Register)中获得发送状态*/
int tx_status = ior(db, DM9000_NSR);
/*如果发送状态为NSR_TX2END(第二个包发送完毕)或NSR_TX1END(第一个包发送完毕)*/
if (tx_status & (NSR_TX2END | NSR_TX1END)) {
/*如果一个数据包发送完,待发送数据包个数减1*/
db->tx_pkt_cnt--;
/*如果一个数据包发送完,已发送数据包个数加1*/
dev->stats.tx_packets++; if (netif_msg_tx_done(db))
dev_dbg(db->dev, "tx done, NSR %02x/n", tx_status); /*如果还有数据包要发送*/
if (db->tx_pkt_cnt > ) {
/*将发送的长度放到TXPLL,TXPLH寄存器中*/
iow(db, DM9000_TXPLL, db->queue_pkt_len);
iow(db, DM9000_TXPLH, db->queue_pkt_len >> );
/*置发送请求位*/
iow(db, DM9000_TCR, TCR_TXREQ);
/*保存时间戳*/
dev->trans_start = jiffies;
}
/*通知内核将待发送数据包放入发送队列*/
netif_wake_queue(dev);
}
}

接收函数dm9000_rx

  每接受到一个包,DM9000都会在包的前面加上4个字节,"01H",status与RSR(RX Status Register)的内容相同,"LENL"(数据包长度低8位),"LENH"(数据包长度高8位)。

  所以首先要读取这4个字节来确定数据包的状态,第一个字节"01H"表示接下来的是有效的数据包,"00H"表示没有数据包,若为其他值则表示网卡没有正确初始化,需要从新初始化。

  这4个字节封装在一起:

 struct dm9000_rxhdr {
u8 RxPktReady;
u8 RxStatus;
__le16 RxLen;
} __attribute__((__packed__));

  清晰一些如下图

  接收函数如下

 /*
* Received a packet and pass to upper layer
*/
static void
dm9000_rx(struct net_device *dev)
{
board_info_t *db = netdev_priv(dev);
struct dm9000_rxhdr rxhdr;
struct sk_buff *skb;
u8 rxbyte, *rdptr;
bool GoodPacket;
int RxLen; /* Check packet ready or not */
do {
/*MRCMDX为内存数据预取读命令,并且没有地址增加,读数据的第一个字节,直到读到0x01(数据有效)为止。*/
ior(db, DM9000_MRCMDX); /* Dummy read */
/*获得数据*/
rxbyte = readb(db->io_data);
/*因为只能为0x00或0x01,所以如果大于0x01,则返回*/
if (rxbyte > DM9000_PKT_RDY) {
dev_warn(db->dev, "status check fail: %d/n", rxbyte);
/*停止设备*/
iow(db, DM9000_RCR, 0x00);
/*停止中断请求*/
iow(db, DM9000_ISR, IMR_PAR);
return;
}
/*如果为0x00,则返回*/
if (rxbyte != DM9000_PKT_RDY)
return; /*如果有有效数据包,设置标志标量*/
GoodPacket = true;
/*MRCMD是地址增加的内存数据读命令*/
writeb(DM9000_MRCMD, db->io_addr);
/*读取RX SRAM中的数据放入struct dm9000_rxhdr中*/
(db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr));
/*将一个无符号的26位小头数值转换成CPU使用的值*/
RxLen = le16_to_cpu(rxhdr.RxLen); if (netif_msg_rx_status(db))
dev_dbg(db->dev, "RX: status %02x, length %04x/n",
rxhdr.RxStatus, RxLen); /*一个数据包的长度应大于64字节*/
if (RxLen < 0x40) {
GoodPacket = false;
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "RX: Bad Packet (runt)/n");
}
/*一个数据包的长度应小于1536字节*/
if (RxLen > DM9000_PKT_MAX) {
dev_dbg(db->dev, "RST: RX Len:%x/n", RxLen);
} /* rxhdr.RxStatus is identical to RSR register. */
if (rxhdr.RxStatus & (RSR_FOE | RSR_CE | RSR_AE |
RSR_PLE | RSR_RWTO |
RSR_LCS | RSR_RF)) {
GoodPacket = false;
if (rxhdr.RxStatus & RSR_FOE) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "fifo error/n");
dev->stats.rx_fifo_errors++;
}
if (rxhdr.RxStatus & RSR_CE) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "crc error/n");
dev->stats.rx_crc_errors++;
}
if (rxhdr.RxStatus & RSR_RF) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "length error/n");
dev->stats.rx_length_errors++;
}
} /* Move data from DM9000 */
if (GoodPacket
/*分配一个套接字缓冲区*/
&& ((skb = dev_alloc_skb(RxLen + )) != NULL)) {
skb_reserve(skb, );
rdptr = (u8 *) skb_put(skb, RxLen - ); /**/
(db->inblk)(db->io_data, rdptr, RxLen);
dev->stats.rx_bytes += RxLen; /*以太网支持代码导出了辅助函数eth_type_trans,用来查找填入protocol中的正确值*/
skb->protocol = eth_type_trans(skb, dev);
/*将套接字缓冲区发向上层*/
netif_rx(skb);
/*增加发送包的引用计数*/
dev->stats.rx_packets++; } else {
/*如果该数据包是坏的,则清理该数据包*/
(db->dumpblk)(db->io_data, RxLen);
}
} while (rxbyte == DM9000_PKT_RDY);
}

  如下图链路层包的传输过程

  

  在netif_rx中会调用napi_schedule,然后该函数又会去调用__napi_schedule()。

  在函数__napi_schedule()中会去调用设备的poll函数,它是设备自己注册的。

  在设备的poll函数中,会去调用netif_receive_skb函数,在该函数中有下面一条语句pt_prev->func,此处的func为一个函数指针,在之前的注册中设置为ip_rcv。

  因此,就完成了从链路层上传到网络层的这一个过程了。


参考博客

  文章出自李大鹏,源博文已被博主删掉,以下是博主博客地址

  http://blog.csdn.net/woshixingaaa

【驱动】DM9000A网卡驱动框架源码分析的更多相关文章

  1. YII框架源码分析(百度PHP大牛创作-原版-无广告无水印)

           YII 框架源码分析    百度联盟事业部——黄银锋 目 录 1. 引言 3 1.1.Yii 简介 3 1.2.本文内容与结构 3 2.组件化与模块化 4 2.1.框架加载和运行流程 4 ...

  2. 介绍开源的.net通信框架NetworkComms框架 源码分析

    原文网址: http://www.cnblogs.com/csdev Networkcomms 是一款C# 语言编写的TCP/UDP通信框架  作者是英国人  以前是收费的 售价249英镑 我曾经花了 ...

  3. Android Small插件化框架源码分析

    Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ...

  4. Spark RPC框架源码分析(一)简述

    Spark RPC系列: Spark RPC框架源码分析(一)运行时序 Spark RPC框架源码分析(二)运行时序 Spark RPC框架源码分析(三)运行时序 一. Spark rpc框架概述 S ...

  5. Spark RPC框架源码分析(二)RPC运行时序

    前情提要: Spark RPC框架源码分析(一)简述 一. Spark RPC概述 上一篇我们已经说明了Spark RPC框架的一个简单例子,Spark RPC相关的两个编程模型,Actor模型和Re ...

  6. Spark RPC框架源码分析(三)Spark心跳机制分析

    一.Spark心跳概述 前面两节中介绍了Spark RPC的基本知识,以及深入剖析了Spark RPC中一些源码的实现流程. 具体可以看这里: Spark RPC框架源码分析(二)运行时序 Spark ...

  7. nodejs的Express框架源码分析、工作流程分析

    nodejs的Express框架源码分析.工作流程分析 1.Express的编写流程 2.Express关键api的使用及其作用分析 app.use(middleware); connect pack ...

  8. laravel框架源码分析(一)自动加载

    一.前言 使用php已有好几年,laravel的使用也是有好长时间,但是一直对于框架源码的理解不深,原因很多,归根到底还是php基础不扎实,所以源码看起来也比较吃力.最近有时间,所以开启第5.6遍的框 ...

  9. iOS常用框架源码分析

    SDWebImage NSCache 类似可变字典,线程安全,使用可变字典自定义实现缓存时需要考虑加锁和释放锁 在内存不足时NSCache会自动释放存储的对象,不需要手动干预 NSCache的key不 ...

  10. 【Redis】事件驱动框架源码分析

    aeEventLoop初始化 在server.c文件的initServer函数中,对aeEventLoop进行了初始化: 调用aeCreateEventLoop函数创建aeEventLoop结构体,对 ...

随机推荐

  1. Redis持久化之RDB与AOF

    1. Redis的持久化方式 Redis作为高效的缓存件,它的数据存放在内存中,如果没有配置持久化,那么数据会在重启后丢失,因此如果不是仅用Redis做缓存的话,需要开启Redis的持久化功能,将数据 ...

  2. iOS中消息传递方式

    iOS中消息传递方式 在iOS中有很多种消息传递方式,这里先简单介绍一下各种消息传递方式. 1.通知:在iOS中由通知中心进行消息接收和消息广播,是一种一对多的消息传递方式. NSNotificati ...

  3. iphone3g 蜂窝数据有效设置

    iphone3g 蜂窝数据有效设置 蜂窝数据     APN cmnet/空     用户名 空     A密码 空彩信(默认为空,需要控制的话,可以设置)     APN cmwap/空     用 ...

  4. 如何创建magento模块z之Hello World例子(转)

    步骤:1.创建一个Hello World模块2.为这个模块配置路由3.为这个模块创建执行控制器 创建Hello World模块 创建模块的结构目录:app/core/local/Sjolzy/Hell ...

  5. mysql基本语句集合

    一.概述 1.删除字段 alter table {table_name} drop column {column_name}; 2.添加字段 alter table {table_name} Add ...

  6. Maven运行Selenium报错org/w3c/dom/ElementTraversal

    项目使用maven 构建,并且使用集成测试(integration-test). 在命令行中运行mvn clean deploy tomcat:undeploy时,报错: java.lang.NoCl ...

  7. 【C语言】字符串与整型数值之间的转换

    一.将字符串转化为对应的数值 /*============================================================================= # # F ...

  8. Kafka生产环境中的错误

    最近在处理日志收集任务时,发现前端服务器用flume进行收集,逐步把所有的服务器都增加上.增加的差不多时.Kafka报了如下类似错误: 709 [main] WARN kafka.producer.a ...

  9. bootstrap-table 的 toolbar 能去掉显示吗?

    我想禁用所有的toolbar,因为我显示的要求很简单,所以不想要所有的toolbar,这样可以省掉一行,但找不到方法.谢谢! data-show-columns="false"就行 ...

  10. MongoDB学习笔记(2)

    MongoDB 创建数据库 语法 MongoDB 创建数据库的语法格式如下: use DATABASE_NAME 如果数据库不存在,则创建数据库,否则切换到指定数据库. 实例 以下实例我们创建了数据库 ...