【驱动】DM9000网卡驱动分析

时间:2020-11-30 17:12:50

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内核是一类复杂的设备驱动,学习的时候要立足从网络协议入手,需要了解网络协议和内核协议栈工作流程。