1 --> 网络驱动架构基础之 — 网络设备驱动基本框架

一、网络协议栈之 — 层次划分

网络协议栈根据osi标准、和linux协议栈划分层级关系对比如下
在这里插入图片描述以太网通信网络一般划分为5层、把linux的网络接口层分为网络层和链路层;一般常用说说的网络分层、如果没有特指的话、可以按照5层分层结构。

二、Linux网络子系统

Linux网络子系统的顶部是系统调用接口层。它为用户空间提供的应用程序提供了一种访问内核网络子系统的方法(socket)。位于其下面是一个协议无关层,它提供一种通用的方法来使用传输层协议。然后是具体协议的实现,在Linux中包括内核的协议TCP,UDP,当然还有IP。然后是设备无关层,它提供了协议与设备驱动通信的通用接口,最下面是设备的驱动程序。
在这里插入图片描述设备无关接口将协议与各种网络驱动连接在一起,这一层提供一组通用函数供底层网络设备驱动使用,让它们可以对高层协议栈进行操作。需要从协议层向设备发送数据,需要调用dev_queue_xmit函数,这个函数对数据进行入列队操作,然后交由底层驱动程序的hard_start_xmit方法最终完成传输。接收通常是使用netif_rx执行的。当底层设备程序接收到一个报文(发生中断)时,就会调用netif_rx将数据上传至设备无关层。

三、设备无关层到驱动层的体系结构

下图为设备无关层到驱动层的体系结构,概述linux网络子系统程序之间的关系。
在这里插入图片描述
3.1)、网络协议接口层向网络层协议提供提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据。这一层的存在使得上层协议独立于具体的设备,各类协议处理函数。

dev_queue_xmit(struct sk_buff *skb);  //网络数据发送
int netif_rx(struct sk_buff *skb);    //网络数据接收

skb_buff结构体,定义于include/linux/skbuff.h中,它的含义为“套接字缓冲区”,用于在Linux网络子系统各层间传输数据,他是一个双向链表,如下:
在这里插入图片描述
sk_buff 的分配和释放

//### 分配
struct sk_buff *alloc_skb(unsigned int len, int priority);
struct sk_buff *dev_alloc_skb(unsigned int len);  
//### 释放
void kfree_skb(struct sk_buff *skb);
void dev_kfree_skb(struct sk_buff *skb); 

Linux内核内部使用alloc_skb()、kfree_skb()函数,而网络设备驱动程序中则最好使用dev_alloc_skb()、dev_kfree_skb()函数。

sk_buff中比较重要的成员是指向数据包中数据的指针,如下图所示:
在这里插入图片描述
Linux内核中的每个网络数据包都由一个套接字缓冲区结构 struct sk_buff 描述,即一个sk_buff结构就是一个网络包,指向sk_buff的指针通常被称做skb。
在这个结构中有这4个成员:

    1、head,包头
    2、data,数据起始位置
    3、tail,数据结尾的位置
    4、end,包尾

3.2)、网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。

网络设备接口层的主要功能是为千变万化的网络设备定义了统一,抽象的数据结构net_device结构体,以不变应万变,实现多种硬件在软件层次上的统一。

每一个网络设备都由struct net_device来描述,该结构可使用如下内核函数进行动态分配

struct net_device *alloc_netdev(int sizeof_priv, const char *mask, void(*setup)(struct net_device *))

sizeof_priv是私有数据区大小;mask是设备名,setup是初始化函数,在注册该设备时,该函数被调用。也就是net_deivce的init成员。

struct net_device *alloc_etherdev(intsizeof_priv)

这个函数和上面的函数不同之处在于内核知道会将该设备做一个以太网设备看待并做一些相关的初始化。
net_device结构可分为全局成员、硬件相关成员、接口相关成员、设备方法成员和公用成员等五个部分
3.2.1)全局成员

char name[INFAMSIZ]    设备名,如:eh%d
unsigned long state  设备状态
unsigned long base_addr  I/O基地址
unsigned int irq   中断号

3.2.2) 主要设备方法

//首先看打开和关闭网络设备的函数:
 
int (*open)(struct net_device *dev);
//打开接口。ifconfig激活时,接口将被打开
 
int (*stop)(struct net_device *dev);  
//停止接口,ifconfig eth% down时调用
//要注意的是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  (*init)(struct  net_device *dev)
//初始化函数,该函数在register_netdev时被调用来完成对net_device结构的初始化
 
int (*hard_start_xmit)(struct sk_buf*skb,struct net_device *dev)
//数据发送函数
 
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);  
//如果数据包发送在超时时间内失败,这时该方法被调用,这个方法应该解决失败的问题,并重新开始发送数据。
 
struct net_device_stats *(*get_stats)(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地址改变,则可以实现该函数。</span>

3.3)、网络驱动接口层各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,他通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。
net_device结构体的成员(属性和函数指针)需要被设备驱动功能层的具体数值和、并初始化相关函数指针。对具体的设置xxx,工程师应该编写设备驱动功能层的函数,这些函数型如xxx_open(),xxx_stop(),xxx_tx(),xxx_hard_header(),xxx_get_stats(),xxx_tx_timeout()等。

4)、网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被驱动功能层中的函数物理上驱动。
网络设备与媒介层直接对应于实际的硬件设备,对于Linux系统而言,网络设备和媒介都可以是虚拟的。

四、驱动的实现

4.1)初始化(init)

设备探测工作在init方法中进行,一般调用一个称之为probe方法的函数;

初始化的主要工作是检测设备,配置和初始化硬件,最后向系统申请这些资源。此外填充该设备的dev结构,我们调用内核提供的ether_setup方法来设置一些以太网默认的设置。

4.2)打开(open)

open这个方法在网络设备驱动程序里是网络设备被激活时被调用(即设备状态由down变成up)

实际上很多在初始化的工作可以放到这里来做。比如说资源的申请,硬件的激活。如果dev->open返回非0,则硬件状态还是down,
注册中断、DMA等;设置寄存器,启动设备,启动发送队列;

一般注册中断都在init中做,但在网卡驱动程序中,注册中断大部分都是放在open中注册,因为要经常关闭和重启网卡。

4.3)关闭(stop)

stop方法做open相反的工作

可以释放某些资源以减少系统负担

stop是在设备状态由up转为down时被调用

4.4)发送(hard_start_xmit)

在系统调用驱动程序的hard_start_xmit时,发送的数据放在一个sk_buff结构中,并把此sk_buf放入发送队列。一般的驱动程序传给硬件发出去。也有一些特殊的设备比如说loopback把数据组成一个接收数据在传送给系统或者dummy设备直接丢弃数据。

如果发送成功,hard_start_xmit方法释放sk_buff。如果设备暂时无法处理,比如硬件忙,则返回1。

4.5) 接收

网卡设备收到数据后都会产生一个中断,在中断处理程序下半部(软中断)程序申请一块sk_buff(skb)从硬件中读取数据位置到申请号的缓冲区里,接下来填充sk_buff中的一些信息。

网卡驱动程序有一个接收方法。当有数据收到时、驱动程序调用netif_rx函数将skb交给设备无关层。

接收流程:
1、分配skb=dev_alloc_skb(pkt->datalen+2)
2、从硬件中读取数据到skb
3、调用netif_rx将数据交给协议栈

4.6)中断

网络接口通常支持3种类型的中断:新报文到达中断、报文发送完成中断和出错中断。中断处理程序可通过查看网卡的中断状态寄存器,来分辨出中断类型;分别调用数据接收函数、数据发送函数或异常函数。

五、网卡驱动架构实例分析

5.1) 网卡描述

在Linux内核中,每个网卡都由一个net_device结构来描述,其中的一些重要成员有:

char name[IFNAMSIZ]:设备名,如:eth%d
unsigned long base_addr :I/O基地址
const struct net_device_ops *netdev_ops

5.2) 网卡操作

类似于字符设备驱动中的file_operations结构,net_device_ops结构记录了网卡所支持的操作,当用户使用网络传输数据时,内核就会找到这些操作函数来实现具体的硬件操作,比如说DM9000的操作函数是如下

static const struct net_device_ops dm9000_netdev_ops =
{
    .ndo_open = dm9000_open,
    .ndo_stop = dm9000_stop,
    .ndo_start_xmit = dm9000_start_xmit,
    .ndo_do_ioctl = dm9000_ioctl,
    .ndo_validate_addr = eth_validate_addr,
    .ndo_set_mac_address = eth_mac_addr,
};

5.3) 网卡初始化分析

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

int __init init_module(void)
{//初始的第一项工作是分配一个net_device结构
  struct net_device *dev = alloc_etherdev(sizeof(struct net_local));
  struct net_local *lp;
  int ret = 0;
  ...
  //第二项工作就是初始化这个结构
  dev->irq = irq;
  dev->base_addr = io;
  ...
  //第三项工作就是调用cs89x0_probe1完成其他的初始化
  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 = 0;
  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);        //注册网卡驱动
}

在这里插入图片描述### 5.4)数据发送

netdev_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,
};

ndo_start_xmit对应的函数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 > 3) {
		printk("%s: sent %d byte packet of type %x\n",
			dev->name, skb->len,
			(skb->data[ETH_ALEN+ETH_ALEN] << 8) | skb->data[ETH_ALEN+ETH_ALEN+1]);
	}
 
	/* keep the upload from being interrupted, since we
                  ask the chip to start transmitting before the
                  whole packet has been completely uploaded. */
 
	spin_lock_irqsave(&lp->lock, flags);
	//首先调用netif_stop_queue,告诉上层协议栈,暂停往网卡中发送数据
	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) == 0) {
		/*
		 * Gasp!  It hasn't.  But that shouldn't happen since
		 * we're waiting for TxOk, so return 1 and requeue this packet.
		 */
 
		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 */
	// skb中的数据写入寄存器中并发送走
	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;
	// 释放skb空间
	dev_kfree_skb (skb);
 
	/*
	 * We DO NOT call netif_wake_queue() here.
	 * We also DO NOT call netif_start_queue().
	 *
	 * Either of these would cause another bottom half run through
	 * net_send_packet() before this packet has fully gone out.  That causes
	 * us to hit the "Gasp!" above and the send is rescheduled.  it runs like
	 * a dog.  We just return and wait for the Tx completion interrupt handler
	 * to restart the netdevice layer
	 */
 
	return NETDEV_TX_OK;
}

如果这就完了上层协议还是无法向网卡发送数据,网卡不能正常工作,显然这是不正常的。那么在什么地方重新允许上层协议向网卡发送数据包呢?
当网卡发送走一个数据包后,会进入网卡中断程序中,查找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;
      ...
    }  
  }
}

数据发送接收,流程总结如下。
在这里插入图片描述### 5.5) 数据接收

接收数据一般在中断中进行的。找到static irqreturn_t net_interrupt(int irq, void *dev_id)这个函数。这里面实现对各种中断的处理,比如说接收数据中断:

  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) == 0) {
		count_rx_errors(status, dev);
		return;
	}
 
	/* Malloc up new buffer. 构造一个skb */
	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包
	skb_reserve(skb, 2);	/* longword align L3 header */
 
	readwords(ioaddr, RX_FRAME_PORT, skb_put(skb, length), length >> 1);
	if (length & 1)
		skb->data[length-1] = readword(ioaddr, RX_FRAME_PORT);
 
	if (net_debug > 3) {
		printk(	"%s: received %d byte packet of type %x\n",
			dev->name, length,
			(skb->data[ETH_ALEN+ETH_ALEN] << 8) | skb->data[ETH_ALEN+ETH_ALEN+1]);
	}
 
    skb->protocol=eth_type_trans(skb,dev);
    // 最后把skb包交给上层协议栈
	netif_rx(skb);
	dev->stats.rx_packets++;
	dev->stats.rx_bytes += length;
}

数据接收流程总结如下
在这里插入图片描述至此数据接收完毕、数据进入协议栈如何解析,请参考本系列下一篇博文。

参考博文链接如下,在此感谢博主们的付出。
https://blog.csdn.net/zqixiao_09/article/details/51146724
https://blog.csdn.net/qq_22847457/article/details/91946775

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值