在天涯上看到一系列分析比较透彻的文章,放到此处学习。
原文:http://blog.tianya.cn/blogger/post_show.asp?idWriter=0&Key=0&BlogID=803354&PostID=21127514
/*
* 驱动内接收数据包过程
* at91rm9200 - dm9161 - 2.6.20
*/
当MAC有数据包接收到,并且顺利的通过DMA拷贝到了驱动里设置的接收缓冲区之后,
会触发一个RCOM(接收完成)的中断,驱动在中断处理例程中调用at91ether_rx(dev)
进行处理。
首先,这里说明一下AT91rm9200的MAC硬件部分是怎么做接收缓冲的。
#define MAX_RBUFF_SZ 0x600 /* 1518 rounded up */
#define MAX_RX_DESCR 9 /* max number of receive buffers */
/* zhs: used in buflist entry's word0 */
#define EMAC_DESC_DONE 0x00000001 /* bit for if DMA is done */
#define EMAC_DESC_WRAP 0x00000002 /* bit for wrap */
#define EMAC_BROADCAST 0x80000000 /* broadcast address */
#define EMAC_MULTICAST 0x40000000 /* multicast address */
#define EMAC_UNICAST 0x20000000 /* unicast address */
struct rbf_t
{
unsigned int addr; //descriptor's word0
unsigned long size; //descriptor's word1
};
struct recv_desc_bufs
{
struct rbf_t descriptors[MAX_RX_DESCR]; /* must be on sizeof (rbf_t) boundary */
char recv_buf[MAX_RX_DESCR][MAX_RBUFF_SZ]; /* must be on long boundary */
};
struct at91_private
{
struct net_device_stats stats;
struct mii_if_info mii; /* ethtool support */
struct at91_eth_data board_data; /* board-specific configuration */
struct clk *ether_clk; /* clock */
/* PHY */
unsigned long phy_type; /* type of PHY (PHY_ID) */
spinlock_t lock; /* lock for MDI interface */
short phy_media; /* media interface type */
unsigned short phy_address; /* 5-bit MDI address of PHY (0..31) */
struct timer_list check_timer; /* Poll link status zhs: 用于轮询(没有中断) */
/* Transmit */
struct sk_buff *skb; /* holds skb until xmit interrupt completes. zhs: just for tx */
dma_addr_t skb_physaddr; /* phys addr from pci_map_single */
int skb_length; /* saved skb length for pci_unmap_single */
/* Receive */
int rxBuffIndex; /* index into receive descriptor list */
struct recv_desc_bufs *dlist; /* descriptor list address. */
struct recv_desc_bufs *dlist_phys; /* descriptor list physical address */
}
以上的宏和结构体定义提供了对于at91rm9200的MAC硬件接收和发送的支持。这里说
明下接收的情况。
如果一个包接收后,经过地址验证后没问题的话,该包被DMA存储到接收缓冲中去。
接收缓冲区是为MAX_RBUFF_SZ大小的一个内存空间,这里定义了MAX_RX_DESCR个接收
缓冲区。其实以太网内最大的包大小为1522.
驱动需要提供给MAC硬件的缓冲区的格式是MAC硬件要求的规定格式.多个的接收缓冲
区被组织成一个list。这个list被叫作descriptor list(描述符列表)。每个list的
节点为一个叫作descriptor(dp)或者一个list entry,每个dp有两个word组成,分别
为word0和word1,结构体 struct rbf_t就为这个list entry,结构体内的addr就为
word0, size为word1。因为 int 和 long 类型都是4字节的,所以这个结构体是字对
齐的,不用担心hole问题。
而list这个结构则有结构体 struct recv-desc_bufs来维护,里面有两个成员变量.
一个是dp的数组,为实际的list,即这个数组就是list的实体。 另一个为缓冲区的
数组,每个缓冲区大小为MAX_RBUFF_SZ,有MAX_RX_DESCR个缓冲区。
在 struct at91_private结构体中的 Receive段有三个成员变量来维护接收缓冲区.
int rxBuffIndex表示当前软件中处理的list的入口标号,即是对那个缓冲区进行处
理。struct recv_desc_bufs *dlist指向descriptor list的虚拟内存地址,在驱动
中调用并操作list。struct recv_desc_bufs *dlist_phys用来保存描述符列表的物
理地址,便于MAC硬件和DMA处理。而对于dlist_phys的初始化在at91ether_setup中
进行。
至于MAC对接收缓冲列表的具体处理过程和list entry每个word代表的含义可以参考
at91rm9200的datasheet。
void at91ether_rx(struct net_device *dev) 这个函数在MAC的中断处理例程中被
调用,在中断上下文中对接收到的包进行处理,并提交给内核上层。
at91ether_rx(dev)
|
取得private和dlist数据
|
循环检查list的每个entry
|
-----------------------------
| |
如果缓冲区中有新包数据 如果没有新包数据
| |
取出实际数据包头地址和长度 |
这个时候的包地址已经是虚拟 |
内存地址了 |
| |
分配skb套接字缓冲区 |
| |
拷贝包数据到skb中 |
| |
设置skb其他成员变量 |
| |
netif_rx(skb) |
传输给内核上层 |
| |
标记该缓冲区已处理 |
| |
对index处理 |
| |
-------------------------------------
|
|
/*
* 驱动内发送数据包过程
* at91rm9200 - dm9161 - 2.6.20
*/
dev_queue_xmit --> dev_hard_start_xmit --> hard_start_xmit(指向驱动函数)
以上为发送数据包的大致流程。来自IP层的的一个套接字结构体struct sk_buff存
放在内核维护的发送队列中。在发送队列能够发送一个套接字缓冲区时,调用这个
函数:dev_queue_xmit(). 该函数的参数为 struct sk_buff *skb, 通过skb->dev
可以取得这个套接字缓冲区所要发送到的设备, struct net_device dev = skb->dev;
获得dev之后,就可以获得操作该网络设备的能力了。检测网络设备硬件状况,如果
符合要求能够发送数据包,则调用dev_hard_start_xmit(skb, dev)发送包。
在int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)中
通过dev->hard_start_xmit(skb, dev)来执行实际的发送动作.这里dev的成员函数
hard_start_xmit()是一个函数指针,在dev的驱动初始化过程中应该应该初始化好
了的。对于at91rm9200-dm9161,该函数为at91_ether.c中的at91ether_tx()函数.
int at91ether_tx(struct sk_buff *skb, struct net_device *dev)函数中,执行
的流程如下:
at91ether_tx(skb, dev);
|
取得private数据
|
检查EMAC寄存器,硬件是否可以发送数据包
|
------------------------------------------
| |
如果可以发送 如果不能发送
| |
netif_stop_queue(dev) 打印一条设备忙信息
暂停发送队列,即不启动其他包发送 |
可以在硬件发送完成的中断中唤醒队列 返回1
| |
将skb相关信息配置到private的发送相 |
关的成员变量中,并配置发送的DMA. |
| |
配置好之后,写EMAC,包括数据包的物 |
理地址和发送长度,即开始一次硬件发 |
送 |
| |
返回0 |
| |
|<------------------------------------------------
这里对于虚拟内存中的套接字缓冲区skb,调用了以下的函数:
lp->skb_physaddr = dma_map_single(NULL, skb->data, skb->len, DMA_TO_DEVICE);
这个函数应该是将虚拟内存的地址转化为物理地址,并存放在private中的成员变量
skb_physaddr中,再调用at91_emac_write(AT91_EMAC_TAR, lp->skb_physaddr);将
要发送的套接字缓冲区物理地址写到EMAC的Transmit Address Register中,这样就
可以在发送这个物理硬件过程和DMA中直接处理物理地址。
这个发送函数的返回值不同,则处理有不同。如果返回的是1,即不能发送,则调用
驱动函数的dev_queue_xmit函数会在调用这个驱动函数之后释放掉skb套接字缓冲区
占用的内存空间;而如果返回的是0,即交由了硬件发送,则在发送完成(包括发送
失败的情况)的中断处理例程中,会释放掉由private中成员变量所指向的套接字缓
冲