一、网卡驱动架构

  由上到下层次依次为:应用程序→系统调用接口→协议无关接口→网络协议栈→设备无关接口→设备驱动。

二、重要数据结构

  1、Linux内核中每一个网卡由一个net_device结构来描述。

  2、网卡操作函数集:net_device_ops,这个数据结构是上面net_device的一个成员。

  3、网络数据包:sk_buff。

三、网卡驱动代码分析

  所用文件为cs89x0.c,主要分析三个部分:网卡初始化、发送数据、接收数据。

  ㈠网卡初始化

    网卡驱动初始化主要在函数init_module中完成,部分代码如下:

int __init init_module(void)
{
  struct net_device *dev = alloc_etherdev(sizeof(struct net_local));
  struct net_local *lp;
  int ret = ;
  ...
  dev->irq = irq;
  dev->base_addr = io;
  ...
  ret = cs89x0_probe1(dev, io, 1);
  ...
}

    cs89x0_probe1函数部分代码如下:

static int __init cs89x0_probe1(struct net_device *dev, int ioaddr, int modular)
{
  struct net_local *lp = netdev_priv(dev);
  static unsigned version_printed;
  int i;
  int tmp;
  unsigned rev_type = ;
  int eeprom_buff[CHKSUM_LEN];
  int retval;
  ...
  writeword(ioaddr, ADD_PORT, PP_ChipID);
  tmp = readword(ioaddr, DATA_PORT);      //对硬件的初始化
  ...
  
  for (i = 0; i < ETH_ALEN/2; i++)       //初始化MAC地址
  {
    dev->dev_addr[i*2] = eeprom_buff[i];
    dev->dev_addr[i*2+1] = eeprom_buff[i] >> 8;
  }
  ...
  
  dev->netdev_ops    = &net_ops;        //初始化netdev_ops
  ...
  retval = register_netdev(dev);        //注册网卡驱动
}

  由代码可以看出

    1、定义并分配net_device结构,使用alloc_etherdev函数。

    2、初始化net_device。(包括中断号、I/O基地址、MAC地址、netdev_ops)

    3、初始化硬件

    4、将网卡驱动注册到内核,使用函数register_netdev

  ㈡发送数据

  初始化netdev_ops时将其赋值为&net_ops,可在这个结构中找到发送函数

  

static const struct net_device_ops net_ops = {
.ndo_open = net_open,
.ndo_stop = net_close,
.ndo_tx_timeout = net_timeout,
.ndo_start_xmit = net_send_packet,
.ndo_get_stats = net_get_stats,
.ndo_set_multicast_list = set_multicast_list,
.ndo_set_mac_address = set_mac_address,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = net_poll_controller,
#endif
.ndo_change_mtu = eth_change_mtu,
.ndo_validate_addr = eth_validate_addr,
};

  net_send_packet代码如下:

static netdev_tx_t net_send_packet(struct sk_buff *skb,struct net_device *dev)
{
struct net_local *lp = netdev_priv(dev);
unsigned long flags; if (net_debug > ) {
printk("%s: sent %d byte packet of type %x\n",
dev->name, skb->len,
(skb->data[ETH_ALEN+ETH_ALEN] << ) | skb->data[ETH_ALEN+ETH_ALEN+]);
} spin_lock_irqsave(&lp->lock, flags);
netif_stop_queue(dev); /* initiate a transmit sequence */
writeword(dev->base_addr, TX_CMD_PORT, lp->send_cmd);
writeword(dev->base_addr, TX_LEN_PORT, skb->len); /* Test to see if the chip has allocated memory for the packet */
if ((readreg(dev, PP_BusST) & READY_FOR_TX_NOW) == ) { spin_unlock_irqrestore(&lp->lock, flags);
if (net_debug) printk("cs89x0: Tx buffer not free!\n");
return NETDEV_TX_BUSY;
}
/* Write the contents of the packet */
writewords(dev->base_addr, TX_FRAME_PORT,skb->data,(skb->len+1) >>1);
spin_unlock_irqrestore(&lp->lock, flags);
dev->stats.tx_bytes += skb->len;
dev_kfree_skb (skb); return NETDEV_TX_OK;
}

  这部分代码做了这些事情(红色高亮部分)

  1、通知上层协议停止向网卡发送数据

    由于网卡现在要向外发送数据包,所以要停止接收数据包

  2、将skb中的数据写入寄存器中并发送走

  3、释放skb空间

  但是到这里并不算完,如果这就完了上层协议还是无法向网卡发送数据,网卡不能正常工作,显然这是不正常的。那么在什么地方重新允许上层协议向网卡发送数据包呢?

  其实,当网卡发送走一个数据包后,会进入网卡中断程序中,查找request_irq的知中断处理程序名称为net_interrupt

static irqreturn_t net_interrupt(int irq, void *dev_id)
{
  struct net_device *dev = dev_id;
  struct net_local *lp;
  int ioaddr, status;
  int handled = 0;   ioaddr = dev->base_addr;
  lp = netdev_priv(dev);   while ((status = readword(dev->base_addr, ISQ_PORT)))
  {
    switch(status & ISQ_EVENT_MASK)
    {
      ...
      case ISQ_TRANSMITTER_EVENT:
        dev->stats.tx_packets++;
        netif_wake_queue(dev);    /* Inform upper layers. */
        if ((status & (    TX_OK |
                    TX_LOST_CRS |
                    TX_SQE_ERROR |
                    TX_LATE_COL |
                    TX_16_COL)) != TX_OK) {
                if ((status & TX_OK) == 0)
                    dev->stats.tx_errors++;
                if (status & TX_LOST_CRS)
                    dev->stats.tx_carrier_errors++;
                if (status & TX_SQE_ERROR)
                    dev->stats.tx_heartbeat_errors++;
                if (status & TX_LATE_COL)
                    dev->stats.tx_window_errors++;
                if (status & TX_16_COL)
                    dev->stats.tx_aborted_errors++;
            }
            break;
      ...
    }  
  }
}

  4、通知上层协议,可以向网卡发送数据包。使用函数netif_wake_queue

  ㈢数据接收

  当网卡接受到一个数据包后,进入网卡中断处理程序

static irqreturn_t net_interrupt(int irq, void *dev_id)
{
  struct net_device *dev = dev_id;
  struct net_local *lp;
  int ioaddr, status;
  int handled = ;   ioaddr = dev->base_addr;
  lp = netdev_priv(dev);   while ((status = readword(dev->base_addr, ISQ_PORT)))
  {
    switch(status & ISQ_EVENT_MASK)
    {
      ...
      case ISQ_RECEIVER_EVENT:
        /* Got a packet(s). */
        net_rx(dev);
        break;
    }  
  }
}
 

  net_rx函数代码如下

static void net_rx(struct net_device *dev)
{
struct sk_buff *skb;
int status, length; int ioaddr = dev->base_addr;
status = readword(ioaddr, RX_FRAME_PORT);
length = readword(ioaddr, RX_FRAME_PORT); if ((status & RX_OK) == ) {
count_rx_errors(status, dev);
return;
} /* Malloc up new buffer. */
skb = dev_alloc_skb(length + 2);
if (skb == NULL) {
#if 0 /* Again, this seems a cruel thing to do */
printk(KERN_WARNING "%s: Memory squeeze, dropping packet.\n", dev->name);
#endif
dev->stats.rx_dropped++;
return;
}
skb_reserve(skb, ); /* longword align L3 header */ readwords(ioaddr, RX_FRAME_PORT, skb_put(skb, length), length >> );
if (length & )
skb->data[length-1] = readword(ioaddr, RX_FRAME_PORT); if (net_debug > ) {
printk( "%s: received %d byte packet of type %x\n",
dev->name, length,
(skb->data[ETH_ALEN+ETH_ALEN] << ) | skb->data[ETH_ALEN+ETH_ALEN+]);
} skb->protocol=eth_type_trans(skb,dev);
netif_rx(skb);
dev->stats.rx_packets++;
dev->stats.rx_bytes += length;
}

  由代码可以看出:

  1、读取接收状态

  2、读取接收到数据的长度

  3、分配skb结构,skb = dev_alloc_skb(length + 2);

  4、从硬件寄存器中读取数据存入skb

  5、江封装好的数据包向上发送给协议栈,使用函数netif_rx

  网卡驱动架构到这里大致就分析完了。如果有疑问或错误,欢迎指出。

Linux网卡驱动架构分析的更多相关文章

  1. Linux 网卡驱动设备程序设计(1)

    一.网卡驱动架构分析 1. Linux 网络子系统 #系统调用接口层 为应用程序提供访问网络子系统的统一方法. #协议无关层 提供通用的方法来使用传输层协议. #协议栈的实现 实现具体的网络协议 #设 ...

  2. linux i2c驱动架构-dm368 i2c驱动分析

      linux i2c驱动架构-dm368 i2c驱动分析   在阅读本文最好先熟悉一种i2c设备的驱动程序,并且浏览一下i2c-core.c以及芯片提供商的提供的i2c总线驱动(i2c-davinc ...

  3. Exynos4412 IIC总线驱动开发(一)—— IIC 基础概念及驱动架构分析

    关于Exynos4412 IIC 裸机开发请看 :Exynos4412 裸机开发 —— IIC总线 ,下面回顾下 IIC 基础概念 一.IIC 基础概念 IIC(Inter-Integrated Ci ...

  4. linux驱动基础系列--linux spi驱动框架分析

    前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...

  5. 【Linux开发】Linux V4L2驱动架构解析与开发导引

    Linux V4L2驱动架构解析与开发导引 Andrew按:众所周知,linux中可以采用灵活的多层次的驱动架构来对接口进行统一与抽象,最低层次的驱动总是直接面向硬件的,而最高层次的驱动在linux中 ...

  6. Linux USB驱动框架分析 【转】

    转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...

  7. linux驱动基础系列--linux spi驱动框架分析(续)

    前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...

  8. Linux USB驱动框架分析【转】

    转自:http://blog.csdn.net/jeffade/article/details/7701431 Linux USB驱动框架分析(一) 初次接触和OS相关的设备驱动编写,感觉还挺有意思的 ...

  9. 【原创】Linux PCI驱动框架分析(二)

    背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...

随机推荐

  1. Android开发技巧——去掉TextView中autolink的下划线

    我们知道,在布局文件中设置textview的autolink及其类型,这时textivew上会显示link的颜色,并且文字下面会有一条下划线,表示可以点击.而在我们在点击textview时,应用将根据 ...

  2. jQuery 表单验证插件——Validation(基础)

    这个插件不错,是用jquery写的.能进行表单验证.我喜欢它的原因是因为 1.他有自带的验证规则 2.你可以自己写验证规则 3.可以通过ajax与后台交互,与后台数据比较.最后返回结果!我在表单中要验 ...

  3. WMI概述

    关于wmi的定义可以在网上和msdn中查询,我在这里想说说自己对wmi的理解.Wmi是Windows Management Instrumentation(windows管理方法)的缩写.在wmi中微 ...

  4. (转)DEDECMS模板原理、模板标签学习 - .Little Hann

    本文,小瀚想和大家一起来学习一下DEDECMS中目前所使用的模板技术的原理: 什么是编译式模板.解释式模板,它们的区别是什么? 模板标签有哪些种类,它们的区别是什么,都应用在哪些场景? 学习模板的机制 ...

  5. Access数据库数据转换Table.Json

    使用WPF组件 xaml <Window x:Class="JsonConvert.MainWindow" xmlns="http://schemas.micros ...

  6. C#基础知识01(continue、break 和 return、ref 和 out)

    break[跳出循环或者退出一个switch语句]由于它是用来退出循环或者switch语句的,所以只有当它出现在这些语句中时才是合法的. continue 语句和break语句相似,只是它不是退出一个 ...

  7. ExtJs API 下载以及部署

    ExtJs API 下载方法 1.进入sencha官网:https://www.sencha.com/ 2.点击“Docs”进入文档帮助页面:http://docs.sencha.com/ 3.点击左 ...

  8. 初涉JavaScript模式 (12) : 沙箱模式

    引子 上一篇说了模块模式,而对于其中的命名空间模式其实也是有着一些问题,比如每添加一个模块或则深入叠加都会导致长命名,并且对于多个库的不同版本同时运行,一不小心就会污染全局标识,而这两天也发现了JSe ...

  9. [ JS 进阶 ] 如何改进代码性能 (3)

    原文链接 总结一下 1.减少操作dom的次数 2.需要多次使用某全局变量的时候,将其赋给一个局部变量,避免重复查找 3.优化循环 4.多用逗号和直接赋值的方式来var,减少工厂方式和构造函数方式创建对 ...

  10. HTML cellpadding与cellspacing属性

    单元格(cell) -- 表格的内容 单元格边距(表格填充)(cellpadding) -- 代表单元格外面的一个距离,用于隔开单元格与单元格空间 单元格间距(表格间距)(cellspacing) - ...