【驱动】DM9000A网卡驱动框架源码分析
Linux网络设备结构
首先看一下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);
如果数据包发送在超时时间内失败,这时该方法被调用,这个方法应该解决失败的问题,并重新开始发送数据。
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种:
- 接收到数据
- 发送完数据
- 链路状态改变
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网卡驱动框架源码分析的更多相关文章
- YII框架源码分析(百度PHP大牛创作-原版-无广告无水印)
YII 框架源码分析 百度联盟事业部——黄银锋 目 录 1. 引言 3 1.1.Yii 简介 3 1.2.本文内容与结构 3 2.组件化与模块化 4 2.1.框架加载和运行流程 4 ...
- 介绍开源的.net通信框架NetworkComms框架 源码分析
原文网址: http://www.cnblogs.com/csdev Networkcomms 是一款C# 语言编写的TCP/UDP通信框架 作者是英国人 以前是收费的 售价249英镑 我曾经花了 ...
- Android Small插件化框架源码分析
Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ...
- Spark RPC框架源码分析(一)简述
Spark RPC系列: Spark RPC框架源码分析(一)运行时序 Spark RPC框架源码分析(二)运行时序 Spark RPC框架源码分析(三)运行时序 一. Spark rpc框架概述 S ...
- Spark RPC框架源码分析(二)RPC运行时序
前情提要: Spark RPC框架源码分析(一)简述 一. Spark RPC概述 上一篇我们已经说明了Spark RPC框架的一个简单例子,Spark RPC相关的两个编程模型,Actor模型和Re ...
- Spark RPC框架源码分析(三)Spark心跳机制分析
一.Spark心跳概述 前面两节中介绍了Spark RPC的基本知识,以及深入剖析了Spark RPC中一些源码的实现流程. 具体可以看这里: Spark RPC框架源码分析(二)运行时序 Spark ...
- nodejs的Express框架源码分析、工作流程分析
nodejs的Express框架源码分析.工作流程分析 1.Express的编写流程 2.Express关键api的使用及其作用分析 app.use(middleware); connect pack ...
- laravel框架源码分析(一)自动加载
一.前言 使用php已有好几年,laravel的使用也是有好长时间,但是一直对于框架源码的理解不深,原因很多,归根到底还是php基础不扎实,所以源码看起来也比较吃力.最近有时间,所以开启第5.6遍的框 ...
- iOS常用框架源码分析
SDWebImage NSCache 类似可变字典,线程安全,使用可变字典自定义实现缓存时需要考虑加锁和释放锁 在内存不足时NSCache会自动释放存储的对象,不需要手动干预 NSCache的key不 ...
- 【Redis】事件驱动框架源码分析
aeEventLoop初始化 在server.c文件的initServer函数中,对aeEventLoop进行了初始化: 调用aeCreateEventLoop函数创建aeEventLoop结构体,对 ...
随机推荐
- Dockerfile 构建前端node应用cnpm命令启动nodejs服务
cat Dockerfile.node FROM centos MAINTAINER zha*****ch.cn ENV LANG en_US.UTF-8 RUN /bin/cp /usr/share ...
- Windows XPSP3通过网络级身份验证方式连接Windows Server 2008远程桌面
远程桌面大大方便了大家的日常管理工作,Windows Server 2008同样秉承这一优秀特性,并引入网络级身份验证(NLA)作为远程桌面连接的默认身份验证方式. 网络级身份验证 (NLA) 是一种 ...
- 关于iOS应用通过OpenURL在设置打开相机权限,然后原应用会立即崩溃(重启)的理解
最近偶然发现的一个怪现象: 应用需要相机权限,如果检测到没有开启权限,我们代码内,通过openURL跳转设置界面 //打开应用设置 let url:NSURL = NSURL(string: UIAp ...
- jeecg平台testDatagrid
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...
- webservice(草稿)
1. 概述 WebService是一种跨编程语言和跨操作系统平台的远程调用技术. Webservice是被定义用来使不同应用之间通过网络传输数据的一种标准,此标准和具体的语言无关,至于哪种语言提供接 ...
- Javascript将html转成pdf,下载(html2canvas 和 jsPDF)
最近碰到个需求,需要把当前页面生成pdf,并下载.弄了几天,自己整理整理,记录下来,我觉得应该会有人需要 :) 项目源码地址:https://github.com/linwalker/render-h ...
- ExpandoObject与DynamicObject的使用 RabbitMQ与.net core(一)安装 RabbitMQ与.net core(二)Producer与Exchange ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler) .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了
ExpandoObject与DynamicObject的使用 using ImpromptuInterface; using System; using System.Dynamic; names ...
- 验证时出错。HRESULT = '8000000A'
这本来是在VS2005下创建的一下项目,后来改用VS2010的开发环境,.NET Framework的版本号还是使用2.0, 但每次生成之后都会在解决方式的同级文件夹下产生一个名称乱码的文件夹, 攻克 ...
- asp.net C#取Excel 合并单元格内容
asp教程.net c#取excel 合并单元格内容读取excel数据,填充dataset// 连接字符串 string xlspath = server.mappath("~/www.11 ...
- 使用equals方法时,要注意
这是我在项目中犯的一个低级错误: 使用equals方法时,要注意这个方法是boolean java.lang.String.equals(Object anObject)传递的是Object,所以传任 ...