一、网络设备基本结构
网络设备的系统框图如下所示:
mac:工作在网络模型的数据链路层,通过rgmii或rmii接口连接phy,mac控制器中的mdio控制器提供mdio接口,用于访问phy寄存器。
phy:工作在网络模型的物理层,是IEEE802.3规定的一个标准模块。IEEE802.3规定了 地址0~15共16个通用寄存器,只要配置好这些通用寄存器就能保证phy芯片正常工作。16~31地址的寄存器有厂家自行定义。
二、linux中关于网络设备概述
总要结构体
struct phy_device:phy
struct phy_driver:phy驱动
struct mdio_device:mdio从设备,指的就是phy设备
struct bus_type mdio_bus_type:mdio总线
struct mii_bus:mdio总线,定义了mdio接口读写方法
linux通过设备、总线、驱动模型来管理设备和驱动,mac控制器通过mdio总线来管理phy设备,mdio总线与i2c总线类似,可以一个主机对应多个从设备,每个从设备都有地址。mdio最多接32个phy设备。内核中用mdio_bus_type代表mdio总线。是struct bus_type结构体,对应的目录是/sys/mdio,在/sys/mdio/devices目录中会有挂载在mdio的phy设备,在/sys/mdio/drivers中会有phy设备的驱动。hi3559对应的/sys/bus/mdio 目录如下所示:
mii_bus 结构体代表mdio总线,定义了mdio总线的读写方法,这个总线和mdio_bus_type不一样,mdio_bus_type是bus_type,只是为了管理mdio设备和驱动。
struct mii_bus {
struct module *owner;
const char *name;
char id[MII_BUS_ID_SIZE];
void *priv;
int (*read)(struct mii_bus *bus, int addr, int regnum); //mdio总线的读方法
int (*write)(struct mii_bus *bus, int addr, int regnum, u16 val); //mdio总线写方法
int (*reset)(struct mii_bus *bus);
/*
* A lock to ensure that only one thing can read/write
* the MDIO bus at a time
*/
struct mutex mdio_lock;
struct device *parent;
enum {
MDIOBUS_ALLOCATED = 1,
MDIOBUS_REGISTERED,
MDIOBUS_UNREGISTERED,
MDIOBUS_RELEASED,
} state;
struct device dev; //mdio总线实体
/* list of all PHYs on bus */
struct mdio_device *mdio_map[PHY_MAX_ADDR]; //挂载在mdio总线下的phy设备,最多32个,PHY_MAX_ADDR值为32
/* PHY addresses to be ignored when probing */
u32 phy_mask;
/* PHY addresses to ignore the TA/read failure */
u32 phy_ignore_ta_mask;
/*
* An array of interrupts, each PHY's interrupt at the index
* matching its address
*/
int irq[PHY_MAX_ADDR]; //pyh地址
};
三、Hi3559以太网卡驱动加载过程
3.1 hi3559以太网设备树配置
Hi3559以太网控制器设备树节点:Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\arch\arm64\boot\dts\hisilicon\hi3559av100.dtsi:
mac中mdio设备树节点:
添加phy设备树节点:
Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\arch\arm64\boot\dts\hisilicon\hi3559av100-demb.dtsi:
3.2 mac控制器驱动加载
mac控制器入口函数为:linux-4.9.y\drivers\net\ethernet\hisilicon\higmac\higmac.c
可以看到compatible与higmac设备树中的一致,所以higmac_dev_probe就是gmac控制器的驱动入口函数
3.3 phy设备创建
从设备树中可以看出,phy在mdio下面,所以先要加载mdio驱动,再加载phy驱动。mdio驱动入口函数为:linux-4.9.y\drivers\net\phy\mdio-hisi-gemac.c
hisi_gemac_mdio_probe
-->of_mdiobus_register
-->of_mdiobus_register_phy
-->get_phy_device
-->phy_device_register
of_mdiobus_register函数主要功能是注册mdio总线mii_bus,以及创建phy
int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
{
struct device_node *child;
bool scanphys = false;
int addr, rc;
/* Do not continue if the node is disabled */
if (!of_device_is_available(np))
return -ENODEV;
/* Mask out all PHYs from auto probing. Instead the PHYs listed in
* the device tree are populated after the bus has been registered */
mdio->phy_mask = ~0;
mdio->dev.of_node = np;
/* Register the MDIO bus */
rc = mdiobus_register(mdio); //注册mdio总线
if (rc)
return rc;
/* Loop over the child nodes and register a phy_device for each phy */
for_each_available_child_of_node(np, child) { //扫描mdio设备树节点下的所有phy芯片,一条mdio总线最多可以接32个phy芯片
addr = of_mdio_parse_addr(&mdio->dev, child); //读取设备树中phy的地址,即reg的值
if (addr < 0) {
scanphys = true;
continue;
}
if (of_mdiobus_child_is_phy(child)) //判断mdio下面如果是phy芯片,则注册phy
of_mdiobus_register_phy(mdio, child, addr);
else
of_mdiobus_register_device(mdio, child, addr);
}
if (!scanphys)
return 0;
/* auto scan for PHYs with empty reg property */ //如果设备树中没有定义phy地址,则自动扫描
for_each_available_child_of_node(np, child) {
/* Skip PHYs with reg property set */
if (of_find_property(child, "reg", NULL))
continue;
for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
/* skip already registered PHYs */
if (mdiobus_is_registered_device(mdio, addr))
continue;
/* be noisy to encourage people to set reg property */
dev_info(&mdio->dev, "scan phy %s at address %i\n",
child->name, addr);
if (of_mdiobus_child_is_phy(child))
of_mdiobus_register_phy(mdio, child, addr);
}
}
return 0;
}
of_mdiobus_register_phy函数功能是获取phy的id并创建phy,phy id号定义在地址为02和03的寄存器中,由厂家设置。
static void of_mdiobus_register_phy(struct mii_bus *mdio,
struct device_node *child, u32 addr)
{
struct phy_device *phy;
bool is_c45;
int rc;
u32 phy_id;
printk ("yy of_mdiobus_register_phy is run\n");
is_c45 = of_device_is_compatible(child,
"ethernet-phy-ieee802.3-c45");
if (!is_c45 && !of_get_phy_id(child, &phy_id)) //如果设备上定义了phy的id就用设备树中的id,这里设备树没有定义
phy = phy_device_create(mdio, addr, phy_id, 0, NULL);
else
phy = get_phy_device(mdio, addr, is_c45); //从02和03寄存器中读取phy id号,并创建phy
if (IS_ERR(phy))
return;
rc = irq_of_parse_and_map(child, 0);
if (rc > 0) {
phy->irq = rc;
mdio->irq[addr] = rc;
} else {
phy->irq = mdio->irq[addr];
}
if (of_property_read_bool(child, "broken-turn-around"))
mdio->phy_ignore_ta_mask |= 1 << addr;
/* Associate the OF node with the device structure so it
* can be looked up later */
of_node_get(child);
phy->mdio.dev.of_node = child;
/* All data is now stored in the phy struct;
* register it */
rc = phy_device_register(phy); //会在/sys/bus/mdio/devices/ 目录下创建phy设备的软链接,真正的phy设备文件会创建在/sys/devices/platform 目录下
if (rc) {
phy_device_free(phy);
of_node_put(child);
return;
}
dev_dbg(&mdio->dev, "registered phy %s at address %i\n",
child->name, addr);
}
3.4 phy驱动加载
phy驱动入口函数:linux-4.9.y\drivers\net\phy\phy_device.c
这里加载了两个phy驱动,一个是百兆的一个是千兆的。驱动加载成功后,会在/sys/bus/mdio/drivers 目录下生成这个两个驱动目录
phy_drivers_register
-->phy_driver_register
-->driver_register
-->phy_probe
phy_driver_register函数主要是填充new_driver这个结构体,然后调用driver_register注册new_driver,
int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
{
int retval;
new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY;
new_driver->mdiodrv.driver.name = new_driver->name;
new_driver->mdiodrv.driver.bus = &mdio_bus_type; //对应的总线是/sys/bus/mdio
new_driver->mdiodrv.driver.probe = phy_probe;
new_driver->mdiodrv.driver.remove = phy_remove;
new_driver->mdiodrv.driver.owner = owner;
retval = driver_register(&new_driver->mdiodrv.driver);
if (retval) {
pr_err("%s: Error %d in registering driver\n",
new_driver->name, retval);
return retval;
}
pr_debug("%s: Registered new driver\n", new_driver->name);
return 0;
}
3.5 mdio总线创建
linux-4.9.y\drivers\net\phy\mdio_bus.c
这个函数执行完后会在/sys/bus 目录下创建mdio_bus目录,代码mdio总线。