上一篇**分析了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; }
|