在嵌入式网络设备中,MAC和PHY是两个层级的底层的网络设备。
MAC对应了MAC controller或者叫做Ethernet controller,软件驱动为以太驱动。它创建netdevice,如eth0,负责收发包。MAC层对外的接口为各种类型的GMII,可以连接phy,也可以连接交换机的MAC。
而PHY对应的是Transceiver。
通常ethernet controller集成在SOC中。通过MII接口外接PHY或者交换机。通过MDIO总线控制PHY和交换机。
以MT7981为例,它有两个2 ethernet controller,各导出一个HSGMII接口,每个HSGMII接口的最大速率可以达到2.5Gbps。然后可以有很多种外挂方案:
1 外挂一个MT7531交换机,两个HSGMII都接到交换机,分别接到交换机的port 5和port 6。
这样可以LAN走一个HSGMII,WAN走一个HSGMII。LAN/WAN使用交换机的port 0-4。
软件层面
以太驱动位于:drivers/net/ethernet/mediatek/
负责GMAC初始化,创建网络接口,注册中断,通过DMA收发包。
负责MDIO总线的初始化和注册。
PHY驱动位于:drivers/net/phy/mtk/mt753x/
由于是外挂的交换机,因此mt7531实际上一个phy层的设备。通过MDIO来控制和初始化。
DTS如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91  | ethernet@15100000 {    compatible = "mediatek,mt7981-eth";    reg = <0x00 0x15100000 0x00 0x80000>;    interrupts = <0x00 0xc4 0x04 0x00 0xc5 0x04 0x00 0xc6 0x04 0x00 0xc7 0x04>;    clocks = <0x0e 0x00 0x0e 0x01 0x0e 0x02 0x0e 0x03 0x0f 0x00 0x0f 0x01 0x0f 0x02 0x0f 0x03 0x10 0x00 0x10 0x01 0x10 0x02 0x10 0x03>;    clock-names = "fe\0gp2\0gp1\0wocpu0\0sgmii_tx250m\0sgmii_rx250m\0sgmii_cdr_ref\0sgmii_cdr_fb\0sgmii2_tx250m\0sgmii2_rx250m\0sgmii2_cdr_ref\0sgmii2_cdr_fb";    assigned-clocks = <0x08 0x60 0x08 0x61>;    assigned-clock-parents = <0x08 0x1b 0x08 0x22>;    mediatek,ethsys = <0x0e>;    mediatek,sgmiisys = <0x0f 0x10>;    mediatek,infracfg = <0x11>;    #reset-cells = <0x01>;    #address-cells = <0x01>;    #size-cells = <0x00>;    status = "okay";    mac@0 {        compatible = "mediatek,eth-mac";        reg = <0x00>;        phy-mode = "2500base-x";        fixed-link {            speed = <0x9c4>;            full-duplex;            pause;        };    };    mac@1 {        compatible = "mediatek,eth-mac";        reg = <0x01>;        phy-mode = "2500base-x";        fixed-link {            speed = <0x9c4>;            full-duplex;            pause;        };    };    mdio-bus {        #address-cells = <0x01>;        #size-cells = <0x00>;        phandle = <0x1a>;        ethernet-phy@0 {            compatible = "ethernet-phy-id03a2.9461";            reg = <0x00>;            phy-mode = "gmii";            nvmem-cells = <0x12>;            nvmem-cell-names = "phy-cal-data";        };    };};gsw@0 {    compatible = "mediatek,mt753x";    mediatek,ethsys = <0x0e>;    #address-cells = <0x01>;    #size-cells = <0x00>;    mediatek,mdio = <0x1a>;    mediatek,portmap = "llllw";    mediatek,mdio_master_pinmux = <0x00>;    reset-gpios = <0x0d 0x27 0x00>;    interrupt-parent = <0x0d>;    interrupts = <0x26 0x04>;    status = "okay";    port@5 {        compatible = "mediatek,mt753x-port";        reg = <0x05>;        phy-mode = "sgmii";        fixed-link {            speed = <0x9c4>;            full-duplex;        };    };    port@6 {        compatible = "mediatek,mt753x-port";        mediatek,ssc-on;        reg = <0x06>;        phy-mode = "sgmii";        fixed-link {            speed = <0x9c4>;            full-duplex;        };    };}; | 
2 一个HSGMII接MT7531交换机作为LAN,一个HSGMII接一个2.5G phy作为WAN。
mdio总线是共享总线,外挂的PHY,MAC,都接到一个MDIO总线上。通过PHY Address来通信。每个设备有一个phy address。通信数据广播到总线上,每个设备根据phy address来接收自己的消息。这个phy address是设备自己决定的。通常可以通过引脚的高低电平,来设置设备的phy address,在多个预置的地址里面设置一个,不写死,而是可调,是避免发生地址冲突。
MII总线里面包括MDIO/MDC,来实现MDIO总线的连接。交换机通常有SMI接口,来将交换机接到CPU的MDIO总线,如下:
PHY除了正常的寄存器外,还有MMD寄存器,比如(Energy Efficient Ethernet)相关的寄存器,它们也是通过MDIO访问的,但是读写方式有些特殊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29  | int mt753x_mii_read(struct gsw_mt753x *gsw, int phy, int reg){        int val;        if (phy < MT753X_NUM_PHYS)                phy = (gsw->phy_base + phy) & MT753X_SMI_ADDR_MASK;        mutex_lock(&gsw->mii_lock);        val = mt753x_mii_rw(gsw, phy, reg, 0, MDIO_CMD_READ, MDIO_ST_C22);        mutex_unlock(&gsw->mii_lock);        return val;}int mt753x_mmd_read(struct gsw_mt753x *gsw, int addr, int devad, u16 reg){        int val;        if (addr < MT753X_NUM_PHYS)                addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK;        mutex_lock(&gsw->mii_lock);        mt753x_mii_rw(gsw, addr, devad, reg, MDIO_CMD_ADDR, MDIO_ST_C45);        val = mt753x_mii_rw(gsw, addr, devad, 0, MDIO_CMD_READ_C45,                            MDIO_ST_C45);        mutex_unlock(&gsw->mii_lock);        return val;} | 
SOC的MDIO总线挂一个交换机,交换机再接PHY。那如何读取交换机寄存器和交换机下挂PHY的寄存器呢?
交换机代理,读取交换机下挂PHY的寄存器。
1 交换机寄存器读取
通过MDIO总线指定交换机的phy address来读取交换机的寄存器。也就是说交换机的寄存器,可以通过特殊的PHY寄存器方式通过MDIO读写,具体怎么通过PHY寄存器实现交换机寄存器的读写,这个是交换机设计厂商决定的。例如MT7531:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23  | u32 mt753x_reg_read(struct gsw_mt753x *gsw, u32 reg){        u32 high, low;        mutex_lock(&gsw->host_bus->mdio_lock);        gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f,                (reg & MT753X_REG_PAGE_ADDR_M) >> MT753X_REG_PAGE_ADDR_S);        low = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr,                (reg & MT753X_REG_ADDR_M) >> MT753X_REG_ADDR_S);        high = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, 0x10);        mutex_unlock(&gsw->host_bus->mdio_lock);        return (high << 16) | (low & 0xffff);}void mt753x_reg_write(struct gsw_mt753x *gsw, u32 reg, u32 val){        mutex_lock(&gsw->host_bus->mdio_lock);        gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f,                (reg & MT753X_REG_PAGE_ADDR_M) >> MT753X_REG_PAGE_ADDR_S);        gsw->host_bus->write(gsw->host_bus, gsw->smi_addr,                (reg & MT753X_REG_ADDR_M) >> MT753X_REG_ADDR_S, val & 0xffff);        gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, val >> 16);        mutex_unlock(&gsw->host_bus->mdio_lock);} | 
如上gsw->host_bus是mdio bus,通过mdio bus的read和write标准phy读写寄存器,指定交换机的phy address gsw->smi_addr,来和交换机通信。具体的方案就不研究了。drivers/net/phy/mtk/mt753x/mt753x.h 里面有详细的注释:Procedure of MT753x Internal Register Access。
2 交换机下LAN/WAN PHY寄存器读取
通过交换机代理,通过第一步的接口,读取交换机某个寄存器来实现phy的读写:
具体可以看:drivers/net/phy/mtk/mt753x/mt753x_mdio.c
/* Indirect MDIO clause 22/45 access */
static int mt753x_mii_rw(struct gsw_mt753x *gsw, int phy, int reg, u16 data,
u32 cmd, u32 st)
{
}
int mt753x_mii_read(struct gsw_mt753x *gsw, int phy, int reg)
{
int val;
if (phy < MT753X_NUM_PHYS)
phy = (gsw->phy_base + phy) & MT753X_SMI_ADDR_MASK;
mutex_lock(&gsw->mii_lock);
val = mt753x_mii_rw(gsw, phy, reg, 0, MDIO_CMD_READ, MDIO_ST_C22);
mutex_unlock(&gsw->mii_lock);
return val;
}
void mt753x_mii_write(struct gsw_mt753x *gsw, int phy, int reg, u16 val)
{
if (phy < MT753X_NUM_PHYS)
phy = (gsw->phy_base + phy) & MT753X_SMI_ADDR_MASK;
mutex_lock(&gsw->mii_lock);
mt753x_mii_rw(gsw, phy, reg, val, MDIO_CMD_WRITE, MDIO_ST_C22);
mutex_unlock(&gsw->mii_lock);
}
交换机驱动通常会为自己下挂的PHY注册一个mdio总线,来方便PHY寄存器的读写:
gsw->gphy_bus->name = "mt753x_mdio";
gsw->gphy_bus->read = mt753x_mdio_read;
gsw->gphy_bus->write = mt753x_mdio_write;
gsw->gphy_bus->priv = gsw;
gsw->gphy_bus->parent = gsw->dev;
gsw->gphy_bus->phy_mask = BIT(MT753X_NUM_PHYS) - 1;
参考:
MANAGEMENT REGISTERS AND MIB OBJECTS
https://www.ieee802.org/3/bn/public/mar13/frazier_3bn_01_0313.pdf
https://www.kernel.org/doc/html/latest/networking/phy.html
https://www.totalphase.com/support/articles/200349206-MDIO-Background