这一篇详细介绍一下zynq下linux内核中网络驱动的运行过程。
在linux中,网络可以分为下面三个层次:
Linux网络驱动涉及到后面两层,网络协议层中需要了解skb和netif;硬件驱动层也就是mac层,需要了解dma和dec。
按内容来说,整个网络驱动又可以分为下面五个部分:
a) control设置,包含控制器操作、mac属性地址,stamp等内容
b) phy接口相关设置,包含ethtool,mdio这些内容
c) ethtool其他设置
d) mac层设置,主要是dma+dec
e) netif设置,skb处理过程,包含IPalign,skb回收,TX/RX skb接口部分
其中a、b、c部分属于基本接口,d、e部分属于数据和迁移,对a、b、d部分需要精通,对c、e部分需要熟悉。
用一张图表示,网络驱动内部调用接口如下:
上图中ethtool是网络中常用工具,该工具调用driver中的接口,也可以直接调用MDIO,phy的接口。
在硬件驱动层,主要是通过DMA进行网络数据包的收发,在控制器端维护一对TX/RX包含desc的队列,mac层收到的每一包数据以及要通过mac层发出的数据信息被保存在这一对队列中,zynq上linux驱动的代码实现位于
Macb.c (drivers\net\ethernet\cadence)中。
如下图所示:
以RX为例,上图中macb_queue为包含desc网络描述符的队列,每一个单元内包含macb_dma_desc结构体,结构体中第一个addr字段指向mac层收到的数据包。该队列在操作过程中存在两种模式,一种是ring mode,另一种是chain mode。目前macb_queue采用的是ring mode。Ring mode是该队列是分配连续空间的一个数组,指针从头开始遍历执行,当执行到数组最后一个元素时返回头部继续执行。P,q指针分别对应处理的数据和接收的数据,通过标志位进行判断。如果接收快而处理慢,网络就会丢包。相反,如果处理快而接收慢,那数组大小为2即可。目前RX数组常见的大小为512,正好占满cpu一个page。
Chain mode指空间可以不连续,用指针进行相连,等同于循环链表。
无论是ring mode还是chain mode,当cpu执行到最后一个元素时,会产生一个中断。
网络收发包由于用到了dma,也可以用下图进行表示,这里的BD表在网络中即为为macb_queue。
在网络协议层中使用了一个skb_buff结构体,定义于include/linux/skbuff.h中,它的含义为“套接字缓冲区”,用于在Linux网络子系统各层间传输数据。他是一个双向链表。从代码中可以找到结构体定义,该结构体的解释参看:
http://blog.csdn.net/wufen_1981/article/details/1925027
网络协议层与硬件驱动层之间的数据交互其实就是一个数据的拷贝过程,收到数据时存放在RX中,然后通过拷贝复制到skb结构体中。发数时将skb结构体中数据拷贝到TX队列内,然后再通过mac层发送。
sk_buff中重要的数据成员
struct device *dev;正在处理该包的设备
__u32 sadd;r//IP元地址
__u32 daddr;//IP目的地址
__u32 raddr;//IP路由器地址
unsigned char *head;//分配空间的开始
unsigned char *data;//有效数据的开始
unsigned char *tail;//有效数据的结束
unsigned char *end;//分配空间的结束
unsigned long len;//有效数据的长度
网络协议层主要是对sk_buff进行操作
a)分配:分配一个sk_buff结构,供协议栈代码使用
struct sk_buff *netdev_alloc_skb(unsignedint len);
分配一个缓冲区。dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配。
b)释放:
void dev_kfree_skb(struct sk_buff *skb);
网络设备驱动程序中使用dev_kfree_skb()对skb进行释放。
sk_buff中比较重要的成员是指向数据包中数据的指针,如下图所示:
用于寻址数据包中数据的指针,head指向已分配空间开头,data指向有效的octet开头,tail指向有效的octet结尾,而end指向tail可以到达的最大地址。如果不这样做而分配一个大小固定的缓冲区,如果buffer不够用,则要申请一个更大的buffer,拷贝进去再增加,这样降低了性能。
c)变更
unsigned char *skb_put(struct sk_buff *skb,int len);将taill指针向后移动len长度,并返回tail移动之前的值。用于向skb有效数据区域末尾添加数据。
unsigned char *skb_push(struct sk_buff*skb, int len);将data指针向前移动len长度。并返回移动之后的值。用于向skb有效数据区域前端添加数据(包头)。
unsigned char *skb_pull(struct sk_buff*skb, int len);
void skb_reserve(struct sk_buff ×skb, intlen);
下图分别对应了这四个函数
详细内容参看:http://blog.chinaunix.net/uid-20672257-id-3147768.html
http://blog.csdn.net/milanlakers/article/details/34133815