嵌入式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 注册网卡设备