经过这一个月的学习,总算勉勉强强在uboot的基础上改好了网卡驱动:
发送只用了一个包缓冲buffer,大小就是1536(MTU1500的情况下),接收区缓冲20个,每个1536字节。发送没有用中断的方式,接收采用中断的方式进行。注意缓冲描述符在这个驱动里面是采用的静态全局变量的方式:
typedef volatile struct rtxbd {
txbd8_t txbd[TX_BUF_CNT];
rxbd8_t rxbd[PKTBUFSRX];
} RTXBD;
static RTXBD rtx __attribute__ ((aligned(8)));
首先我们需要熟读一下mpc8377的网卡相关与中断相关的datasheet,主要注意以下几个寄存器:
中断控制器ipic的SIVCR、SIPNR(H/L)、SIMSR(H/L)、SEMSR
网卡控制器etsec的IEVENT、IMASK、DMACTRL、MACCFG1、MACCFG2,还有一些PHY相关寄存器(好吧其实我没怎么看PHY的)
设置相关寄存器的宏定义、相关寄存器位的置位宏定义,注意ievent寄存器是写1清除中断,imask是写0屏蔽中断。
整个网卡的驱动初始化流程(以SylixOS的BSP为例):
1.在bsp初始化中调用的是halNetifAttch (VOID)函数:
完成的功能:
设置网卡物理地址mac、网关、默认ip、设置ipv6相关、调用上层协议(应该是lwip)里面的初始化函数(涉及内核,不需要我们实现)。
然后用协议栈的netif_add函数添加网卡,并绑定初始化函数tsecEthInit
2.tsecEthInit函数:
从这里开始的实现,部分抄袭了uboot网卡驱动,这里提一句,uboot里面是最简单的轮询的方式才进行接收,比较消耗处理器,而且速度相对而言也比较慢。所以我要改造uboot里面的网卡驱动,将接收从轮询改为中断的方式进行。
这个函数里面进行的步骤如下:
2.1分配DMA区域给实际包缓冲区域:
for (i = 0; i < PKTBUFSRX; i++) {
NetRxPackets[i] = API_VmmDmaAllocAlign(PKTSIZE_ALIGN, PKTALIGN);
}
NetTxPacket = API_VmmDmaAllocAlign(PKTSIZE_ALIGN, PKTALIGN);
然后分配网卡数据结构:
priv = (struct tsec_private *)sys_malloc(sizeof(*priv)); /* 分配网卡数据结构 */
调用tsec_initialize()函数
2.2 int tsec_initialize(int index, struct tsec_private *priv):
为结构体priv里的结构分配内存并初始化phy寄存器、mac控制寄存器、bufferdescriptor(缓冲描述符)。前者调用的是init_phy函数,后二者调用tsec_init函数
init_phy函数抄自UBOOT:
static int init_phy(struct tsec_private *priv)
{
struct phy_info *curphy;
volatile tsec_t *regs = (volatile tsec_t *)(TSEC_BASE_ADDR);
/* Assign a Physical address to the TBI */
regs->tbipa = CFG_TBIPA_VALUE;
regs = (volatile tsec_t *)(TSEC_BASE_ADDR + TSEC_SIZE);
regs->tbipa = CFG_TBIPA_VALUE;
asm("sync");
/* Reset MII (due to new addresses) */
priv->phyregs->miimcfg = MIIMCFG_RESET;
asm("sync");
priv->phyregs->miimcfg = MIIMCFG_INIT_VALUE;
asm("sync");
while (priv->phyregs->miimind & MIIMIND_BUSY) ;
/* Get the cmd structure corresponding to the attached
* PHY */
curphy = get_phy_info(priv);
if (curphy == NULL) {
priv->phyinfo = NULL;
printk(KERN_ERR"%s: No PHY found\n", priv->name);
return 0;
}
priv->phyinfo = curphy;
phy_run_commands(priv, priv->phyinfo->config);
return 1;
}
int tsec_init(struct tsec_private *priv)调用init_registers初始化其他通用控制器(比如重要的ievent与imask),然后调用static startup_tsec(struct tsec_private *priv)来进行缓冲描述符的初始化;
static void startup_tsec(struct tsec_private *priv)
{
int i;
volatile tsec_t *regs = priv->regs;
/* Point to the buffer descriptors */
regs->tbase = (unsigned int)(&rtx.txbd[txIdx]);
regs->rbase = (unsigned int)(&rtx.rxbd[rxIdx]);
/* Initialize the Rx Buffer descriptors */
for (i = 0; i < PKTBUFSRX; i++) {
rtx.rxbd[i].status = RXBD_EMPTY | RXBD_INTERRUPT;
rtx.rxbd[i].length = 0;
rtx.rxbd[i].bufPtr = (UINT32)NetRxPackets[i];
}
rtx.rxbd[PKTBUFSRX - 1].status |= RXBD_WRAP; /* BD的最后一个status要为回卷 */
/* Initialize the TX Buffer Descriptors */
for (i = 0; i < TX_BUF_CNT; i++) {
rtx.txbd[i].status = TXBD_INTERRUPT;
rtx.txbd[i].length = 0;
rtx.txbd[i].bufPtr = 0;
}
rtx.txbd[TX_BUF_CNT - 1].status |= TXBD_WRAP;
/* Start up the PHY */
if(priv->phyinfo)
phy_run_commands(priv, priv->phyinfo->startup);
adjust_link(priv);
/* Enable Transmit and Receive */
regs->maccfg1 |= (MACCFG1_RX_EN | MACCFG1_TX_EN);
/* Tell the DMA it is clear to go */
regs->dmactrl |= DMACTRL_INIT_SETTINGS;
regs->tstat = TSTAT_CLEAR_THALT;
regs->rstat = RSTAT_CLEAR_RHALT;
regs->dmactrl &= ~(DMACTRL_GRS | DMACTRL_GTS);
regs->imask=IMASK_RXFEN0 | IMASK_RXB0;/先只要接收中断打开
}
这里注意一个很
重要的地方,我之前一直是失败的状态就是因为这里:
rtx.rxbd[i].status = RXBD_EMPTY | RXBD_INTERRUPT; //在uboot原始驱动里面是不使用中断的,所以不会有后面那个interrupt部分,必须加上。
2.3 记录网卡数据并设置网卡参数:
pNetif->state = priv; /* 记录网卡数据结构 */
#if LWIP_NETIF_HOSTNAME
pNetif->hostname = HOSTNAME; /* 设置主机名 */
#endif /* LWIP_NETIF_HOSTNAME */
pNetif->name[0] = IFNAME0; /* 设置网卡名 */
pNetif->name[1] = IFNAME1;
NETIF_INIT_SNMP(pNetif, snmp_ifType_ethernet_csmacd, 100000000); /* 初始化 SNMP */
pNetif->output = etharp_output; /* 安装驱动发送函数,上层函数,不用实现 */
pNetif->linkoutput = __tsecEthTx; /* 发包函数,需要自己写 */
#if LWIP_IPV6 > 0
pNetif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 > 0 ,这个函数不用实现 */
lib_memcpy(pNetif->hwaddr,
priv->enetaddr,
ETHARP_HWADDR_LEN); /* 设置 MAC 地址 */
pNetif->hwaddr_len = ETHARP_HWADDR_LEN; /* 设置 MAC 地址长度 */
pNetif->mtu = 1500; /* 设置最大传输单元 */
pNetif->flags = NETIF_FLAG_BROADCAST |
NETIF_FLAG_ETHARP |
NETIF_FLAG_ETHERNET;
这个套路基本上是固定的。
然后这里我为了保证网卡链路链接上,用了一个while循环,这可能不太对:
while (!priv->link) { /*优先初始化网口链路*/
phy_run_commands(priv, priv->phyinfo->startup);
if (priv->link) {
adjust_link(priv);
pNetif->link_speed = priv->speed * 1000 * 1000;
netif_set_link_up(pNetif);
break;
}
}
2.4 注册中断(这里我自己只注册了接收中断并使能)
也就是说网卡驱动需要中断控制器驱动先写好的情况下才能完成,我之前已经写了中断控制器的驱动了,那个相对而言比较简单,就不再这里赘述,值得注意的是IPIC在MPC8377里面实际上中断屏蔽相关寄存器已经变成了SIMSR_H、SIMSR_L、SEMSR三个,注意根据中断号来对不同的屏蔽寄存器进行操作来使能禁能中断。
3. 中断响应函数receiveIsr:
完成的功能:
3.1 关中断,然后使用netJobAdd函数添加一个netTask进行收包:
if(pNetif!=NULL){
if (netJobAdd((VOIDFUNCPTR)tsec_recv,
pNetif,
0, 0, 0, 0, 0) == ERROR_NONE) {
/*
* 关闭接收中断
*/
regs->imask &= ~IMASK_RXFEN0;
}else{
printk(KERN_INFO"The netJobRing is full.\n");
regs->imask &= IMASK_RXFEN0;
}
}else{
printk(KERN_INFO"receiveIsr:error irq\n");
}
while (!(rtx.rxbd[rxIdx].status & RXBD_EMPTY)) {
…………
rtx.rxbd[rxIdx].length = 0;
/* Set the wrap bit if this is the last element in the list */
rtx.rxbd[rxIdx].status =
RXBD_EMPTY | RXBD_INTERRUPT | (((rxIdx + 1) == PKTBUFSRX) ? RXBD_WRAP : 0);
rxIdx = (rxIdx + 1) % PKTBUFSRX;///检查下一个缓冲描述符
}
同样要记住把intterrupt位设置,因为uboot里同样这里没有设置中断,我之前忘记设置了,搞了半天都没有收包中断,所以特别蛋疼。设置完之后记得把index设置到下一个位置再次进行检查。
3.2 在ievent里写1清中断,并且设置imask再使能中断
4.用到的调试技巧:
没有MPC8377的调试器,所以调试只能通过打印的方式进行。打印过的主要信息是:
中断控制器里收到的中断控制号(注意串口的不要打印,不然会输出很多很多……)
收包函数里面打印包的信息,查看数据有没有收到。
打印ievent和imask寄存器值,这里可以使用SylixOS的系统API来安装一个控制台指令,这样就能比较方便的打一条指令就能查看寄存器的值了。
API_TShellKeywordAdd("printEn", (PCOMMAND_START_ROUTINE)__tsecCallDebug); /*安装Debug指令*/
在_tsecCallDebug里面打印自己需要的寄存器的值。
如果嫌打指令麻烦,可以创建线程,但是不推荐,因为打印速度比较快,会看不清信息。建议还是用安装Debug指令的方式查看寄存器的值。
还可以用ints指令看看中断安装是否成功(看enable返回以及最后的count值有没有增加,如下图所示)
最后贴一下互ping图,哈哈哈,搞了将近3个多星期,在没有什么嵌入式基础的情况下,能搞完中断控制器驱动和把uboot的网卡驱动改成中断形式,我觉得虽然效率非常不理想,但是总算是有所收获了吧,还是比较高兴的。