上一篇**分析了Freescale i.MX6 Linux Ethernet Driver的device的添加和driver的加载过程,接下来分析fec_enet_init()函数:首先提一点这个函数的声明是static int fec_enet_init(struct net_device *ndev),即传递参数为net_device,那么通过netdev_priv(ndev)即可以获取到之前alloc_etherdev()函数分配的指向私有数据的地址:
ndev = alloc_etherdev(sizeof(struct fec_enet_private));
struct fec_enet_private *fep = netdev_priv(ndev);
这种通过结构体内部指针传递私有数据的方式在driver中非常常见。函数开头即为Ethernet Controller的DMA 控制器分配相应的buffer描述符:
/* Allocate memory for buffer descriptors. */
cbd_base = dma_alloc_noncacheable(NULL, BUFDES_SIZE, &fep->bd_dma,
GFP_KERNEL);
if (!cbd_base) {
printk("FEC: allocate descriptor memory failed?\n");
return -ENOMEM;
}
这里分配的缓冲区大小是(tx buffer个数+rx buffer个数)×buffer描述符大小:
#define BUFDES_SIZE ((RX_RING_SIZE + TX_RING_SIZE) * sizeof(struct bufdesc))
由于buffer描述符会被CPU以及DMA控制器访问,因此会存在Cache一致性问题,这里采用了dma_alloc_noncacheable()函数,即DMA一致性映射。这里采用一致性映射是因为CPU或者DMA控制器会以不可预知的方式去访问这段内存区,在Linux Kernel中解决Cache一致性问题有两种方案:DMA流式映射和DMA一致性映射,关于这两者的区别在《Understanding Linux Kernel》以及《LDD3》中均有介绍,我个人也总结了一篇博文初步讲述了这两者的区别:http://blog.163.com/thinki_cao/blog/static/83944875201362142939337。
这里分析一下DMA控制器,i.MX6的DMA控制器采用了环形buffer描述符,这里buffer分为两种,Legacy buffer descriptor是为了保持对前代Freescale器件的兼容性,而Enhanced buffer descriptor则提供了更多的功能,引用i.MX6Q的reference manual中的图:
Legacy buffer descriptor一共有8个字节,注意这里是采用大端存储模式的。
而Enhanced buffer descriptor一个有64字节,也是采用大端存储模式的,个人觉得这个Ethernet IP有点像是从PowerPC那边扣过来的。
可以从fec.h文件中找到对这两个描述符的定义:
struct bufdesc {
unsigned short cbd_datlen; /* Data length */
unsigned short cbd_sc; /* Control and status info */
unsigned long cbd_bufaddr; /* Buffer address */
#ifdef CONFIG_ENHANCED_BD
unsigned long cbd_esc;
unsigned long cbd_prot;
unsigned long cbd_bdu;
unsigned long ts;
unsigned short res0[4];
#endif
如果定义了CONFIG_ENHANCED_BD宏,则开启Enhanced buffer descriptor的支持。不过纵观整个driver程序,3.0.35的内核并没有使用enhanced buffer descriptor使用的一些功能,比如Enhanced transmit buffer descriptor中的offset+8位置的PINS和IINS位,提供了采用MAC提供的IP accelerator进行硬件校验,提供对协议的校验和IP头的校验。而在yocto 3.10.17内核上,这些已经支持了!这也是为什么3.0.35上的Ethernet driver的性能不如3.10.17上的原因之一吧。下面继续分析代码:
spin_lock_init(&fep->hw_lock); /* 初始化自旋锁 */
fep->netdev = ndev; /*把net_device的地址传给netdev*/
/* Get the Ethernet address */fec_get_mac(ndev);
fec_get_mac会从多个地方获取mac地址:
static void __inline__ fec_get_mac(struct net_device *ndev)
{
struct fec_enet_private *fep = netdev_priv(ndev);
struct fec_platform_data *pdata = fep->pdev->dev.platform_data;
unsigned char *iap, tmpaddr[ETH_ALEN];
/*
* try to get mac address in following order:
*
* 1) module parameter via kernel command line in form
* fec.macaddr=0x00,0x04,0x9f,0x01,0x30,0xe0
*/
iap = macaddr;
/*
* 2) from flash or fuse (via platform data)
*/
if (!is_valid_ether_addr(iap)) {
if (pdata)
memcpy(iap, pdata->mac, ETH_ALEN);
}
/*
* 3) FEC mac registers set by bootloader
*/
if (!is_valid_ether_addr(iap)) {
*((unsigned long *) &tmpaddr[0]) =
be32_to_cpu(readl(fep->hwp + FEC_ADDR_LOW));
*((unsigned short *) &tmpaddr[4]) =
be16_to_cpu(readl(fep->hwp + FEC_ADDR_HIGH) >> 16);
iap = &tmpaddr[0];
}
memcpy(ndev->dev_addr, iap, ETH_ALEN);
/* Adjust MAC if using macaddr */
if (iap == macaddr)
ndev->dev_addr[ETH_ALEN-1] = macaddr[ETH_ALEN-1] + fep->pdev->id;
}
1)首先是从全局变量macaddr获取ip地址,macaddr定义相关的代码如下:
static unsigned char macaddr[ETH_ALEN];
module_param_array(macaddr, byte, NULL, 0);
MODULE_PARM_DESC(macaddr, "FEC Ethernet MAC address");
__setup("fec_mac=", fec_mac_addr_setup);
这里的__setup是用来从uboot传给内核的启动参数中捕获fec_mac(即mac地址)参数,并将该参数传递给fec_mac_addr_setup(char *mac_addr)函数进行解析的。如果uboot中没有传递mac参数,那么macaddr数组中的成员全是0。
2)检测1)中获取的mac地址是否合法,如果不合法,则从设备的私有数据结构(如果pdata指针不为空)struct fec_platform_data中获取mac数组的值。
3)检测2)中获取的mac地址是否合法,如果不合法,则读取Ethernet控制器的mac地址寄存器来获取mac地址。
最后把mac地址传递给内核中net_device结构体中的dev_addr字段。
接着继续分析代码:
/* Set receive and transmit descriptor base. */
fep->rx_bd_base = cbd_base;
fep->tx_bd_base = cbd_base + RX_RING_SIZE;
设置tx_bd_base和rx_bd_base,即tx buffer descriptor base 和rx buffer descriptor base,示意图如下:
接着就是net_device已经fec_enet_private等结构体的设置:
/* The FEC Ethernet specific entries in the device structure */
ndev->watchdog_timeo = TX_TIMEOUT; /* watchdong定时器唤醒间隔 */
ndev->netdev_ops = &fec_netdev_ops;
ndev->ethtool_ops = &fec_enet_ethtool_ops;
fep->use_napi = FEC_NAPI_ENABLE;fep->napi_weight = FEC_NAPI_WEIGHT;if (fep->use_napi) { fec_rx_int_is_enabled(ndev, false); netif_napi_add(ndev, &fep->napi, fec_rx_poll, fep->napi_weight);}
|