Linux下的网络设备驱动(二)

来源:互联网 发布:常用的预测数据方法 编辑:程序博客网 时间:2024/06/16 14:42

版权所有,转载必须说明转自 http://my.csdn.net/weiqing1981127 

原创作者:南京邮电大学  通信与信息系统专业 研二 魏清

 

三.网络设备驱动分析

我们先看看dm9000.c驱动的模块加载函数

static struct platform_driver dm9000_driver = {

       .driver     = {

              .name    = "dm9000",  //驱动名

              .owner    = THIS_MODULE,

              .pm  = &dm9000_drv_pm_ops, //电源相关操作

       },

       .probe   = dm9000_probe,  //探测函数

       .remove  = __devexit_p(dm9000_drv_remove),

};

static int __init  dm9000_init(void)

{

#if defined(CONFIG_ARCH_S3C2410)  //调整DM9000所用的位宽寄存器

       unsigned int oldval_bwscon = *(volatile unsigned int *)S3C2410_BWSCON;

       unsigned int oldval_bankcon4 = *(volatile unsigned int *)S3C2410_BANKCON4;

       *((volatile unsigned int *)S3C2410_BWSCON) =

                     (oldval_bwscon & ~(3<<16)) | S3C2410_BWSCON_DW4_16 | S3C2410_BWSCON_WS4 | S3C2410_BWSCON_ST4;

       *((volatile unsigned int *)S3C2410_BANKCON4) = 0x1f7c;

#endif

       printk(KERN_INFO "%s Ethernet Driver, V%s\n", CARDNAME, DRV_VERSION);

       return platform_driver_register(&dm9000_driver); //注册驱动

}

 

下面我们重点关注下probe探测函数

static int __devinit dm9000_probe(struct platform_device *pdev)

{

       struct dm9000_plat_data *pdata = pdev->dev.platform_data;  //获取平台数据

       struct board_info *db;   //网卡私有数据

       struct net_device *ndev; //网络设备结构体

       const unsigned char *mac_src;

       int ret = 0;

       int iosize;

       int i;

       u32 id_val;

       //分配并初始化net_device,把board_info作为net_device的私有数据

       ndev = alloc_etherdev(sizeof(struct board_info)); 

       if (!ndev) {

              dev_err(&pdev->dev, "could not allocate device.\n");

              return -ENOMEM;

       }

       //设置继承关系,使pdev->dev作为ndevparent

       SET_NETDEV_DEV(ndev, &pdev->dev);

       dev_dbg(&pdev->dev, "dm9000_probe()\n");

       db = netdev_priv(ndev);  //获取私有数据

       db->dev = &pdev->dev;  //设置父设备

       db->ndev = ndev;  

       spin_lock_init(&db->lock);  //初始化锁

       mutex_init(&db->addr_lock);  //初始化互斥锁

       INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);  //初始化工作队列

       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;

       }

       iosize = resource_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 = resource_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;  //中断号

       dm9000_set_io(db, iosize);  //根据DM9000数据位宽设置读写数据帧的函数指针

       if (pdata != NULL) {

              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);

              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_reset(db);  //DM9000进行复位

       for (i = 0; i < 8; i++) {  //读芯片ID

              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) {  //判断是否为0x90000A46

              dev_err(db->dev, "wrong id: 0x%08x\n", id_val);

              ret = -ENODEV;

              goto out;

       }

       id_val = ior(db, DM9000_CHIPR);

       dev_dbg(db->dev, "dm9000 revision 0x%02x\n", id_val);

       switch (id_val) { //设置DM000的具体类型

       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;

       }

       if (db->type == TYPE_DM9000A || db->type == TYPE_DM9000B) {

              db->can_csum = 1;

              db->rx_csum = 1;

              ndev->features |= NETIF_F_IP_CSUM;

       }

       ether_setup(ndev);  //net_device中部分成员进行初始化

       ndev->netdev_ops  = &dm9000_netdev_ops; //网络操作方法

       ndev->watchdog_timeo  = msecs_to_jiffies(watchdog); //看门狗时间

       ndev->ethtool_ops  = &dm9000_ethtool_ops;   //使用户空间支持ethtool这一工具

       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_src = "eeprom";

       for (i = 0; i < 6; i += 2)  //E2PROM中读取MAC地址

              dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i);

       if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {

              mac_src = "platform data";

              memcpy(ndev->dev_addr, pdata->dev_addr, 6);  //从平台数据中读取MAC地址

       }

       if (!is_valid_ether_addr(ndev->dev_addr)) {

              mac_src = "chip";

              for (i = 0; i < 6; i++)

                     ndev->dev_addr[i] = ior(db, i+DM9000_PAR);

       }

       memcpy(ndev->dev_addr, "\x08\x90\x90\x90\x90\x90", 6);  //直接给软MAC地址

       if (!is_valid_ether_addr(ndev->dev_addr))

              dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "

                      "set using ifconfig\n", ndev->name);

       platform_set_drvdata(pdev, ndev); //net_device作为平台数据

       ret = register_netdev(ndev);  //注册net_device

       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;

}

网络设备的probe函数跟字符设备的probe函数类似,上面这个probe函数主要完成DM9000的初始化工作,主要是获取并申请硬件资源和中断号、初始化net_device结构体,最后注册网络设备。主要是网络设备成员较多,导致这个probe看起来有些长而已。

在上面的探测函数中,我们注册了网络操作函数集合,跟踪下dm9000_netdev_ops的定义

static const struct net_device_ops dm9000_netdev_ops = {

       .ndo_open             = dm9000_open,  //打开

       .ndo_stop              = dm9000_stop, //关闭

       .ndo_start_xmit             = dm9000_start_xmit,  //发送

       .ndo_tx_timeout            = dm9000_timeout, //超时

       .ndo_set_multicast_list   = dm9000_hash_table,

       .ndo_do_ioctl         = dm9000_ioctl,

       .ndo_change_mtu          = eth_change_mtu,

       .ndo_validate_addr  = eth_validate_addr,

       .ndo_set_mac_address   = eth_mac_addr,  //设置MAC地址

#ifdef CONFIG_NET_POLL_CONTROLLER

       .ndo_poll_controller       = dm9000_poll_controller,

#endif

};

打开网络设备就是激活网络接口,使得它能够接收网络上的数据并传送到网络的协议栈上去,当然也可以将数据发送到网络上去。下面我们看看这个dm9000_open的实现

static int dm9000_open(struct net_device *dev)

{

       board_info_t *db = netdev_priv(dev); //获得net_device的私有数据

       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 (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;

       dm9000_reset(db);  //重置网卡

       dm9000_init_dm9000(dev); //初始化网卡,这里主要对DM9000寄存器进行设置

       db->dbug_cnt = 0;

       mii_check_media(&db->mii, netif_msg_link(db), 1);

       netif_start_queue(dev);//告诉上层网络协议该驱动还有空的缓冲区可用,即可接受数据

       dm9000_schedule_poll(db);

       return 0;

}

在上面这个open函数中,我们注册了一个中断处理函数,这个中断主要完成接收数据的功能。当网络上的数据到来时,将触发硬件中断,根据注册的中断向量表确定处理函数,进入中断向量处理函数,将数据送到上层协议进行处理或者转发出去。需要注意的是中断处理函数会根据中断的原因来调用不同的处理函数。硬件芯片可能因为三种情况而触发中断:硬件将一个外发数据包发送完成;一个新的数据包到达硬件;网络接口也能够产生中断来指示错误。对于以上三种中断对应的处理方式分别是:通知协议栈可以重启发送队列,即调用netif_wake_queue函数;调用接收函数;处理错误。下面我们看看这个中断处理函数dm9000_interrupt

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, 3, "entering %s\n", __func__);

       spin_lock_irqsave(&db->lock, flags); //关其他中断

       reg_save = readb(db->io_addr);

       iow(db, DM9000_IMR, IMR_PAR);

       int_status = ior(db, DM9000_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);

       if (int_status & ISR_PRS)  //接收中断

              dm9000_rx(dev);

       if (int_status & ISR_PTS)  //发送完毕产生的中断

              dm9000_tx_done(dev, db);

       if (db->type != TYPE_DM9000E) {

              if (int_status & ISR_LNKCHNG) {

                     schedule_delayed_work(&db->phy_poll, 1);

              }

       }

       iow(db, DM9000_IMR, db->imr_all);  //中断使能

       writeb(reg_save, db->io_addr);

       spin_unlock_irqrestore(&db->lock, flags);

       return IRQ_HANDLED;

}

现在我们主要来分析中断处理函数中对于接收中断的处理,也就是dm9000_rx的实现

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;

       do {//读取芯片相关寄存器,确定DM9000正确接收到一帧数据

              ior(db, DM9000_MRCMDX);      

              rxbyte = readb(db->io_data);

              if (rxbyte & DM9000_PKT_ERR) { //出错处理

                     dev_warn(db->dev, "status check fail: %d\n", rxbyte);

                     iow(db, DM9000_RCR, 0x00);     //停止DM9000工作

                     iow(db, DM9000_ISR, IMR_PAR);      //停止中断请求

                     return;

              }

              if (!(rxbyte & DM9000_PKT_RDY))

                     return;

              GoodPacket = true; //正确获取了一帧数据

              writeb(DM9000_MRCMD, db->io_addr);

              (db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr));  //接收数据

              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);

              if (RxLen < 0x40) {     //接收错误

                     GoodPacket = false;

                     if (netif_msg_rx_err(db))

                            dev_dbg(db->dev, "RX: Bad Packet (runt)\n");

              }

              if (RxLen > DM9000_PKT_MAX) {  //接收出错

                     dev_dbg(db->dev, "RST: RX Len:%x\n", RxLen);

              }

              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++;

                     }

              }

              //已经正确接收数据后分配skb,将数据从DM9000中复制到skb_buffer

              if (GoodPacket && ((skb = dev_alloc_skb(RxLen + 4)) != NULL)) {

                     skb_reserve(skb, 2); //在数据包缓冲的起始和载荷的开始之间增加2B的填充位

                     rdptr = (u8 *) skb_put(skb, RxLen - 4);

                     (db->inblk)(db->io_data, rdptr, RxLen);  //真正将接收的数据保存到skb

                     dev->stats.rx_bytes += RxLen;

                     skb->protocol = eth_type_trans(skb, dev);  //去除以太网头

                     if (db->rx_csum) {

                            if ((((rxbyte & 0x1c) << 3) & rxbyte) == 0)

                                   skb->ip_summed = CHECKSUM_UNNECESSARY;

                            else

                                   skb->ip_summed = CHECKSUM_NONE;

                     }

                     netif_rx(skb);          //接收数据,把skb_buffer交给上层协议

                     dev->stats.rx_packets++;

              } else {

                     (db->dumpblk)(db->io_data, RxLen);

              }

       } while (rxbyte & DM9000_PKT_RDY);

}

 

我们知道数据包的接收是通过中断来实现的,下面再分析一下数据的发送,当协议层已经封装好上层协议数据的skb_buffer后,将调用前面提到的dm9000_start_xmit函数把数据发送出去,下面我们看看这个dm9000_start_xmit函数的实现

static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)

{

       unsigned long flags;

       board_info_t *db = netdev_priv(dev); //获取私有数据

       dm9000_dbg(db, 3, "%s:\n", __func__);

       if (db->tx_pkt_cnt > 1)     //网卡忙

              return NETDEV_TX_BUSY;

       spin_lock_irqsave(&db->lock, flags);   //关闭中断,获取锁

       writeb(DM9000_MWCMD, db->io_addr); //读数据命令

       (db->outblk)(db->io_data, skb->data, skb->len);  //把数据发送出去

       dev->stats.tx_bytes += skb->len;   //统计发送字节数

       db->tx_pkt_cnt++; //正在发送中的数据计数加1

       if (db->tx_pkt_cnt == 1) {

              dm9000_send_packet(dev, skb->ip_summed, skb->len);

       } else {

              db->queue_pkt_len = skb->len;

              db->queue_ip_summed = skb->ip_summed;

              netif_stop_queue(dev);  //网卡停止接收数据

       }

       spin_unlock_irqrestore(&db->lock, flags); //释放锁

       dev_kfree_skb(skb);   //释放内存空间

       return NETDEV_TX_OK;

}

首先需要说明的是上述函数实现中调用spin_lock_irqsave(&db->lock, flags)来获取一个自旋锁,当重发后释放该自旋锁,以避免与协议栈调用发送函数产生竞态。

另外,当软件完成指示硬件发送数据包后该函数返回,但此时硬件传送可能还没完成,因此驱动需要告知协议栈不要再起的发送,即调用netif_stop_queue函数,直至硬件准备好接收新的数据。当数据发送完,会触发中断,仍然调用前面的中断处理函数,然后执行dm9000_tx_done(dev, db),跟踪下dm9000_tx_done的实现吧

static void dm9000_tx_done(struct net_device *dev, board_info_t *db)

{

       int tx_status = ior(db, DM9000_NSR);

       if (tx_status & (NSR_TX2END | NSR_TX1END)) {

              db->tx_pkt_cnt--;

              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 > 0)

                     dm9000_send_packet(dev, db->queue_ip_summed,

                                      db->queue_pkt_len);

              netif_wake_queue(dev); //告知协议栈可以再次发送数据

       }

}

需要补充的是如果在其他地方需要停止数据包的传送,可以使用netif_tx_disable函数。

 

四.网络设备驱动测试

网络设备没有字符设备里的open、close等函数,而是靠IP地址选择路由,Linux网络系统的路由选择会自动查找匹配合适的驱动,这是网络驱动和其他两种驱动的主要区别。

下面给出三种测试网络驱动是否正常的方法

其一. 使用ifconfig进行IP地址的设置

其二. 为了让用户获取网络统计的数据,驱动一般有一个net_device_stats结构体,并提供get_stats函数接收它,比如要从eth0接口得到接收的数据包数,可以查看/sys/class/net/eth0/statistics,其中rx_packet表示受到的报文数目,rx_byte表示收到的字节总数。

其三. 应用程序使用标准的socket、bind、send等操作。

 

原创粉丝点击