at91rm9200中dm9161的数据收发流程

当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 */

/* 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 */

    /* Transmit */
    struct sk_buff *skb; /* holds skb until xmit interrupt completes. */
    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处理

以下为很简单的发送流程:

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)发送。

在dev_hard_start_xmit()中通过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)暂停发送队列
    将skb相关信息配置到private变量中,并配置DMA
    配置好之后,写EMAC,包括数据包物理地址和长度,启动发送

对于虚拟内存中的套接字缓冲区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中成员变量所指向的套接字缓冲。