MDIO介绍

MDIO

ethernet-control-and-interfaces_20251009_174016

我们平时看到网口映入眼帘的就是RJ45网口底座,也很少追究网口底座之下还有什么。最近在搞MDIO的驱动,对MAC、PHY之间的关系很陌生。而这两者也是组成网口的物理硬件,所以趁着这次机会弄清楚这两者之间的关系。

上图是组成网口的基本单元,一般SOC芯片内部会集成MAC,然后外接PHY芯片,PHY芯片最后连接RJ45。可以看到MAC与PHY之间有两组连线,其中MDIO是用于MAC与PHY之间的控制线;而GMII或者RGMII是MAC与PHY之间的数据传输线,网口的数据走的就是这组线。

1 MDIO

MDIO 全称是 Management Data Input/Output,直译过来就是管理数据输入输出接口,是一个简单的两线串行接口,一根 MDIO 数据线一根 MDC 时钟线

驱动程序通过MDIOMDC这两根线就可以访问PHY芯片的任意一个寄存器。和i2c总线一样,同一根MDIO总线下的器件地址是唯一的。同一时刻只能对一个PHY进行操作。

MDIO线是双向的数据线,在写PHY寄存器的时候,由MAC驱动MDIO向PHY写入数据;在读PHY寄存器的时候,会从PHY读取寄存器的值。

MAC通过Clause 22和Clause 45两种数据帧读写PHY寄存器。

1.2 Clause 22

Clause 22用于百兆或者千兆以太网设备。最多只能接入32个PHY芯片。Clause 22只有5bit的寄存器地址空间,因此最多只能访问32个寄存器。

detailed-explanation-of-mdio-interface-clause-45-and-clause-22_20251009_174259

Clause 22的数据帧格式如上图。Clause 22的数据帧分为读和写。

  • PRE:帧前导码,为32个连续“1”比特。
  • OP:操作码,CL22中比特“10”表示此帧为一读操作帧,比特“01”表示此帧为一写操作帧
  • MMD的物理地址,5个比特,每个MMD都把自己的地址与这5个比特进行比较,若匹配则响应后面的操作,若不匹配,则忽略掉后面的操作。
  • REGAD:用来选MMD32个寄存器中的某个寄存器的地址。
  • DATA:帧的寄存器的数据域,16比特,若为读操作,则为MMD送到STA的数据,若为写操作,则为STA送到MMD数据。
  • TA:用于地址传输和数据传输之间的空闲时间。由于读操作和写操作的控制方不同,设置2位的TA是为了防止MDIO总线上产生竞争。在读操作时,TA的第1位表示PHY和MAC都释放总线控制,输出高阻态。TA的第2位表示总线由PHY控制,输出低电平(0)。因此,读操作时TA为2'bZ0(由于总线上有上拉电阻存在,从总线上看到的依然是2'b10)。在写操作时,TA由MAC输出2'b10。

clause22里面的PHYAD 通常由PHY设备的硬件配置去决定的,值大小为0-31.注意PHYADPHY ID不要弄混了。 其中phy_addr可以通过ethtool eth0获取PHYAD值。

1.3 Clause 45

由于Clause 22访问的寄存器数量有限,因此在Clause 22的基础上推出了Clause 45,有16bit地址空间,最多能访问65536个寄存器。

detailed-explanation-of-mdio-interface-clause-45-and-clause-22_20251009_174305

  • PRE:帧前导码,为32个连续“1”比特。
  • ST:帧开始标志, Clause22 的开始标志为比特“01”。
  • OP:操作码,CL22中比特“10”表示此帧为一读操作帧,比特“01”表示此帧为一写操作帧。
  • PHYADMMD的物理地址,5个比特,每个MMD都把自己的地址与这5个比特进行比较,若匹配则响应后面的操作,若不匹配,则忽略掉后面的操作。
  • REGAD:用来选MMD32个寄存器中的某个寄存器的地址。
  • TA:状态转换域,若为读操作,则第一比特时MDIO为高阻态,第二比特时由MMD使MDIO置“0”。若为写操作,则MDIO仍由STA控制,连续输出“10”两个比特。
  • DATA:帧的寄存器的数据域,16比特,若为读操作,则为MMD送到STA的数据,若为写操作,则为STA送到MMD数据。
  • IDLE:帧结束后的空闲状态,此时MDIO无源驱动,处高阻状态。

1.4 Clause 22扩展

Clause 22访问超过5bit地址空间的寄存器,可以使用PHY的13和14号寄存器

image-20260308174726664

把13号寄存器的Bit14:15写入Clause 45的OP字段,Bit0:4写入Clause45的DEVAD字段,然后14号寄存器作为Clause 45的数据寄存器。举个例子,github上有个开源的MDIO工具

这个工具就是通过上述这种方式读写超过5bit地址空间的寄存器的。

Clause 22扩展读:

/* Register Control Register */
#define MII_REGCR	0x0d

/* Data register */
#define MII_ADDAR	0x0e
#define MII_ADDAR_DATA_NOINC	0xC000
static int mdio_read_extended_reg(int skfd, int mmd, int location)
{
    // 13号寄存器Bit14:15=00,Bit0:4=mmd,表示需要读取的device
	mdio_write(skfd, MII_REGCR, mmd);
    // 需要把需要读取的寄存器地址发送PHY芯片
	mdio_write(skfd, MII_ADDAR, location);
    /* 13号寄存器Bit14:15=11,Bit0:4=mmd,表示要读取location寄存器数据
       需要注意的是Bit14:15=11,表示在读写寄存器后,PHY芯片的地址指针会加一。
       如果不想地址指针移动,可以根据上图填入合适的值。
    */
	mdio_write(skfd, MII_REGCR, mmd | MII_ADDAR_DATA_NOINC);
    // 从14号寄存器读取数据
	return mdio_read(skfd, MII_ADDAR);
}

Clause 22扩展写:

static void mdio_write_extended_reg(int skfd, int mmd, int location, int value)
{
    // 13号寄存器Bit14:15=00,Bit0:4=mmd,表示需要读取的device
	mdio_write(skfd, MII_REGCR, mmd);
    // 需要把需要读取的寄存器地址发送PHY芯片
	mdio_write(skfd, MII_ADDAR, location);
    /* 13号寄存器Bit14:15=11,Bit0:4=mmd,表示要读取location寄存器数据
       需要注意的是Bit14:15=11,表示在读写寄存器后,PHY芯片的地址指针会加一。
       如果不想地址指针移动,可以根据上图填入合适的值。
    */
	mdio_write(skfd, MII_REGCR, mmd | MII_ADDAR_DATA_NOINC);
    // 写入寄存器的数据
	mdio_write(skfd, MII_ADDAR, value);
}

2 Aspeed 2600 MDIO驱动

Aspeed 2600芯片的驱动实际上很简单,就只需要读写MDC/MDIO Bus的控制寄存器和数据寄存器即可。

image-20260308184230562

可以看到控制寄存器实际上就是按照Clause 22和Clause 45协议填入对应的字段。

image-20260308184303061

Clause 45读:

static int aspeed_mdio_op(struct mii_bus *bus, u8 st, u8 op, u8 phyad, u8 regad,
			  u16 data)
{
	struct aspeed_mdio *ctx = bus->priv;
	u32 ctrl;

	dev_dbg(&bus->dev, "%s: st: %u op: %u, phyad: %u, regad: %u, data: %u\n",
		__func__, st, op, phyad, regad, data);
	/* 
	   把phyad、regad、st、op和data写入控制寄存器对应的比特位上;
	   如果是读操作,那data对应的bit位全部填0即可。
    */
	ctrl = ASPEED_MDIO_CTRL_FIRE
		| FIELD_PREP(ASPEED_MDIO_CTRL_ST, st)
		| FIELD_PREP(ASPEED_MDIO_CTRL_OP, op)
		| FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, phyad)
		| FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, regad)
		| FIELD_PREP(ASPEED_MDIO_DATA_MIIRDATA, data);
	/* 
	   把组装好的控制寄存器的值写入寄存器 
    */
	iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL);
	/* Add dummy read to ensure triggering mdio controller */
	(void)ioread32(ctx->base + ASPEED_MDIO_CTRL);
	/* 
	   循环读取bit31的值等于0为止,否则超时。读到为0,表示数据发送完成。
    */
	return readl_poll_timeout(ctx->base + ASPEED_MDIO_CTRL, ctrl,
				!(ctrl & ASPEED_MDIO_CTRL_FIRE),
				ASPEED_MDIO_INTERVAL_US,
				ASPEED_MDIO_TIMEOUT_US);
}

static int aspeed_mdio_read_c45(struct mii_bus *bus, int addr, int devad,
				int regnum)
{
	int rc;
	/* 
	   Clause 45读取寄存器的第一步就是把devad和regad地址发送给对应的PHY芯片 
    */
	rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_ADDR,
			    addr, devad, regnum);
	if (rc < 0)
		return rc;
	/* 
	   Clause 45读取寄存器的第二步就是发送一条读命令给PHY芯片
    */
	rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_READ,
			    addr, devad, 0);
	if (rc < 0)
		return rc;
	/* 
	   从MDIO的数据寄存器读取PHY芯片返回的数据 
    */
	return aspeed_mdio_get_data(bus);
}

Clause 45写:

static int aspeed_mdio_op(struct mii_bus *bus, u8 st, u8 op, u8 phyad, u8 regad,
			  u16 data)
{
	struct aspeed_mdio *ctx = bus->priv;
	u32 ctrl;

	dev_dbg(&bus->dev, "%s: st: %u op: %u, phyad: %u, regad: %u, data: %u\n",
		__func__, st, op, phyad, regad, data);
	/* 
	   把phyad、regad、st、op和data写入控制寄存器对应的比特位上;
	   如果是读操作,那data对应的bit位全部填0即可。
    */
	ctrl = ASPEED_MDIO_CTRL_FIRE
		| FIELD_PREP(ASPEED_MDIO_CTRL_ST, st)
		| FIELD_PREP(ASPEED_MDIO_CTRL_OP, op)
		| FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, phyad)
		| FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, regad)
		| FIELD_PREP(ASPEED_MDIO_DATA_MIIRDATA, data);
	/* 
	   把组装好的控制寄存器的值写入寄存器 
    */
	iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL);
	/* Add dummy read to ensure triggering mdio controller */
	(void)ioread32(ctx->base + ASPEED_MDIO_CTRL);
	/* 
	   循环读取bit31的值等于0为止,否则超时。读到为0,表示数据发送完成。
    */
	return readl_poll_timeout(ctx->base + ASPEED_MDIO_CTRL, ctrl,
				!(ctrl & ASPEED_MDIO_CTRL_FIRE),
				ASPEED_MDIO_INTERVAL_US,
				ASPEED_MDIO_TIMEOUT_US);
}

static int aspeed_mdio_write_c45(struct mii_bus *bus, int addr, int devad,
				 int regnum, u16 val)
{
	int rc;
	/* 
	   把devad和regad地址发送给对应的PHY芯片
	*/
	rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_ADDR,
			    addr, devad, regnum);
	if (rc < 0)
		return rc;
	/*
	   把需要发送的数据写入控制寄存器,把OP Code设置为MDIO_C45_OP_WRITE
    */
	return aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_WRITE,
			      addr, devad, val);
}

3 Reference

  1. https://openkits-wiki.easyeda.com/zh-hans/linux-docs/complex-device-peripheral-driver/ethernet/ethernet-control-and-interfaces.html

  2. http://wangzaifa.com/2025/02/20/PHY管理接口MDIO/

  3. https://wiki.lckfb.com/zh-hans/linux-docs/complex-device-peripheral-driver/ethernet/detailed-explanation-of-mdio-interface-clause-45-and-clause-22.html

  4. https://zhuanlan.zhihu.com/p/682337429

  5. https://www.ieee802.org/3/efm/public/nov02/oam/pannell_oam_1_1102.pdf

posted @ 2026-03-08 19:04  cockpunctual  阅读(1)  评论(0)    收藏  举报