Preface

   内核源码版本:linux-2.6.18

   网卡驱动·linux内核网络分层结构http://infohacker.blog.51cto.com/6751239/1221140

 


DM9000芯片

DM9000是一款高度集成低功耗快速以太网处理器,该芯片集成了MAC和PHY。DM9000可以和CPU直接连接,支持8位、16位和32位数据总线宽度。该芯片支持10M和100M自适应以太网接口,内部有16K的FIFO以及4K双字节SRAM,支持全双工工作。

DM9000内部还集成了接收缓冲区,可以在接收到数据的时候把数据存放到缓冲区中,链路层可以直接把数据从缓冲区取走。


网卡驱动程序框架

在一个网络驱动程序中,一般都提供了一个platform_driver结构变量。

platform_driver结构包括了网卡驱动的相关操作函数,通过platform_driver_register()函数注册到内核设备驱动列表。

内核会根据驱动程序中设备描述设置网卡的中断和定时器,并且在网络数据包到来的时候调用网卡对应的处理函数。

通常,网卡需要向内核提供下面几个接口函数:

    • probe:加载网卡驱动的时候执行,主要用于初始化网卡硬件接口,设置网络接口函数;

    • remove:卸载网卡驱动的时候执行该函数,用于从系统中注销网络接口函数;

    • suspend:在挂起网络设备的时候被调用;

    • resume:在恢复网络设备的时候被调用。

网络设备驱动主要是按照内核网络数据包处理流程中用到的数据结构,设置对应的处理函数供内核使用。


DM9000网卡驱动主要数据结构

DM9000网卡驱动位于driver/net/dm9000.c文件,有两个主要的数据结构dm9000_driver和board_info。其中,dm9000_driver是platform_driver结构。

static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000", //网卡名称
.owner = THIS_MODULE,
},
.probe = dm9000_probe, //加载驱动函数
.remove = dm9000_drv_remove, //删除驱动函数
.suspend = dm9000_drv_suspend, //挂起驱动函数
.resume = dm9000_drv_resume, //恢复驱动函数
};
  • dm9000_probe()函数在加载驱动的时候被内核调用,用于检测mqh上设备并且分配资源,设置网络接口控制器;

  • dm9000_drv_remove()函数在卸载驱动的时候被调用,用于释放网卡驱动占用的资源;

  • dm9000_drv_suspend()函数在挂起网卡的时候被调用,该函数会暂时删除网络口;

  • dm9000_drv_resume()函数在恢复网卡接口时被调用,该函数重新加载网络接口。

DM9000网卡驱动还设置了供DM9000网络控制芯片使用的 board_info结构

/* 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;//网卡物理地址
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 resource*addr_res; /* resources found */
struct resource *data_res;
struct resource*addr_req; /* resources requested */
struct resource *data_req;
struct resource *irq_res;
struct timer_list timer;
struct net_device_stats stats;
unsigned char srom[128];//网络控制器内部EEPROM内容
spinlock_t lock;
struct mii_if_info mii;
u32 msg_enable;
} board_info_t;

board_info结构存放在 net_device结构的私有数据部分,DM9000驱动的接口处理函数会使用该结构访问网络控制芯片。

在 board_info结构中,

  • io_addrio_data成员变量存放了控制寄存器和数据寄存器地址;

  • tx_pkt_cnt记录了发送数据包个数;

  • queue_pkt_len记录了发送队列中数据包个数;

  • queue_start_addr记录了数据包发送队列的起始地址;

  • phy_addr是网卡的物理地址;

  • srom是一个组,记录了DM9000网络控制芯片内部EEPROM的内容。


加载驱动程序

在dm9000.c文件中使用模块加载宏和卸载宏设置了模块的初始化函数dm9000_init()和卸载函数dm9000_cleanup()。

static int __init
dm9000_init(void)
{
printk(KERN_INFO "%s Ethernet Driver\n", CARDNAME); //打印模块启动信息
return platform_driver_register(&dm9000_driver); /* search board and register */ //调用驱动注册函数
}
static void __exit
dm9000_cleanup(void)
{
platform_driver_unregister(&dm9000_driver); //调用驱动卸载函数
}
module_init(dm9000_init); //设置模块启动函数
module_exit(dm9000_cleanup); //设置模块卸载函数

   设置好驱动函数的初始化后,在启动的时候会注册 dm9000_driver结构到内核,内核会调用 dm9000 driver结构中的 probe函数成员,也就是调用 dm9000_probe()函数设置网卡驱动。

函数执行过程如下。


步骤一

函数首先是分配 board_info结构占用的私有资源,在程序中使用 alloc_etherdev()函数分配网卡驱动使用的私有资源。

如果分配资源失败,提示出错wyth 并且退出函数,设置返回值为 -ENOMEM,表示没有内存。

static int
dm9000_probe(struct platform_device *pdev)
{
struct dm9000_plat_data *pdata = pdev->dev.platform_data;
struct board_info *db; /* Point a board information structure */
struct net_device *ndev;
unsigned long base;
int ret = 0;
int iosize;
int i;
u32 id_val;
/* Init network device */
ndev = alloc_etherdev(sizeof (struct board_info)); //分配资源,在私有数据区保存 board_info内容
if (!ndev) {
printk("%s: could not allocate device.\n", CARDNAME);
return -ENOMEM;
}
SET_MODULE_OWNER(ndev); //该宏是空定义,被忽略,下面一行相同
SET_NETDEV_DEV(ndev, &pdev->dev);
PRINTK2("dm9000_probe()");

步骤二

分配资源成功后,开始初始化 board_info结构,设置结构的值为0,然后初始化 spin_lock,spin_lock称做自旋锁,是内核中用于临界资源的一种结构。

初始化自旋锁以后,程序分配网络适配器 I/O地址寄存器、数据寄存器用到的内存,并且映射到内核空间。

/* setup board info structure */
db = (struct board_info *) ndev->priv;
memset(db, 0, sizeof (*db)); //初始化 board_info结构为0
spin_lock_init(&db->lock); //初始化 spin_lock
if (pdev->num_resources < 2) { //检查是否安装多个网络适配器
ret = -ENODEV;
goto out;
} else if (pdev->num_resources == 2) {
base = pdev->resource[0].start;
if (!request_mem_region(base, 4, ndev->name)) { //分配网络适配器结构占用的内存
ret = -EBUSY;
goto out;
}
ndev->base_addr = base; //设置网络适配器 I/O基址
ndev->irq = pdev->resource[1].start; //设置网络适配器中断地址
db->io_addr = (void __iomem *)base; //设置网络适配器地址寄存器基址
db->io_data = (void __iomem *)(base + 4); //设置网络适配器数据寄存器基址
} else {
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取 I/O地址
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //获取 IRQ地址
if (db->addr_res == NULL || db->data_res == NULL ||
db->irq_res == NULL) { //检查网络适配器用到的内存地址是否有效
printk(KERN_ERR PFX "insufficient resources\n");
ret = -ENOENT;
goto out;
}
i = res_size(db->addr_res); //计算地址寄存器空间
db->addr_req = request_mem_region(db->addr_res->start, i,
pdev->name); //请求内存地址
if (db->addr_req == NULL) { //检查地址寄存器是否有效
printk(KERN_ERR PFX "cannot claim address reg area\n");
ret = -EIO;
goto out;
}
db->io_addr = ioremap(db->addr_res->start, i); //映射网络适配器 I/O地址
if (db->io_addr == NULL) { //检查网络适配器 I/O地址是否有效
printk(KERN_ERR "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) { //检查数据寄存器地址是否有效
printk(KERN_ERR PFX "cannot claim data reg area\n");
ret = -EIO;
goto out;
}
db->io_data = ioremap(db->data_res->start, iosize); //映射网络适配器数据寄存器地址
if (db->io_data == NULL) { //检查 I/O地址是否有效
printk(KERN_ERR "failed to ioremap data reg\n");
ret = -EINVAL;
goto out;
}
/* fill in parameters for net-dev structure */ //填充网络设备数据结构
ndev->base_addr = (unsigned long)db->io_addr; //设置网络设备 I/O地址
ndev->irq = db->irq_res->start; //设置IRQ
/* ensure at least we have a default set of IO routines */
dm9000_set_io(db, iosize); //设置DM9000寄存器地址
}

步骤三

   检查是否需要继承系统提供的函数。

   初始化网络适配器数据结构后,需要设置网络设备用到的回调函数。

/* check to see if anything is being over-ridden */ //检查是否有继承的数据
if (pdata != NULL) {
/* check to see if the driver wants to over-ride the
* default IO width */ //检查是否继承默认 I/O宽度
if (pdata->flags & DM9000_PLATF_8BITONLY) // 8位 I/O位宽
dm9000_set_io(db, 1);
if (pdata->flags & DM9000_PLATF_16BITONLY) // 16位 I/O位宽
dm9000_set_io(db, 2);
if (pdata->flags & DM9000_PLATF_32BITONLY) // 32位 I/O位宽
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; // dump()函数
}

步骤四

   设置DM9000网络适配器芯片ID。

dm9000_reset(db);   //复位DM9000网络控制芯片
/* try two times, DM9000 sometimes gets the first read wrong */
for (i = 0; i < 2; i++) { //读取芯片 ID,需要读两次,这是芯片的一个Bug
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;
printk("%s: read wrong id 0x%08x\n", CARDNAME, id_val);
}
if (id_val != DM9000_ID) { //检查芯片 ID是否正确
printk("%s: wrong id: 0x%08x\n", CARDNAME, id_val);
goto release;
}

这里的for循环从DM9000网络控制器的寄存器两次读取芯片ID,这样做的目的是因为芯片的Bug,第一次读取的芯片ID是错误的,需要再读取一次。

读取芯片ID结束后,程序验证芯片ID是否正确,如果不正确,跳转到release标签,清空分配的内存,退出函数。


步骤五

   设置网络适配器用到的回调函数。

/* from this point we assume that we have found a DM9000 */
/* driver system function */
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->get_stats = &dm9000_get_stats;//设置获取网卡信息函数
ndev->set_multicast_list = &dm9000_hash_table;
#ifdef CONFIG_NET_POLL_CONTROLLER
ndev->poll_controller = &dm9000_poll_controller;
#endif

程序中使用 ether_setup()函数设置了网卡的驱动描述结构,然后设置网卡打开、接收数据包、发送数据包超时处理函数。


步骤六

设置回调函数后,设置MII接口,MII接口不是所有的处理器都支持,设置的目的是供支持MII接口的处理器使用。

#ifdef DM9000_PROGRAM_EEPROM
program_eeprom(db); //更新网络控制器内部EEPROM
#endif
db->msg_enable = NETIF_MSG_LINK; //设置MII接口
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; // MII方式读函数
db->mii.mdio_write = dm9000_phy_write; // MII方式写函数
/* Read SROM content */
for (i = 0; i < 64; i++) //读取 EEPROM内容,每次读 16b
((u16 *) db->srom)[i] = read_srom_word(db, i);
/* Set Node Address */
for (i = 0; i < 6; i++) // EEPROM的前6个字节是MAC地址,存放到 board_info结构内
ndev->dev_addr[i] = db->srom[i];
if (!is_valid_ether_addr(ndev->dev_addr)) { //验证MAC地址是否合法
/* try reading from mac */
for (i = 0; i < 6; i++) //如果是不合法地址,则从DM9000内部寄存器中重新读取 MAC地址
ndev->dev_addr[i] = ior(db, i+DM9000_PAR);
}
if (!is_valid_ether_addr(ndev->dev_addr)) //两次验证MAC地址
printk("%s: Invalid ethernet MAC address. Please "
"set using ifconfig\n", ndev->name);
platform_set_drvdata(pdev, ndev);
ret = register_netdev(ndev); //注册网络设备驱动
if (ret == 0) { //打印网卡信息
printk("%s: dm9000 at %p,%p IRQ %d MAC: ",
ndev->name, db->io_addr, db->io_data, ndev->irq);
for (i = 0; i < 5; i++)
printk("%02x:", ndev->dev_addr[i]);
printk("%02x\n", ndev->dev_addr[5]);
}
return 0;

程序读取 EEPROM的内容到 board_info结构,每次读取16b数据。读取结束后,再读取 EEPROM的前6个字节,该处存放了网卡的 MAC地址。

读取 MAC地址后,使用 is_valid_ether_addr()函数验证 MAC地址是否正确,如果不正确,则从DM9000网络控制器的寄存器中重新读取 MAC地址,然后两次比较,如果 MAC地址还是错误,提示用户使用 ifconfig命令设置 MAC地址。

验证 MAC地址结束后,程序使用 register_netdev()函数注册网络驱动到内核。如果注册成功,则打印网卡的基本信息。


步骤七

   出错处理。

函数的最后是出错处理,使用 releaseout标号标记,对应不同的处理流程。函数在执行过程中,可以使用 goto语言直接转到出错处理代码。

   出错处理的主要功能是释放网络适配器用到的数据结构,然后返回出错值。

release:
out:
printk("%s: not found (%d).\n", CARDNAME, ret);
dm9000_release_board(pdev, db);
kfree(ndev);
return ret;
}

停止网卡

停止网卡是用户使用 ifdown命令设置网卡暂时停止,用户的命令通过系统调用最终会调用网卡驱动的停止函数,对于 DM9000网卡驱动来说 dm9000_stop()函数

static void
dm9000_shutdown(struct net_device *dev)
{
board_info_t *db = (board_info_t *) dev->priv;
/* RESET device */
dm9000_phy_write(dev, 0, MII_BMCR, BMCR_RESET); /* PHY RESET */ //重启 PHY
iow(db, DM9000_GPR, 0x01); /* Power-Down PHY */ //关闭 PHY
iow(db, DM9000_IMR, IMR_PAR); /* Disable all interrupt */ //屏蔽所有中断
iow(db, DM9000_RCR, 0x00); /* Disable RX */ //停止接收数据包
}
/*
* Stop the interface.
* The interface is stopped when it is brought.
*/
static int
dm9000_stop(struct net_device *ndev)
{
board_info_t *db = (board_info_t *) ndev->priv;
PRINTK1("entering %s\n",__FUNCTION__);
/* deleted timer */
del_timer(&db->timer); //删除定时器
netif_stop_queue(ndev); //停止数据包发送队列
netif_carrier_off(ndev);
/* free interrupt */
free_irq(ndev->irq, ndev); //释放所有中断请求
dm9000_shutdown(ndev);
return 0;
}

函数首先删除网络驱动的定时器,然后停止数据包发送队列工作、释放中断请求,最后调用 dm9000_shutdown()函数。

   dm9000_shutdown()函数的作用是重启设备,然后通过设置网络控制器的控制寄存器关闭芯片的部分电源和中断,并且停止接收数据包。


启动网卡

与关闭网卡相反,用户使用 ifup命令可以启动一个网卡,内核会调用一个网卡的启动函数。

DM9000网卡的 dm9000_open()函数供内核在启动网卡时调用。

/*
* Open the interface.
* The interface is opened whenever "ifconfig" actives it.
*/
static int
dm9000_open(struct net_device *dev)
{
board_info_t *db = (board_info_t *) dev->priv;
PRINTK2("entering dm9000_open\n");
if (request_irq(dev->irq, &dm9000_interrupt, IRQF_SHARED, dev->name, dev))
return -EAGAIN;
/* Initialize DM9000 board */
dm9000_reset(db); //重启网络控制芯片
dm9000_init_dm9000(dev); //初始化网络控制芯片
/* Init driver variable */
db->dbug_cnt = 0;
/* set and active a timer process */
init_timer(&db->timer); //初始化定时器
db->timer.expires = DM9000_TIMER_WUT; //设置超时值
db->timer.data = (unsigned long) dev;
db->timer.function = &dm9000_timer; //设置超时处理函数
add_timer(&db->timer); //添加定时器
mii_check_media(&db->mii, netif_msg_link(db), 1); //检查 MII接口
netif_start_queue(dev); //启动包发送队列
return 0;
}

程序中使用 request_irq()函数申请中断请求,然后重启网络控制芯片,然后调用 dm9000_init_dm9000()函数初始化网络控制芯片。

网络控制芯片设置完毕后,程序初始化定时器,然后设置定时器超时值,添加定时器超时处理函数。

程序最后检查MII接口,然后调用 netif_start_queue()函数启动包发送队列。


发送数据包

网卡驱动程序需要向内核提供两个发送数据包的回调函数,一个用于发送数据包,一个用于数据包发送完毕后的处理。

DM9000向内核提供 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)
{
board_info_t *db = (board_info_t *) dev->priv;
PRINTK3("dm9000_start_xmit\n");
if (db->tx_pkt_cnt > 1)
return 1;
netif_stop_queue(dev); //停止接收队列
/* Disable all interrupts */
iow(db, DM9000_IMR, IMR_PAR); //关闭所有中断
/* Move data to DM9000 TX RAM */
writeb(DM9000_MWCMD, db->io_addr); //设置网卡控制器的控制寄存器
(db->outblk)(db->io_data, skb->data, skb->len); //复制 sk_buff的数据到网卡控制器的 SRAM
db->stats.tx_bytes += skb->len; //发送字节数统计加上当前数据包长度
/* TX control: First packet immediately send, second packet queue */
if (db->tx_pkt_cnt == 0) { //判断是否第一次发送数据包
/* First Packet */
db->tx_pkt_cnt++; //发送数据包总结加1
/* Set TX length to DM9000 */
iow(db, DM9000_TXPLL, skb->len & 0xff); //设置 DM9000的发送数据长度寄存器
iow(db, DM9000_TXPLH, (skb->len >> 8) & 0xff);
/* Issue TX polling command */
iow(db, DM9000_TCR, TCR_TXREQ); /* Cleared after TX complete */ //设定发送请求
dev->trans_start = jiffies; /* save the time stamp */ //写入发送数据包的时间戳
} else {
/* Second packet */
db->tx_pkt_cnt++;
db->queue_pkt_len = skb->len;
}
/* free this SKB */
dev_kfree_skb(skb); //释放数据包缓存
/* Re-enable resource check */
if (db->tx_pkt_cnt == 1) //检查第一个包是否发送完毕
netif_wake_queue(dev); //如果发送完毕可以重启接收队列
/* Re-enable interrupt */
iow(db, DM9000_IMR, IMR_PAR | IMR_PTM | IMR_PRM); //打开 DM9000中断,如果此时数据包成功发送,
//会收到 DM9000发送的中断,转到驱动的中断处理函数进行处理
return 0;
}

   发送数据包的流程需要考虑到内核数据包队列和中断控制器。

程序首先使用 netif_stop_queue()函数停止接收队列,该队列是内核与网卡驱动之间的数据包队列,内核把发送的数据包放到队列中,网卡驱动从队列中取出数据包进行发送。

关闭队列后,程序操作 DM9000的控制寄存器,关闭中断请求,目的是防止在发送数据包的过程中被打断,因为内核的代码都是可重入的,这点需要注意。

程序第16行设置 DM9000的控制寄存器通知 DM9000开始内存复制,然后把发适的数据包 sk_buff中的内容复制到 DM9000内部的SRAM,然后更新网卡发送字节统计。

程序进行发送流程时,首先通过 tx_pkt_cnt变量判断是否发送第一个数据包,DM9000的驱动设计第一个数据包可以被发送,第二个数据包通过 dm9000_tx_done()函数发送。如果发送的是第一数据包,则程序把发送数据包个数加1,通过设置 DM9000控制寄存器,通知发送数据包长度,然后向 DM9000写入发送命令。

设置发送数据包后,可以认为数据包已经发送出去,而发送的状态需要通过中断得到。接下来,程序释放已经发送数据包的 sk_buff,然后检查 tx_pkt_cnt,判断数据包是否已经发送。如果数据包已经发送,则通过 netif_wake_queue()函数重新开启接收队列。

最后,在程序中写入 DM9000的命令打开中断响应,如果数据包已经发送,驱动程序会收到 DM9000控制器发送的中断。

数据包发送完毕后,内核会调用后续的处理函数,DM9000驱动程序提供了 dm9000_tx_done()函数

/*
* 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)
{
int tx_status = ior(db, DM9000_NSR); /* Got TX status */
if (tx_status & (NSR_TX2END | NSR_TX1END)) { //判断是否已经有一个数据包发送完毕
/* One packet sent complete */
db->tx_pkt_cnt--;
db->stats.tx_packets++;
/* Queue packet check & send */
if (db->tx_pkt_cnt > 0) { //判断缓冲区是否有未发送的数据包
iow(db, DM9000_TXPLL, db->queue_pkt_len & 0xff); //设置发送数据包长度
iow(db, DM9000_TXPLH, (db->queue_pkt_len >> 8) & 0xff);
iow(db, DM9000_TCR, TCR_TXREQ); //启动数据包发送
dev->trans_start = jiffies;
}
netif_wake_queue(dev); //通知内核开启接收队列
}
}

程序首先判断是否已经有一个数据包被成功发送,如果已经有数据包功能发送,则进入第二个数据包处理。程序通过判断缓冲区是否有未发送的数据包,如果有,则通知 DM9000控制器数据包的长度,然后写入命令发送数据包。

数据包发送完毕后,程序开启内核接收数据包队列。


接收数据包

DM9000向内核提供了dm9000_rx()函数,在内核收到DM9000网络控制器的接收数据包中断后被内核调用。dm9000_rx()函数使用了一个自定义的dm9000_rxhdr结构,该结构与DM9000网络控制器提供的数据包接收信息对应。

struct dm9000_rxhdr {
u16 RxStatus;
u16 RxLen;
} __attribute__((__packed__));
/*
* Received a packet and pass to upper layer
*/
static void
dm9000_rx(struct net_device *dev)
{
board_info_t *db = (board_info_t *) dev->priv;
struct dm9000_rxhdr rxhdr;
struct sk_buff *skb;
u8 rxbyte, *rdptr;
int GoodPacket;
int RxLen;
/* Check packet ready or not */
do {
ior(db, DM9000_MRCMDX); /* Dummy read */
/* Get most updated data */
rxbyte = readb(db->io_data); //读取网络控制器状态
/* Status check: this byte must be 0 or 1 */
if (rxbyte > DM9000_PKT_RDY) { //判断状态是否正确
printk("status check failed: %d\n", rxbyte);
iow(db, DM9000_RCR, 0x00); /* Stop Device */ //停止网络控制器
iow(db, DM9000_ISR, IMR_PAR); /* Stop INT request */ //停止中断请求
return;
}
if (rxbyte != DM9000_PKT_RDY)
return;
/* A packet ready now & Get status/length */
GoodPacket = TRUE;
writeb(DM9000_MRCMD, db->io_addr); //向控制器发起读命令
(db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr)); //读取包头
RxLen = rxhdr.RxLen; //读取包长度
/* Packet Status check */
if (RxLen < 0x40) { //判断数据包是否小于64字节
GoodPacket = FALSE;
PRINTK1("Bad Packet received (runt)\n");
}
if (RxLen > DM9000_PKT_MAX) { //判断数据包是否超过 1536字节
PRINTK1("RST: RX Len:%x\n", RxLen);
}
if (rxhdr.RxStatus & 0xbf00) { //检查接收状态是否出错
GoodPacket = FALSE;
if (rxhdr.RxStatus & 0x100) { // FIFO 错误
PRINTK1("fifo error\n");
db->stats.rx_fifo_errors++;
}
if (rxhdr.RxStatus & 0x200) { // CRC 错误
PRINTK1("crc error\n");
db->stats.rx_crc_errors++;
}
if (rxhdr.RxStatus & 0x8000) { // 包长度错误
PRINTK1("length error\n");
db->stats.rx_length_errors++;
}
}
/* Move data from DM9000 */
if (GoodPacket
&& ((skb = dev_alloc_skb(RxLen + 4)) != NULL)) { //分配 sk_buff
skb->dev = dev;
skb_reserve(skb, 2);
rdptr = (u8 *) skb_put(skb, RxLen - 4);
/* Read received packet from RX SRAM */
(db->inblk)(db->io_data, rdptr, RxLen); //所数据包从 DM9000控制器复制到 sk_buff
db->stats.rx_bytes += RxLen; //更新包计数器
/* Pass to upper layer */
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);
db->stats.rx_packets++;
} else {
/* need to dump the packet's data */
(db->dumpblk)(db->io_data, RxLen);
}
} while (rxbyte == DM9000_PKT_RDY); //判断网络控制器处理准备好状态
}

程序中 dm9000_rxhdr结构的 RxStatus成员变量存放接收数据包的状态,RxLen存放接收到的数据包长度。dm9000_rx()函数内部是一个大的 do……while{}循环。

从18行开始,首先获取网络控制器状态,然后判断网络控制器状态是否正确。如果网络控制器状态不正确,则停止网络控制器,并且屏蔽中断请求。

如果网络控制器处理"准备好"的状态,则向网络控制器发起读数据包命令。

程序从34行读取数据包头,然后取出包长。再判断数据包长度是否小于64字节,因为以太网协议规定,小于64字节的数据包是错误的。

在从网络控制器接收数据包内容之前,程序首先在使用 dev_alloc_skb()函数分配了一个 sk_buff缓冲区,用于存放数据包,然后把数据包从网络控制器的 SRAM复制到 sk_buff,再更新字节计数器。

新的数据包收到后,就可以通知上层协议栈处理了,程序使用 eth_type_trans()函数把数据包丢给协议栈,然后更新包计数器。


中断和定时器处理

网络设备驱动需要提供中断处理函数和定时处理函数供内核使用。

  • 中断处理函数当网络控制器向CPU发出中断后,由内核中断处理函数调用。

  • 定时器处理函数是由内核的一个定时器周期的调用。

DM9000网卡驱动设计了 dm9000_interrupt()函数响应网络控制发送的中断请求。

static irqreturn_t
dm9000_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct net_device *dev = dev_id;
board_info_t *db;
int int_status;
u8 reg_save;
PRINTK3("entering %s\n",__FUNCTION__);
if (!dev) { //检查网络设备是否存在
PRINTK1("dm9000_interrupt() without DEVICE arg\n");
return IRQ_HANDLED;
}
/* A real interrupt coming */
db = (board_info_t *) dev->priv;
spin_lock(&db->lock); //对临界资源加锁
/* Save previous register address */
reg_save = readb(db->io_addr); //保存当前中断寄存器的值
/* Disable all interrupts */
iow(db, DM9000_IMR, IMR_PAR); //关闭所有中断请求
/* Got DM9000 interrupt status */
int_status = ior(db, DM9000_ISR); /* Got ISR */ //获取 ISR
iow(db, DM9000_ISR, int_status); /* Clear ISR status */ //清除 ISR状态
/* Received the coming packet */
if (int_status & ISR_PRS) //判断是否收到数据包中断
dm9000_rx(dev); //调用接收数据包函数处理
/* Trnasmit Interrupt check */
if (int_status & ISR_PTS) //判断是否发送数据包中断
dm9000_tx_done(dev, db); //调用发送数据包函数处理
/* Re-enable interrupt mask */
iow(db, DM9000_IMR, IMR_PAR | IMR_PTM | IMR_PRM); //打开中断请求
/* Restore previous register address */
writeb(reg_save, db->io_addr); //恢复中断处理前中断寄存器的值
spin_unlock(&db->lock); //对临界资源解锁
return IRQ_HANDLED;
}

   DM9000的中断处理函数只处理网络控制器发送的接收数据包和发送数据包请求。

进入函数后,程序街道检查内核传入的网络设备句柄是否合法,不合法则直接退出函数。如果是合法的网络设备句柄,则对网络设备加锁,防止其它例程处理。

然后取出当前中断寄存器的值保存,关闭中断请求,并处理 DM9000的ISR。

前面的工作都是建立中断处理的环境,接下在程序判断是否是接收到数据包中断,如果是则调用 dm9000_rx()函数接收数据包,再判断是否发送数据包中断,如果是则调用 dm9000_tx_done()函数进行处理。

处理完所有的中断以后,程序重新打开中断请求,然后恢复中断处理之前中断寄存器的值。最后对临界资源解锁,整个中断处理流程结束。


小结

在网络通信中,计算机通过网卡(包括网络控制器和网络接口)与其它网络节点通信。由于不同的网络有不同协议,网卡的设计不仅需要兼顾到网络上数据包的处理,还涉及主机网络协议栈的接口。

网卡驱动在linux内核是一类复杂的设备驱动,学习的时候要立足从网络协议入手,需要了解网络协议和内核协议栈工作流程。

【驱动】DM9000网卡驱动分析的更多相关文章

  1. Linux网卡驱动移植--Dm9000网卡驱动分析

    1. Linux网络体系结构由以下5部分组成 ① 系统调用接口: 位于Linux网络子系统的顶部,为应用程序提供访问内核网络子系统的方法,主要指socket系统调用. ② 协议无关接口: 实现一组基于 ...

  2. DM9000网卡驱动分析(转)

    s3c6410自带的DM9000网卡驱动也是基于platform设备模型. 其定义的设备资源在arch/arm/mach-s3c64xx/mach-smdk6410中.有网卡的resource res ...

  3. mini2440移植uboot-2008.10 (二) DM9000网卡驱动移植

    还是利用 mini2440移植uboot-2008.10 (一)  修改好的代码 通过观察可以发现,mini2400使用的网卡芯片是DM9000,在uboot-2008.10源码中已经支持该芯片的驱动 ...

  4. DM9000网卡驱动深度分析

    一.dm9000_porbe函数分析 不同于u-boot代码,tq2440中的DM9000更加复杂,需要分析的点也很多: /* * Search DM9000 board, allocate spac ...

  5. [国嵌攻略][136][DM9000网卡驱动深度分析]

    网卡初始化 1.分配描述结构,alloc_etherdev 2.获取平台资源,platform_get_resource 2.1.在s3c_dm9k_resource中有相关的资源 2.2.add地址 ...

  6. 【驱动】网卡驱动·linux内核网络分层结构

    Preface   Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计. Linux内核采用分层结构处理网络数据包.分层结构与网络协议的结构匹配,既能简化数据包处理流程 ...

  7. [中英对照]Device Drivers in User Space: A Case for Network Device Driver | 用户态设备驱动: 以网卡驱动为例

    前文初步介绍了Linux用户态设备驱动,本文将介绍一个典型的案例.Again, 如对Linux用户态设备驱动程序开发感兴趣,请阅读本文,否则请飘过. Device Drivers in User Sp ...

  8. 【Linux高级驱动】网卡驱动分析

    两个重要的结构体简单介绍 *sk_buff 如果把网络传输看成是运送货物的话,那么sk_buff就是这个“货物”了,所有经手这个货物的人都要干点什么事儿,要么加个包装,要么印个戳儿等等.收货的时候就要 ...

  9. DM9000网卡驱动接受数据从中断方式改成NAPI方式小记

    平台是最最经典的s3c2440了,说了要做这件事情很久了,就是改几行代码,一直没有做.前几天逼了自己一下,终于给做了,拖延症患者伤不起. 以下是需要读者对napi机制有所熟悉: step1:在boar ...

随机推荐

  1. shell 脚本启动tomcat服务

    #!/bin/bash # kill tomcat进程 tomcat_fashion_dev_pid=`ps aux|grep tomcat_fashion_dev|grep -v "gre ...

  2. 说说JSON和JSONP,也许你会豁然开朗,含jQuery使用jsonp用例

    [原创]说说JSON和JSONP,也许你会豁然开朗,含jQuery用例  前言: 由于Sencha Touch 2这种开发模式的特性,基本决定了它原生的数据交互行为几乎只能通过AJAX来实现. 当然了 ...

  3. 树莓派进阶之路 (031) -字符问题(1) - GBK汉字编码表(转)

    转载:http://blog.sina.com.cn/s/blog_8184e033010109ug.html   基本简介 GB码,全称是GB2312-80<信息交换用汉字编码字符集基本集&g ...

  4. Linux(Ubuntu12.04)上玩儿STC单片机(转)

    操作系统:Ubuntu16.04 TLS 单片机:STC的STC89C52RC系列及 Atmel的AT89S52... 所需工具: 1.编辑器- Vim(不钟情于IDE,个人喜好,高手勿喷) 2.编译 ...

  5. 树莓派 SD卡镜像备份

    一.准备工作 1.已经配置启动的树莓派SD卡 2.Linux系统的pc 3.读卡器 二.修改SD卡文件 刚开始我先再win7下把SD卡接到pc上,发现无法读取.用DISKGENIUS查看pc的磁盘状态 ...

  6. mvc 模型验证及正则表达式

    ASP.NET MVC3中的Model是自验证的,这是通过.NET4的System.ComponentModel.DataAnnotations命名空间完成的. 我们要做的只是给Model类的各属性加 ...

  7. C# 使用系统方法发送异步邮件

    项目背景: 最近在对几年前的一个项目进行重构,发现发送邮件功能需要一定的时间来处理,而由于发送是同步的因此导致在发送邮件时无法执行后续的操作 实际上发送邮件后只需要将发送结果写入系统日志即可对其他业务 ...

  8. idea设置tomcat虚拟路径的两种方法

    1.使用tomcat自己的虚拟路径 1.1.在tomcat\config\server.xml中配置 path="/upload" 虚拟路径 E:\photo\upload 图片存 ...

  9. SharePoint 2013怎样创建Wiki库

    们使用Wiki页面来分享知识,增进团队合作.下面我将向大家展示SharePoint 2013 Wiki的使用方法.教程我都将以这张Wiki页面(即当前页)为示例. 编辑页面 如要编辑页面,单击顶部Ed ...

  10. PHP位运算用途

    在实际应用中可以做用户权限的应用 我这里说到的权限管理办法是一个普遍采用的方法,主要是使用到”位运行符”操作,& 位与运算符.| 位或运行符.参与运算的如果是10进制数,则会被转换至2进制数参 ...