Linux网络子系统之---- PHY 配置
阅读原文时间:2021年04月21日阅读:1

 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; /* 0x.520 - MII Management Config Register */

  3.     u32 miimcom; /* 0x.524 - MII Management Command Register */

  4.     u32 miimadd; /* 0x.528 - MII Management Address Register */

  5.     u32 miimcon; /* 0x.52c - MII Management Control Register */

  6.     u32 miimstat; /* 0x.530 - MII Management Status Register */

  7.     u32 miimind; /* 0x.534 - MII Management Indicator Register */

  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.     /* Set the PHY address and the register address we want to read */

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

  6.     /* Clear miimcom, and then initiate a read */

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

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

  9.     /* Wait for the transaction to finish */

  10.     while (gfar_read(&regs->miimind) & (MIIMIND_NOTVALID | MIIMIND_BUSY))

  11.         cpu_relax();

  12.     /* Grab the value of the register from miimstat */

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

  14.     return value;

  15. }

  16. int gfar_local_mdio_write(struct gfar_mii *regs, int mii_id,

  17.               int regnum, u16 value)

  18. {

  19.     /* Set the PHY address and the register address we want to write */

  20.     gfar_write(&regs->miimadd, (mii_id << 8) | regnum);

  21.     /* Write out the value we want */

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

  23.     /* Wait for the transaction to finish */

  24.     while (gfar_read(&regs->miimind) & MIIMIND_BUSY)

  25.         cpu_relax();

  26.     return 0;

  27. }

内核启动时的准备工作

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
  5. 遍历cpu私有变量_get_cpu_var(softnet_data).poll_list
  6. 取出poll_list上面挂的napi_struct 结构,执行钩子函数napi_struct.poll()
    (设备不同,钩子函数不同,流程类似,TSEC是gfar_poll)
  7. 若poll钩子函数处理完所有包,则打开rx event mask,再来数据包的话会产生rx中断
  8. 调用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
}

http://tech.watchstor.com/storage-network-115062.htm

http://blog.csdn.net/jw212/article/details/6738457

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器