1 MAC和PHY体系结构
在嵌入式网络设备中,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如下:
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总线
mdio总线是共享总线,外挂的PHY,MAC,都接到一个MDIO总线上。通过PHY Address来通信。每个设备有一个phy address。通信数据广播到总线上,每个设备根据phy address来接收自己的消息。这个phy address是设备自己决定的。通常可以通过引脚的高低电平,来设置设备的phy address,在多个预置的地址里面设置一个,不写死,而是可调,是避免发生地址冲突。
MII总线里面包括MDIO/MDC,来实现MDIO总线的连接。交换机通常有SMI接口,来将交换机接到CPU的MDIO总线,如下:
PHY MMD寄存器
PHY除了正常的寄存器外,还有MMD寄存器,比如(Energy Efficient Ethernet)相关的寄存器,它们也是通过MDIO访问的,但是读写方式有些特殊。
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; }
交换机寄存器和交换机下phy寄存器读写架构
SOC的MDIO总线挂一个交换机,交换机再接PHY。那如何读取交换机寄存器和交换机下挂PHY的寄存器呢?
交换机代理,读取交换机下挂PHY的寄存器。
1 交换机寄存器读取
通过MDIO总线指定交换机的phy address来读取交换机的寄存器。也就是说交换机的寄存器,可以通过特殊的PHY寄存器方式通过MDIO读写,具体怎么通过PHY寄存器实现交换机寄存器的读写,这个是交换机设计厂商决定的。例如MT7531:
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;