千兆网口 Freescale ETSEC + Marvell 88E1111 uboot Linux 驱动分析

在连续两个平台的uboot和Linux系统移植过程中,在千兆网口调试这块都遇到了很大的麻烦。由于寄存器数量庞大,千兆网口MAC和PHY内部结构复杂,MAC和PHY接口种类多,千兆以太网驱动的调试成了系统移植过程中最让人烦心的一个环节。就像火箭队,每次都让球迷无比揪心,不是输的窝囊,就是伤兵满营,现在新赛季又两连败了,打的比勇士还勇士,后场两个比我还瘦的家伙,怎么防守。算了,不扯这么多了,今天要说的是网口MAC+PHY的一些原理和代码分析。(以Freescale的ETSEC和Marvell的88E1111为例。)

 

1 千兆以太网的物理层

 

千兆以太网的物理层分为物理编码子层PCS(Physical Coding Sublayer)、物理介质连接子层PMA(Physical Medium Attachment)和物理介质相关子层PMD(Physical Medium Dependent)三层,如下图所示:

其中PCS子层负责8b10b编码,它可以把从GMII口接收到的8位并行的数据转换成10位并行的数据输出。因为10比特的数据能有效地减小直流分量,降低误码率,另外采用8b10b编码便于在数据中提取时钟和进行首发同步。可以把PCS两头看成GMII接口和TBI接口。

PMA子层进一步将PCS子层的编码结果向各种物理媒体传送,主要是负责完成串并转换。PCS层以125M的速率并行传送10位代码到PMA层,由PMA层转换为1.25Gbps的串行数据流进行发送,以便实际能得到1Gbps的千兆以太网传送速率。可以把PMA子层的两头分别看做TBI接口和SGMII接口。

PMD子层将对各种实际的物理媒体完成接口,完成真正的物理连接。由于1000BASE-X支持多种物理媒介,如光纤和屏蔽双绞线,它们的物理接口显然不会相同。有的要进行光电转换,有的要完成从不平衡到平衡的转换。PMD层将对这些具体的连接器作出规定。

 

2 Freescale 的ETSEC与PHY之间的接口

 

Freescale的MPC8314和P2020都自带了三速以太网控制器ETSEC,可以提供10M,100M,1000M三种速率的接口。当作为以太网时,需要外部的PHY芯片或者Serdes设备与其相连接。每个ETSEC都支持多标准的MII接口,总体结构如下图所示,可以提供GMII,RGMII,MII,RMII,RTBI,SGMII 六种接口,下图为从MPC8314 datasheet中截取的ETSEC的结构图。

 

     如果CPU与PHY之间是GMII接口或RGMII接口,那么PHY将提供完整的PCS,PMA,PMD三层工作;如果CPU与PHY之间是RTBI接口,那么PCS层的工作在ETSEC中已经做完了,ETSEC中的TBI模块可以做PCS层的工作,PHY只需要做PMA和PMD的工作即可;如果CPU与PHY之间是SGMII接口,那么PHY只需要完成PMD的工作,ETSEC中的PCS由TBI完成,而PMA由CPU自带的Serdes模块完成。

 

3 BD表结构

在千兆以太网的驱动中,现在一般都使用一个叫BD表的东西来管理MAC层发送和接收的内存区域,如下图所示:

 

 

在IMMR映射的寄存器空间中有两组寄存器TBASEn和RBASEn,分别为TxBD Ringn 和 RxBD Ringn的指针。MPC8314的ETSEC允许有8个TxBD Ring和8个RxBD Ring,他们都存放在内存的某个区域中。每个Buffer Descriptor 都是有8个字节构成,两个字节的状态,两个字节的数据长度和四个字节的数据指针,这个指针指向内存的另一块地方,这才是真正存储发送接收数据的地方。Buffer Descriptor必须在网口初始化的时候初始化,并将自己的地址赋给TBASEn和RBASEn。

在网口驱动程序中可以看到,每个BD Ring中的BD数量是可变的(我们设为64),而他们之间并没有指针连接,只是一段连续的空间,顺序下来的,所谓的环只是一个虚拟的概念,在最后一个BD时,需要将BD状态位中的W位(Wrap)置一,表示这是最后一个BD,他的下一个BD就是第一个BD。如下图所示:

 

 

 

下面一节将结合uboot源码分析一下网口初始化以及PHY配置的过程,再下一节会分析内核中的驱动。为什么先说uboot,因为在我看来,驱动程序就是分为两个部分,1 按照Datasheet的说明去配置寄存器,2 添加符合操作系统规范去融入操作系统。在uboot下系统很简单,代码一目了然,所以我们应该在boot下先把寄存器配置好,再去分析复杂的多的内核代码。



这节分析uboot中的网口驱动代码。

 

1 网口驱动函数列表

 

函数名

函数用途

tsec_initialize()

网口初始化函数

tsec_init()

网口启动函数

tsec_local_mdio_write()

MDIO口写函数

tsec_local_mdio_read()

MDIO口读函数

tsec_send()

网口发送函数

tsec_recv()

网口接收函数

tsec_configure_serdes()

配置TBI PHY的函数

fsl_serdes_init()

Serdes模块初始化函数

init_phy()

PHY初始化函数

adjust_link()

根据PHY状态配置MAC的函数

 

2 tsec_initialize()函数

   

该函数为ETSEC的初始化函数,在该函数中要初始化eth_device结构和私有的tsec_private结构,并初始化PHY。

 

int tsec_initialize(bd_t * bis, int index, char *devname)

{

       struct eth_device *dev;

       int i;      

       struct tsec_private *priv;     

/*为dev分配空间*/

       dev = (struct eth_device *)malloc(sizeof *dev);  

       if (NULL == dev)       

              return 0;     

       memset(dev, 0, sizeof *dev);

 /*为priv分配空间*/

       priv = (struct tsec_private *)malloc(sizeof(*priv));     

       if (NULL == priv)             

              return 0;     

 

       /*从tsec_info 数组中取合适的值去初始化私有结构tsec_private*/

       privlist[num_tsecs++] = priv;     

       priv->regs = tsec_info[index].regs;  //每个tsec寄存器的基址

       priv->phyregs = tsec_info[index].miiregs;     //PHY MDIO读写状态寄存器基址

    /*TBI PHY的MDIO读写状态寄存器基址*/

       priv->phyregs_sgmii = tsec_info[index].miiregs_sgmii;  

       priv->phyaddr = tsec_info[index].phyaddr; //PHY 地址

       priv->flags = tsec_info[index].flags;

       priv->ID = index;

    /*使用将priv结构体挂到dev结构体下,挂载tsec的打开、关闭、发送、接收函数*/

       sprintf(dev->name, tsec_info[index].devname); 

       dev->iobase = 0;     

       dev->priv = priv;     

       dev->init = tsec_init;      

       dev->halt = tsec_halt;    

       dev->send = tsec_send;      

       dev->recv = tsec_recv;

           /*初始化IP地址*/

       for (i = 0; i < 6; i++)          

              dev->enetaddr[i] = 0;     

       /*设置当前活跃的网口名相当于 set ethact eTSECn,将多个网口级联*/

eth_register(dev);

 

       /* 通过设置MACCFG1寄存器重启 MAC */      

       priv->regs->maccfg1 |= MACCFG1_SOFT_RESET;      

       udelay(2);  /* Soft Reset must be asserted for 3 TX clocks */ 

       priv->regs->maccfg1 &= ~(MACCFG1_SOFT_RESET);

      

/*挂载MII口的读写函数*/

       #if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) /  

              && !defined(BITBANGMII)    

              miiphy_register(dev->name, tsec_miiphy_read, tsec_miiphy_write);

       #endif  

 

       /* 初始化PHY,并返回 */      

       return init_phy(dev);

}

 

3 init_phy()

 

static int init_phy(struct eth_device *dev)

{

       struct tsec_private *priv = (struct tsec_private *)dev->priv;  

       struct phy_info *curphy;

       volatile tsec_t *regs = priv->regs;    

 

       /*在TBIPA的寄存器中写入TBI PHY的地址*/    

       regs->tbipa = CONFIG_SYS_TBIPA_VALUE + priv->ID;

       asm("sync");     

       /* 重启MII接口 */

       priv->phyregs->miimcfg = MIIMCFG_RESET; 

       asm("sync");     

       priv->phyregs->miimcfg = MIIMCFG_INIT_VALUE;      

       asm("sync");     

       while (priv->phyregs->miimind & MIIMIND_BUSY) ;    

      

/* 通过读PHY的2号3号寄存器获得该ETSEC外连的PHY的ID,搜索phy_info数组,找到符合ID的PHY信息返回。 */     

       curphy = get_phy_info(dev);      

       if (curphy == NULL)

              {

              priv->phyinfo = NULL;           

              printf("%s: No PHY found/n", dev->name);           

              return 0;     

              }

 

    /*如果是SGMII的接口,需要使用TBI PHY,初始化TBI PHY,注意这里名字竟然叫serdes配置,Linux里面也这么叫,真是误人子弟啊。*/

       if (regs->ecntrl & ECNTRL_SGMII_MODE)             

             tsec_configure_serdes(priv);

       /*在符合条件的PHY的phy_info数组中调用其初始化配置函数*/

priv->phyinfo = curphy; 

       phy_run_commands(priv, priv->phyinfo->config);     

    return 1;

}

 

4 phy_info结构

 

Uboot中使用这个结构来完成phy的操作,所有的phy都要使用这个结构表示,下面是88E1111的phy_info结构:

struct phy_info phy_info_M88E1111S = {

           0x01410cc,  // PHY ID

              "Marvell 88E1111S",       // PHY名称

              4,   

              (struct phy_cmd[])

              {     

              /* 配置数组,在调用priv->phyinfo->config时将依次调用下面的内容,每个大括号内,第一个为PHY寄存器地址,第二个为value*/

              /* Reset and configure the PHY */            

              {MIIM_CONTROL, MIIM_CONTROL_RESET, NULL},            

              /* Delay RGMII TX and RX */         

              {MIIM_GBIT_CONTROL, MIIM_GBIT_CONTROL_INIT, NULL},           

              {MIIM_ANAR, MIIM_ANAR_INIT, NULL},           

              {MIIM_CONTROL, MIIM_CONTROL_RESET, NULL},            

              {MIIM_CONTROL, MIIM_CONTROL_INIT, &mii_cr_init},

              {miim_end,}

              },    

              (struct phy_cmd[])

              {     

              /* 启动数组,在ETSEC启动的时候要依次调用。 */             

              /* Status is read once to clear old link state */            

              {MIIM_STATUS, miim_read, NULL},           

              /* Auto-negotiate */         

              {MIIM_STATUS, miim_read, &mii_parse_sr},         

              /* Read the status */       

              {MIIM_88E1011_PHY_STATUS, miim_read, &mii_parse_88E1011_psr},       

              {miim_end,} },    

              (struct phy_cmd[])

              {     

              /* shutdown */          

              {miim_end,}

              },

};

需要注意的是,这个数组时uboot的源码中提供的,但是由于PHY与MAC之间接口使用的不同,这个数组中的内容需要根据需要,参考相应PHY的datasheet作出一定的修改。

 

5 tsec_init()

 

    该函数不会在初始化的时候调用,它在每当你使用网口的时候被调用,使用网口,不管是ping,还是tftp。

 

int tsec_init(struct eth_device *dev, bd_t * bd)

{

       uint tempval;    

       char tmpbuf[MAC_ADDR_LEN];  

       int i;      

       struct tsec_private *priv = (struct tsec_private *)dev->priv;  

       volatile tsec_t *regs = priv->regs;

      

       /* 初始化MACCFG2和ECNTRL两个寄存器,这两个寄存器非常重要,它们主要是用来是配置MAC对PHY的接口。在这里先给个初始化的值,默认为GMII。*/ 

       tsec_halt(dev); 

       regs->maccfg2 = MACCFG2_INIT_SETTINGS;

       regs->ecntrl = ECNTRL_INIT_SETTINGS; 

       /* 配置MAC地址。 */     

       for (i = 0; i < MAC_ADDR_LEN; i++)

              {

              tmpbuf[MAC_ADDR_LEN - 1 - i] = dev->enetaddr[i];   

              }     

       tempval = (tmpbuf[0] << 24) | (tmpbuf[1] << 16) | (tmpbuf[2] << 8) |

              tmpbuf[3];  

       regs->macstnaddr1 = tempval;  

       tempval = *((uint *) (tmpbuf + 4));      

       regs->macstnaddr2 = tempval;  

       /* reset the indices to zero */      

       rxIdx = 0;    

       txIdx = 0;     

       /* 清除其它的寄存器 */   

       init_registers(regs);

       /* 启动tsec */    

       startup_tsec(dev);  

       /* If there's no link, fail */      

       return (priv->link ? 0 : -1);

}

 

6 startup_tsec()

 

static void startup_tsec(struct eth_device *dev)

{

       int i;

       struct tsec_private *priv = (struct tsec_private *)dev->priv;

       volatile tsec_t *regs = priv->regs;

 

       /* 初始化BD表基址指针 */

       regs->tbase = (unsigned int)(&rtx.txbd[txIdx]);

       regs->rbase = (unsigned int)(&rtx.rxbd[rxIdx]);

 

       /* 初始化RX BD*/

       for (i = 0; i < PKTBUFSRX; i++) {

              rtx.rxbd[i].status =( RXBD_EMPTY | RXBD_INTERRUPT);

              rtx.rxbd[i].length = 0;

              rtx.rxbd[i].bufPtr = (uint) NetRxPackets[i];

       }

       rtx.rxbd[PKTBUFSRX - 1].status |= RXBD_WRAP;

 

       /*初始化TX BD*/

       for (i = 0; i < TX_BUF_CNT; i++) {

              rtx.txbd[i].status = 0;

              rtx.txbd[i].length = 0;

              rtx.txbd[i].bufPtr = 0;

       }

       rtx.txbd[TX_BUF_CNT - 1].status |= TXBD_WRAP;

 

       /*又要去找phy_info数组了,这次调用的是startup中的命令和函数*/

       if(priv->phyinfo)

              phy_run_commands(priv, priv->phyinfo->startup);

      

/*根据PHY的Copper侧值配置MAC寄存器*/

adjust_link(dev);

 

 

       /* 使能MACCFG1中的发送接收使能 */

       regs->maccfg1 |= (MACCFG1_RX_EN | MACCFG1_TX_EN);

 

       /* 让DMA知道可以准备搬运了这里的DMA是ETSEC内部的,并不是CPU中的DMA单元。*/

       regs->dmactrl |= DMACTRL_INIT_SETTINGS;

       regs->tstat = TSTAT_CLEAR_THALT;

       regs->dmactrl &= ~(DMACTRL_GRS | DMACTRL_GTS);

}

 

参照上面的phy_info数组的startup中的内容得知这里phy_run_commands(priv, priv->phyinfo->startup)要调用两个函数mii_parse_sr和mii_parse_88E1011_psr。

 

这两个函数主要是配置三个重要的priv结构体中的成员

priv->link

priv->speed

priv-> duplexity

分别是link状态,速率和双工。具体的代码就不分析了,主要是读PHY的Copper侧寄存器,然后根据寄存器的值去配置这三个成员,在后面的adjust_link函数中会根据这三个成员的值去配置MAC的MACCFG2和ECNTRL寄存器。



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值