Linux网络子系统之---- PHY 配置

来源:互联网 发布:澳大利亚签证 知乎 编辑:程序博客网 时间:2024/06/03 20:03

Linux网络子系统之---- PHY 配置

 

网址:http://blog.sina.com.cn/s/blog_5426448c0102wfv6.html

 MII即媒体独立接口,也叫介质无关接口。

它包括一个数据接口,以及一个MAC和PHY之间的管理接口(图1)。

数据接口包括分别用于发送器和接收器的两条独立信道。每条信道都有自己的数据、时钟和控制信号。MII数据接口总共需16个信号。

管理接口是个双信号接口:一个是时钟信号,另一个是数据信号。通过管理接口,上层能监视和控制PHY。


RMII口是用两根线来传输数据的,

MII口是用4根线来传输数据的,

GMII是用8根线来传输数据的。

GMII (Gigabit MII)

GMII是8bit并行同步收发接口,采用8位接口数据,工作时钟125MHz,因此传输速率可达1000Mbps。同时兼容MII所规定的10/100 Mbps工作方式。

GMII接口数据结构符合IEEE以太网标准。该接口定义见IEEE802.3-2000。

发送器:

◇ GTXCLK——吉比特TX..信号的时钟信号(125MHz)

◇ TXCLK——10/100M信号时钟

◇ TXD[7..0]——被发送数据  -------  mii  为4,所以一个信道是8,两个是16

◇ TXEN——发送器使能信号

◇ TXER——发送器错误(用于破坏一个数据包)

注:在千兆速率下,向PHY提供GTXCLK信号,TXD、TXEN、TXER信号与此时钟信号同步。否则,在10/100M速率下,PHY提供 TXCLK时钟信号,其它信号与此信号同步。其工作频率为25MHz(100M网络)或2.5MHz(10M网络)。

接收器:

◇ RXCLK——接收时钟信号(从收到的数据中提取,因此与GTXCLK无关联)

◇ RXD[7..0]——接收数据

◇ RXDV——接收数据有效指示

◇ RXER——接收数据出错指示

◇ COL——冲突检测(仅用于半双工状态)

管理配置

◇ MDC——配置接口时钟

◇ MDIO——配置接口I/O

管理配置接口控制PHY的特性。该接口有32个寄存器地址,每个地址16位。其中前16个已经在“IEEE 802.3,2000-22.2.4Management Functions”中规定了用途,其余的则由各器件自己指定。

MII/RMII只是一种接口,对于10M线速,MII的速率是2.5M,RMII则是5M;对于100M线速,MII的速率是25M,RMII则是50M。

SGMII--Serial Gigabit Media IndependentInterface

SGMII是PHY与MAC之间的接口,类似与GMII和RGMII,只不过GMII和RGMII都是并行的,而且需要随路时钟,PCB布线相对麻烦,而且不适应背板应用。

而SGMII是串行的,不需要提供另外的时钟,MAC和PHY都需要CDR去恢复时钟。另外SGMII是有8B/10b编码的,速率是1.25G


在 linux 配置PHY

drivers/net/phy

配置的参数  自适应, 1000M, 全双工。

phydev-> autonet, speed, duplex.


1. MDIO简介

  The MDIO interface is a simple, two-wire, serial interface to connect a management entity and a managed PHY for the purposes of controlling the PHY and gathering status from the PHY.
   The two lines include the MDC line [Management Data Clock], and the MDIO line [Management Data Input/Output]. The clock is point-to-point, while the data line is a bi-directional multi-drop interface.
   The data line is Tri-state able and can drive 32 devices.

   MDIO接口,MAC与PHY间的管理接口(MII是数据接口),有2根线:时钟线MDC,数据线MDIO(双向)



   MDIO工作流程:
    * Preamle(PRE)       在没有传输数据的空闲状态时,数据线MDIO处于高阻态(一直为1)。
    * Start of Frame(ST) MAC驱动MDIO线,出现一个2bit的开始标识码(01)。
    * Operation Code(OP) MAC驱动MDIO线,出现一个2bit数据来标识是读操作(10)还是写操作(01)。
    * PHY Address(PHYAD) MAC驱动MDIO线,出现一个5bit数据标识PHY的地址。
    * Reg Address(REGAD) MAC驱动MDIO线,出现一个5bitPHY寄存器地址。
    * Turnaround(TA)     写操作的话,MAC驱动MDIO线,出现10
                         读操作的话,MDIO pin of MAC must be put in high-impedance state
                                     在第二个周期,PHY驱动MDIO线,出现0

    * Data               MDIO串行读出/写入16bit的寄存器数据。

    * MDIO恢复成空闲状态,同时MDIO进入高阻状态。


    下面是PHY芯片 BCM5461 的一个例子:


2. PowerPC对MDIO的支持

PowerPC操作MDIO时,涉及以下寄存器:
MIIMCFG  配置寄存器
MIIMCOM  命令寄存器
MIIMADD  地址寄存器
MIIMCON  控制寄存器
MIIMSTAT 状态寄存器
MIIMIND  指示寄存器

以MPC8560举例,这些寄存器在CCSR中的位置如下:






2.1 MIIMCFG:配置寄存器


ResetMgmt:   用于重置MDIO模块
MgmtClockSet:时钟设置,是CCB的 2的n次方之一



2.2 MIIMCOM  命令寄存器


ReadCycle: 0->1 触发MDIO读时序


2.3 MIIMADD  地址寄存器


PHYaddr:PHY地址,共5bit,系统最多联31个PHY(地址0为保留)
REGaddr:寄存器地址,共5bit,一个PHY上最多32个寄存器地址(可以使用shadow value技术,访问更多的寄存器)


2.4 MIIMCON  控制寄存器



PHYcontrol:在写流程时,这里存放要写入寄存器的值


2.5 MIIMSTAT 状态寄存器



PHYstatus:读流程时,PHY reg的内容会放到此

2.6 MIIMIND  指示寄存器


NotVal:若置1,表示读流程结束,可以去读MIIMSTAT
Scan:  若置1,表示扫描流程进行中
Busy:  只有置0时,才能进行新的读写流程



3. linux中MDIO的实现

读写PHY寄存器时通过2个函数 

phy_read()和phy_write(),

最终调用
int gfar_local_mdio_read(struct gfar_mii *regs, int mii_id, int regnum)
int gfar_local_mdio_write(struct gfar_mii *regs, int mii_id, int regnum, u16 value)

参数regs就是MDIO相关寄存器:
  1. struct gfar_mii {
  2.     u32 miimcfg; 
  3.     u32 miimcom; 
  4.     u32 miimadd; 
  5.     u32 miimcon; 
  6.     u32 miimstat; 
  7.     u32 miimind; 
  8. };
参数mii_id,就是PHY的id
参数regnum,就是寄存器地址


上代码,简单不解释
  1. int gfar_local_mdio_read(struct gfar_mii *regs, int mii_id, int regnum)

  2. {
  3.     u16 value;

  4.     
  5.     gfar_write(&regs->miimadd, (mii_id <</span><</span> 8) | regnum);

  6.     
  7.     gfar_write(&regs->miimcom, 0);
  8.     gfar_write(&regs->miimcom, MII_READ_COMMAND);

  9.     
  10.     while (gfar_read(&regs->miimind) & (MIIMIND_NOTVALID | MIIMIND_BUSY))
  11.         cpu_relax();

  12.     
  13.     value = gfar_read(&regs->miimstat);

  14.     return value;
  15. }


  1. int gfar_local_mdio_write(struct gfar_mii *regs, int mii_id,
  2.               int regnum, u16 value)
  3. {
  4.     
  5.     gfar_write(&regs->miimadd, (mii_id <</span><</span> 8) | regnum);

  6.     
  7.     gfar_write(&regs->miimcon, value);

  8.     
  9.     while (gfar_read(&regs->miimind) & MIIMIND_BUSY)
  10.         cpu_relax();

  11.     return 0;
  12. }



内核启动时的准备工作

4.1 初始化网络相关的全局数据结构,并挂载处理网络相关软中断的钩子函数
start_kernel()
    --> rest_init()
        --> do_basic_setup()
            --> do_initcall
               -->net_dev_init

__init net_dev_init()
{
    //每个CPU都有一个CPU私有变量 _get_cpu_var(softnet_data)
    //_get_cpu_var(softnet_data).poll_list很重要,软中断中需要遍历它的

    for_each_possible_cpu(i) {
        struct softnet_data *queue;
        queue = &per_cpu(softnet_data, i);
        skb_queue_head_init(&queue->input_pkt_queue);
        queue->completion_queue = NULL;
      INIT_LIST_HEAD(&queue->poll_list);
        queue->backlog.poll = process_backlog;
        queue->backlog.weight = weight_p;
    }
    open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL); //在软中断上挂网络发送handler
    open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL); //在软中断上挂网络接收handler
}
   4.2 加载网络设备的驱动
NOTE:这里的网络设备是指MAC层的网络设备,即TSEC和PCI网卡(bcm5461是phy)
在网络设备驱动中创建net_device数据结构,并初始化其钩子函数 open(),close() 等
挂载TSEC的驱动的入口函数是 gfar_probe

// 平台设备 TSEC 的数据结构
static struct platform_driver gfar_driver = {
    .probe = gfar_probe,
    .remove = gfar_remove,
    .driver = {
        .name = "fsl-gianfar",
    },
};

int gfar_probe(struct platform_device *pdev)
{
    dev = alloc_etherdev(sizeof (*priv)); // 创建net_device数据结构

    dev->open = gfar_enet_open;
    dev->hard_start_xmit = gfar_start_xmit;
    dev->tx_timeout = gfar_timeout;
    dev->watchdog_timeo = TX_TIMEOUT;
#ifdef CONFIG_GFAR_NAPI
    netif_napi_add(dev, &priv->napi,gfar_poll,GFAR_DEV_WEIGHT); //软中断里会调用poll钩子函数
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
    dev->poll_controller = gfar_netpoll;
#endif
    dev->stop = gfar_close;
    dev->change_mtu = gfar_change_mtu;
    dev->mtu = 1500;
    dev->set_multicast_list = gfar_set_multi;
    dev->set_mac_address = gfar_set_mac_address;
    dev->ethtool_ops = &gfar_ethtool_ops;
}


五、启用网络设备
5.1 用户调用ifconfig等程序,然后通过ioctl系统调用进入内核
socket的ioctl()系统调用
    --> sock_ioctl()
        --> dev_ioctl()                              //判断SIOCSIFFLAGS
          --> __dev_get_by_name(net, ifr->ifr_name)  //根据名字选net_device
             --> dev_change_flags()                  //判断IFF_UP
                --> dev_open(net_device)             //调用open钩子函数 

对于TSEC来说,挂的钩子函数是 gfar_enet_open(net_device)

5.2 在网络设备的open钩子函数里,分配接收bd,挂中断ISR(包括rx、tx、err),对于TSEC来说

gfar_enet_open
    --> 给Rx Tx Bd 分配一致性DMA内存 
    --> 把Rx Bd的“EA地址”赋给数据结构,物理地址赋给TSEC寄存器
    --> 把Tx Bd的“EA地址”赋给数据结构,物理地址赋给TSEC寄存器
    --> 给 tx_skbuff 指针数组 分配内存,并初始化为NULL
    --> 给 rx_skbuff 指针数组 分配内存,并初始化为NULL

    --> 初始化Tx Bd
    --> 初始化Rx Bd,提前分配存储以太网包的skb,这里使用的是一次性dma映射
       (注意:#define DEFAULT_RX_BUFFER_SIZE  1536保证了skb能存一个以太网包)
        rxbdp = priv->rx_bd_base;
        for (i = 0; i < priv->rx_ring_size; i++) {
            struct sk_buff *skb = NULL;
            rxbdp->status = 0;
            //这里真正分配skb,并且初始化rxbpd->bufPtr, rxbdpd->length
           skb = gfar_new_skb(dev, rxbdp);    
            priv->rx_skbuff[i] = skb;

            rxbdp++;
        }
        rxbdp--;
        rxbdp->status |= RXBD_WRAP; // 给最后一个bd设置标记WRAP标记
        
    --> 注册TSEC相关的中断handler: 错误,接收,发送
        request_irq(priv->interruptError, gfar_error, 0, "enet_error", dev)
        request_irq(priv->interruptTransmit, gfar_transmit, 0, "enet_tx", dev)//包发送完
        request_irq(priv->interruptReceive, gfar_receive, 0, "enet_rx", dev)  //包接收完

    -->gfar_start(net_device)
        // 使能Rx、Tx
        // 开启TSEC的 DMA 寄存器
        // Mask 掉我们不关心的中断event


最终,TSEC相关的Bd等数据结构应该是下面这个样子的

六、中断里接收以太网包

 TSEC的RX已经使能了,网络数据包进入内存的流程为:
    网线 --> Rj45网口 --> MDI 差分线
         --> bcm5461(PHY芯片进行数模转换) --> MII总线 
         --> TSEC的DMA Engine 会自动检查下一个可用的Rx bd 
         --> 把网络数据包 DMA 到 Rx bd 所指向的内存,即skb->data


接收到一个完整的以太网数据包后,TSEC会根据event mask触发一个 Rx 外部中断。
cpu保存现场,根据中断向量,开始执行外部中断处理函数do_IRQ()

do_IRQ 伪代码
{
   上半部处理硬中断
       查看中断源寄存器,得知是网络外设产生了外部中断
       执行网络设备的rx中断handler(设备不同,函数不同,但流程类似,TSEC是gfar_receive
          1. mask 掉 rx event,再来数据包就不会产生rx中断
          2. 给napi_struct.state加上 NAPI_STATE_SCHED 状态
          3. 挂网络设备自己的napi_struct结构到cpu私有变量_get_cpu_var(softnet_data).poll_list
          4. 触发网络接收软中断
    下半部处理软中断
        依次执行所有软中断handler,包括timer,tasklet等等
        执行网络接收的软中断handler net_rx_action
          1. 遍历cpu私有变量_get_cpu_var(softnet_data).poll_list 
          2. 取出poll_list上面挂的napi_struct 结构,执行钩子函数napi_struct.poll()
             (设备不同,钩子函数不同,流程类似,TSEC是gfar_poll)
          3. 若poll钩子函数处理完所有包,则打开rx event mask,再来数据包的话会产生rx中断
          4. 调用napi_complete(napi_struct *n)
             把napi_struct 结构从_get_cpu_var(softnet_data).poll_list 上移走
             同时去掉 napi_struct.state 的 NAPI_STATE_SCHED 状态
}

6.1 TSEC的接收中断处理函数
gfar_receive
{
#ifdef CONFIG_GFAR_NAPI
    // test_and_set当前net_device的napi_struct.state 为 NAPI_STATE_SCHED
    // 在软中断里调用 net_rx_action 会检查状态 napi_struct.state

    if (netif_rx_schedule_prep(dev, &priv->napi)) {  
        tempval = gfar_read(&priv->regs->imask);            
        tempval &= IMASK_RX_DISABLED; //mask掉rx,不再产生rx中断
        gfar_write(&priv->regs->imask, tempval);    
        // 将当前net_device的 napi_struct.poll_list 挂到
        // CPU私有变量__get_cpu_var(softnet_data).poll_list 上,并触发软中断
        // 所以,在软中断中调用 net_rx_action 的时候,就会执行当前net_device的
        // napi_struct.poll()钩子函数,即 gfar_poll()

        __netif_rx_schedule(dev, &priv->napi);   
    } 
#else
    gfar_clean_rx_ring(dev, priv->rx_ring_size);
#endif
}

原创粉丝点击