嵌入式Linux开发模式下,以太网硬件架构一般都是 MAC与PHY是独立的。所以以太网模块的硬件相关的驱动代码主要包括 GMAC 和 PHY,其中MAC控制器驱动由SoC厂商开发,PHY芯片驱动由PHY厂商开发,PHY 驱动一般使用通用 PHY 驱动,如果有需要修改特殊寄存器,请使用对应的 PHY 驱动,代码都在 drivers/net/phy。当然,驱动之间要完成通信,必须严格按照IEEE802.3制定的协议。
以RK芯片为例,RK系列的Soc中内置了以太网MAC控制器,所以只需要搭配一颗以太网PHY芯片就可以实现以太网功能。按照规范 就算是不同的厂家的PHY,仍然有一部分寄存器的定义是通用的,只要配置了这些通用的寄存器,基本上PHY就可以正常工作。所以一般情况下,如果不需要使用PHY厂家提供的自定义的寄存器配置实现一些个性化的功能,那么PHY驱动就基本不需要修改。所以Linux驱动中有通用的PHY驱动。
本文只对MAC控制器进行简单说明
MAC控制器通过MDIO总线来管理phy设备,mdio总线与i2c总线类似,可以一个主机对应多个从设备,每个从设备都有地址。mdio最多接32个phy设备。
对应的目录是/sys/mdio,在/sys/mdio/devices目录中会有挂载在mdio的phy设备,在/sys/mdio/drivers中会有phy设备的驱动。
如:
 /sys/bus/mdio_bus/devices/stmmac-0:00 
其中 stmmac-0:00 表示 PHY 地址是 0。
该命令会读取 0~31 的所有寄存器,所以可以查看对应的寄存器值
cat /sys/bus/mdio_bus/devices/stmmac-0:00/phy_registers
 
Linux3.10 版本内核
 GMAC 驱动代码 driver/net/ethernet/rockchip/gmac/*
其它内核
 GMAC 驱动代码,高于3.10 的内核版本,
 GMAC 驱动代码位置 : drivers/net/ethernet/stmicro/stmmac/*
GMAC控制器相关设备树资源:
gmac: ethernet@ffc40000 {
		compatible = "rockchip,rv1126-gmac", "snps,dwmac-4.20a";
		reg = <0xffc40000 0x0ffff>;
		interrupts = <GIC_SPI 95 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-names = "macirq", "eth_wake_irq";
		rockchip,grf = <&grf>;
	...
		//关于MDC/MDIO通信相关配置信息
		mdio: mdio {
			compatible = "snps,dwmac-mdio";
			#address-cells = <0x1>;
			#size-cells = <0x0>;
		};
	...
	};
&gmac {
	phy-mode = "rmii"; //!!!  rmii模式
	clock_in_out = "output";// output 时钟由MAC输入给PHY input与之相反
    snps,reset-gpio = <&gpio3 RK_PA4 GPIO_ACTIVE_LOW>;//用于复位PHY的GPIO
    snps,reset-active-low;//复位PHY的GPIO低电平有效
    /* Reset time is 20ms, 100ms for rtl8211f */
    snps,reset-delays-us = <0 100000 100000>;// 复位PHY之前延时0ms  拉低维持的时间为100ms 拉高后延时100ms
	 // MAC时钟源
    assigned-clocks = <&cru CLK_GMAC_SRC_M0>, <&cru CLK_GMAC_SRC>, <&cru CLK_GMAC_TX_RX>;
    // MAC父时钟源
    assigned-clock-parents = <&cru CLK_GMAC_RGMII_M0>, <&cru CLK_GMAC_SRC_M0>, <&cru RMII_MODE_CLK>; 
     // MAC时钟频率
    assigned-clock-rates = <0>, <50000000>;
	pinctrl-names = "default";
    pinctrl-0 = <&rmiim0_pins &gmac_clk_m0_drv_level0_pins>;
	status = "okay";
	phy-handle = <&phy>;// 指定与MAC连接的PHY的配置信息
};
&mdio {
  //指定与MAC连接的PHY的配置信息
  phy: phy@1 {
	  // mdio和PHY之间的通信协议为22
    compatible = "ethernet-phy-ieee802.3-c22";
	  // phy硬件地址与与硬件上的phy地址相对应
    reg = <0x1>;
    clocks = <&cru CLK_GMAC_ETHERNET_OUT>;
  };
};
 
drivers\net\ethernet\stmicro\stmmac\dwmac-rk.c
static const struct of_device_id rk_gmac_dwmac_match[] = {
	...
	{ .compatible = "rockchip,px30-gmac",	.data = &px30_ops   },
	{ .compatible = "rockchip,rk3288-gmac", .data = &rk3288_ops },
	{ .compatible = "rockchip,rv1126-gmac", .data = &rv1126_ops },
	{ }
};
MODULE_DEVICE_TABLE(of, rk_gmac_dwmac_match);
static int rk_gmac_probe(struct platform_device *pdev)
{
	//gmac控制器所有配置信息
	struct plat_stmmacenet_data *plat_dat;
	
	//gmac控制器资源数据
	struct stmmac_resources stmmac_res;
	
	...
	/* 从设备树解析 gmac控制器所有配置信息
	如:
	struct plat_stmmacenet_data *plat;
		int interface;//接口类型 rmii  PHY_INTERFACE_MODE_RMII
		int phy_addr;// phy 地址 默认设置为-1 表示phy自动检测
		struct device_node *phy_node; //phy配置节点
		struct device_node *mdio_node; // mdio总线配置节点
		struct stmmac_mdio_bus_data *mdio_bus_data;//mdio总线数据	
		...
		
	*/
	plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac);
...
	
	/* 主要工作
		struct platform_device
			struct device	dev;
			
		struct plat_stmmacenet_data //gmac控制器所有配置信息
		struct stmmac_resources //gmac控制器资源
	*/
	ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
	if (ret)
		goto err_gmac_powerdown;
	...
}
 
drivers\net\ethernet\stmicro\stmmac\stmmac_platform.c
struct plat_stmmacenet_data *
stmmac_probe_config_dt(struct platform_device *pdev, const char **mac)
{
	struct device_node *np = pdev->dev.of_node;
	struct plat_stmmacenet_data *plat;
	struct stmmac_dma_cfg *dma_cfg;
	int rc;
	// gmac控制器所有配置信息
	plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL);
	if (!plat)
		return ERR_PTR(-ENOMEM);
	//设备树中没有指定固定的 MAC地址,所以这里还是空
	*mac = of_get_mac_address(np);
	
	//Interface Mode definitions    PHY_INTERFACE_MODE_RMII  -- rmii
	/*
	struct plat_stmmacenet_data *plat;
		int interface;//接口类型 rmii  PHY_INTERFACE_MODE_RMII
	*/
	plat->interface = of_get_phy_mode(np);
	/* Get max speed of operation from device tree */
	//没有
	if (of_property_read_u32(np, "max-speed", &plat->max_speed))
		plat->max_speed = -1;
...
	/* Default to phy auto-detection */
	//默认 phy 自动检测
	/*
	struct plat_stmmacenet_data *plat;
		int phy_addr;// phy 地址 默认设置为-1 表示phy自动检测
	*/
	plat->phy_addr = -1;
...
	/* To Configure PHY by using all device-tree supported properties */
	//解析设备树驱动参数来分配PHY资源
	/*
	struct plat_stmmacenet_data *plat
	struct platform_device
		struct device	dev;
			struct device_node	*of_node;
			
	struct platform_device
		struct device	dev;
	解析设备树获取:
	struct plat_stmmacenet_data *plat;
		struct device_node *phy_node; //phy配置节点
		struct device_node *mdio_node; // mdio总线配置节点
	*/
	rc = stmmac_dt_phy(plat, np, &pdev->dev);
	...
}
 
.
//解析设备树驱动参数来分配PHY资源
/*
struct plat_stmmacenet_data *plat
struct platform_device
	struct device	dev;
		struct device_node	*of_node;
		
struct platform_device
	struct device	dev;
*/
/*
	gmac: ethernet@ffc40000 {
		...
		mdio: mdio {
			compatible = "snps,dwmac-mdio";
		}
		...
	}
	&gmac{
		...
		 //指定与MAC连接的PHY的配置信息
		 //phy: phy@1
		phy-handle = <&phy>;
	}
*/	
static int stmmac_dt_phy(struct plat_stmmacenet_data *plat,
			 struct device_node *np, struct device *dev)
{
	bool mdio = true;
	static const struct of_device_id need_mdio_ids[] = {
		{ .compatible = "snps,dwc-qos-ethernet-4.10" },
		{},
	};
	/* If phy-handle property is passed from DT, use it as the PHY */
	//获取 设备树中   与MAC连接的PHY的配置信息
	plat->phy_node = of_parse_phandle(np, "phy-handle", 0);
	if (plat->phy_node)
		dev_dbg(dev, "Found phy-handle subnode\n");
	...
	if (of_match_node(need_mdio_ids, np)) {
		plat->mdio_node = of_get_child_by_name(np, "mdio");
	} else {
		// 关于MDC/MDIO通信相关配置信息
		/**
		 * If snps,dwmac-mdio is passed from DT, always register
		 * the MDIO
		 */
		//遍历 gmac: ethernet 的所有子节点 找到 mdio 节点
		/*
			gmac: ethernet@ffc40000 {
				...
				mdio: mdio {
					compatible = "snps,dwmac-mdio";
				}
				...
			}
		*/
		for_each_child_of_node(np, plat->mdio_node) {
			if (of_device_is_compatible(plat->mdio_node,
						    "snps,dwmac-mdio"))
				break;
		}
	}
	//至此 找到了 gmac 控制器节点下的 mdio总线节点信息
	if (plat->mdio_node) {
		dev_dbg(dev, "Found MDIO subnode\n");
		mdio = true;
	}
	if (mdio)
		plat->mdio_bus_data =
			devm_kzalloc(dev, sizeof(struct stmmac_mdio_bus_data),
				     GFP_KERNEL);
	return 0;
}
 
drivers\net\ethernet\stmicro\stmmac\stmmac_main.c
 工作:

/*
	struct platform_device
		struct device	dev;
		
	struct plat_stmmacenet_data //gmac控制器所有配置信息
	struct stmmac_resources //gmac控制器资源
*/
int stmmac_dvr_probe(struct device *device,
		     struct plat_stmmacenet_data *plat_dat,
		     struct stmmac_resources *res)
{
	
	struct net_device *ndev = NULL;
	struct stmmac_priv *priv;
	u32 queue, maxq;
	int ret = 0;
	//申请网卡
	ndev = alloc_etherdev_mqs(sizeof(struct stmmac_priv),
				  MTL_MAX_TX_QUEUES,
				  MTL_MAX_RX_QUEUES);
	if (!ndev)
		return -ENOMEM;
	SET_NETDEV_DEV(ndev, device);
//即通过struct net_device *dev首地址加对齐后的偏移量就得到了私有数据的首地址
//直接返回了net_device结构末端地址,也就是网卡私有数据结构的起始地址。当然其中考虑了字节对齐的问题
/*
	struct net_device *ndev 
		...
	}+ 网卡私有数据
	struct stmmac_priv
*/
	priv = netdev_priv(ndev);
	priv->device = device;
	priv->dev = ndev;
	
	
/*网卡操作集合
struct net_device *ndev 
	const struct ethtool_ops *ethtool_ops;
*/
	stmmac_set_ethtool_ops(ndev);
	
	
/*
	struct net_device {
	}++网卡私有数据
	struct stmmac_priv 							  //gmac控制器所有配置信息
		struct plat_stmmacenet_data *plat; ------ struct plat_stmmacenet_data *plat;
*/
	priv->plat = plat_dat;
	priv->ioaddr = res->addr;
	priv->dev->base_addr = (unsigned long)res->addr;
	
	...
	
	/* MDIO bus Registration */
	/*
	暂时分析的主要工作:
	1 设置总线信息,并注册0-32地址中识别到的phy到 mdio总线,即添加到 mdio_map[]数组
							  +---struct net_device *ndev 					+-------struct device_node *phy_node; //phy配置节点
			//mido总线		  |												|
		+---struct mii_bus	  |												|
		|		void *priv;---+												|
		|		struct device dev;											|
		|			struct device_node	*of_node;---------------------------+
		|		const char *name;//"stmmac";
		|		int (*read)(struct mii_bus *bus, int addr, int regnum);
		|		int (*write)(struct mii_bus *bus, int addr, int regnum, u16 val);
		|		int (*reset)(struct mii_bus *bus);
		|		struct device dev;
		|			struct class	*class; 
		|				.name		= "mdio_bus",
		|				
		|		/* list of all PHYs on bus */
		|	+---struct mdio_device *mdio_map[PHY_MAX_ADDR];//mdio设备数组 ---------------------------------------+
		|	|																									 |
		|	|																									 |
		|	|																									 |
		|	|	// phy 设备					  // phy 设备 					// phy 设备				             |
		|	|	struct phy_device				struct phy_device				 struct phy_device				 |
		|	+-------struct mdio_device mdio;--------struct mdio_device mdio;--------struct mdio_device mdio;-----+
		|				//phy地址 						//phy地址 						//phy地址
		|				int addr;						int addr;						int addr;
		+---------------struct mii_bus *bus;------------struct mii_bus *bus;------------struct mii_bus *bus;
						struct device dev;				struct device dev;				struct device dev;
	
	*/
	ret = stmmac_mdio_register(ndev);
	
	//注册网卡设备
	ret = register_netdev(ndev);
}
 
drivers\net\ethernet\stmicro\stmmac\stmmac_mdio.c
/*
							//网卡设备												//gmac控制器所有配置信息
					+---- struct net_device *ndev									struct plat_stmmacenet_data *plat;
					|																+----struct device_node *phy_node; //phy配置节点
					|                                                               |
					|                                                               |
	//mido总线		|	                                                            |
+---struct mii_bus  |                                                               |
|		void *priv;-+                                                               |
|		struct device dev;                                                          |
|			struct device_node	*of_node;-------------------------------------------+
|		const char *name;//"stmmac";
|		int (*read)(struct mii_bus *bus, int addr, int regnum);
|		int (*write)(struct mii_bus *bus, int addr, int regnum, u16 val);
|		int (*reset)(struct mii_bus *bus);
|		struct device dev;
|			struct class	*class; 
|				.name		= "mdio_bus",
|				
|		/* list of all PHYs on bus */
|	+---struct mdio_device *mdio_map[PHY_MAX_ADDR];//mdio设备数组 ---------------------------------------+
|	|                                                                                                    |
|	|                                                                                                    |
|	|                                                                                                    |
|	|	// phy 设备                     // phy 设备                     // phy 设备                      |
|	|	struct phy_device 				struct phy_device				 struct phy_device               |
|	+-------struct mdio_device mdio;--------struct mdio_device mdio;--------struct mdio_device mdio;-----+
|				//phy地址						//phy地址						//phy地址
|				int addr;						int addr;						int addr;
+---------------struct mii_bus *bus;------------struct mii_bus *bus;------------struct mii_bus *bus;
				struct device dev;				struct device dev;				struct device dev;
		
*/
int stmmac_mdio_register(struct net_device *ndev)
{
	int err = 0;
	struct mii_bus *new_bus;
	struct stmmac_priv *priv = netdev_priv(ndev);
	struct stmmac_mdio_bus_data *mdio_bus_data = priv->plat->mdio_bus_data;
	struct device_node *mdio_node = priv->plat->mdio_node;
	struct device *dev = ndev->dev.parent;
	int addr, found, max_addr;
...
	//分配 mii_bus
	new_bus = mdiobus_alloc();
	if (!new_bus)
		return -ENOMEM;
	if (mdio_bus_data->irqs)
		memcpy(new_bus->irq, mdio_bus_data->irqs, sizeof(new_bus->irq));
#ifdef CONFIG_OF
	if (priv->device->of_node)
		mdio_bus_data->reset_gpio = -1;
#endif
	//总线名
	new_bus->name = "stmmac";
	//总线读写 复位 函数
	...
	new_bus->read = &stmmac_mdio_read;
	new_bus->write = &stmmac_mdio_write;
	max_addr = PHY_MAX_ADDR;//32
	new_bus->reset = &stmmac_mdio_reset;
	/*
	总线 ---bind--- 网卡设备
	struct mii_bus
		void *priv;---------+ struct net_device *ndev
	*/
	new_bus->priv = ndev;
	new_bus->phy_mask = mdio_bus_data->phy_mask;
	new_bus->parent = priv->device;
	/*
	struct mii_bus
	struct stmmac_priv 
		struct plat_stmmacenet_data *plat
			struct device_node *mdio_node;
	*/
	/* 注册 mdio总线
	
	目前分析的主要工作:
	1
	struct mii_bus
		struct device dev;								struct plat_stmmacenet_data
			struct device_node	*of_node;-------------------struct device_node *phy_node; //phy配置节点
	
	2
	将总线上 0- 32 地址 识别到但是并未注册的 phy设备,注册到 mdio总线,即添加到 将该phy添加到 mdio_map[]数组
	*/
	err = of_mdiobus_register(new_bus, mdio_node);
	if (err != 0) {
		dev_err(dev, "Cannot register the MDIO bus\n");
		goto bus_register_fail;
	}
...
}
 
drivers\of\of_mdio.c
/* 注册 mii_bus 并且根据设备树信息创建 PHYS 
struct mii_bus
struct stmmac_priv 
	struct plat_stmmacenet_data *plat
		struct device_node *mdio_node;
目前分析的主要工作:
1
struct mii_bus
	struct device dev;								struct plat_stmmacenet_data
		struct device_node	*of_node;-------------------struct device_node *phy_node; //phy配置节点
2
将总线上 0- 32 地址 识别到但是并未注册的 phy设备,注册到 mdio总线,即添加到 将该phy添加到 mdio_map[]数组
*/
int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
{
	struct device_node *child;
	bool scanphys = false;
	int addr, rc;
	if (!np)
		return mdiobus_register(mdio);
	/* Do not continue if the node is disabled */
	if (!of_device_is_available(np))
		return -ENODEV;
	/* PHY addresses to be ignored when probing */
	//设置为全真
	mdio->phy_mask = ~0;
	/*
	struct mii_bus
		struct device dev; 								struct plat_stmmacenet_data
			struct device_node	*of_node;-------------------struct device_node *phy_node; //phy配置节点
	*/
	mdio->dev.of_node = np;
	mdio->dev.fwnode = of_fwnode_handle(np);
	// Register the MDIO bus 
	/*
	
	主要工作:
	将总线上 0- 32 地址 识别到但是并未注册的 phy设备,注册到 mdio总线,即添加到 将该phy添加到 mdio_map[]数组
	
	*/
	rc = mdiobus_register(mdio);
	...
	return 0;
unregister:
	mdiobus_unregister(mdio);
	return rc;
}
EXPORT_SYMBOL(of_mdiobus_register)
 
#define mdiobus_register(bus) __mdiobus_register(bus, THIS_MODULE)
drivers\net\phy\mdio_bus.c
/*
主要工作:
将总线上 0- 32 地址 识别到但是并未注册的 phy设备,注册到 mdio总线,即添加到 将该phy添加到	mdio_map[]数组
*/
int __mdiobus_register(struct mii_bus *bus, struct module *owner)
{
	struct mdio_device *mdiodev;
	int i, err;
	struct gpio_desc *gpiod;
...
	bus->owner = owner;
	bus->dev.parent = bus->parent;
	bus->dev.class = &mdio_bus_class;
	bus->dev.groups = NULL;
	dev_set_name(&bus->dev, "%s", bus->id);
...
	//32
	/*
		检索 探测时忽略的PHY地址
	*/
	for (i = 0; i < PHY_MAX_ADDR; i++) {
		if ((bus->phy_mask & (1 << i)) == 0) {
			struct phy_device *phydev;
		 /*
		
		遍历 0 - 32 地址中每一个地址,检查该地址是否有 phy,并检测有没有注册到mdio总线,没有的话 注册到mdio总线,
		即 将该phy添加到	mdio_map[]数组
		*/
			phydev = mdiobus_scan(bus, i);
			if (IS_ERR(phydev) && (PTR_ERR(phydev) != -ENODEV)) {
				err = PTR_ERR(phydev);
				goto error;
			}
		}
	}
...
}
EXPORT_SYMBOL(__mdiobus_register);
/*
遍历 0 - 32 地址中每一个地址,检查该地址是否有 phy,并检测有没有注册到mdio总线,没有的话 注册到mdio总线,
即 将该phy添加到  mdio_map[]数组
*/
struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr)
{
	struct phy_device *phydev;
	int err;
	//检查该地址 是否有 phy
	phydev = get_phy_device(bus, addr, false);
	if (IS_ERR(phydev))
		return phydev;
...
	//注册该 PHY
	//就是将该 phy 添加到 mdio_map[]数组。 即注册
	err = phy_device_register(phydev);
	if (err) {
		phy_device_free(phydev);
		return ERR_PTR(-ENODEV);
	}
	return phydev;
}
EXPORT_SYMBOL(mdiobus_scan);
 
drivers\net\phy\phy_device.c
 /*
向 mdio总线添加 phy 设备
	
*/
int phy_device_register(struct phy_device *phydev)
{
	int err;
/*
struct phy_device
	struct mdio_device mdio;
*/
/*
工作:
	检查 0 - 32 之前没有检测的地址 是否有phy存在,有的添加到 mdio_map[]数组
*/
	err = mdiobus_register_device(&phydev->mdio);
	if (err)
		return err;
	/* Deassert the reset signal */
	//复位 PHY
	phy_device_reset(phydev, 0);
	/* Run all of the fixups for this PHY */
	//暂时不知道 phy_fixup_list 是干啥的
	// 如果该phy有设定一些额外的操作 这里就会执行phy 所设定的额外操作 如led灯控制等
	err = phy_scan_fixups(phydev);
	...
}
EXPORT_SYMBOL(phy_device_register);
 
drivers\net\phy\mdio_bus.c
/*
struct phy_device
	struct mdio_device mdio;
*/
/*
工作:
	检查 0 - 32 之前没有检测的地址 是否有phy存在,有的添加到 mdio_map[]数组
*/
int mdiobus_register_device(struct mdio_device *mdiodev)
{
	int err;
/*
struct phy_device
	struct mdio_device mdio;
		struct mii_bus *bus;
			//list of all PHYs on bus
			struct mdio_device *mdio_map[PHY_MAX_ADDR];
*/
//如果已经存在于 mdio_map[]数组,直接返回
	if (mdiodev->bus->mdio_map[mdiodev->addr])
		return -EBUSY;
	if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) {
		err = mdiobus_register_gpiod(mdiodev);
		if (err)
			return err;
	}
//添加到 mdio_map[]数组。 即注册
	mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev;
	return 0;
}
EXPORT_SYMBOL(mdiobus_register_device);
 
至此暂时分析到的 GMAC主要工作
 1 创建网卡设备 struct net_device,并根据 GMAC控制器 设备树资源,整合信息。
 2 创建 mido总线 struct mii_bus,并遍历总线上 0 - 31 每一个地址,检查该地址是否有 phy,并检测有没有注册到mdio总线,没有的话 注册到mdio总线,就是 将该phy添加到 mdio_map[]数组
 3 注册网卡设备


















![[年终总结] 2023,希望一切都能够好起来](https://img-blog.csdnimg.cn/a53506bb40394275a400c4a181166c8f.jpeg)
