RK356x网口驱动学习记录
网口驱动probe流程
网口驱动匹配成功后会调用网口驱动中的probe函数,probe函数主要有以下几点。
1.申请注册mii_bus
mii_bus用来绑定mac驱动中实现的mdio和phy通信的读写函数。mdio->phy_mask = ~0;会屏蔽掉自动检测phy的机制最终使用设备树中的节点。并且会和后面申请的phy_device中的mdio_devie的mii_bus进行绑定。注册mii_bus的流程如下。
//drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
stmmac_mdio_register(ndev); //注册mdio bus
new_bus = mdiobus_alloc(); //分配一个mii_bus 结构体,mii_bus主要绑定mac驱动中通过mdio与phy通信的接口
new_bus->read = &stmmac_mdio_read;
new_bus->write = &stmmac_mdio_write; //绑定mac 中实现的与phy通信读写函数,用于mdio回调
//drivers/of/of_mdio.c
//new_bus, mdio, bus都为申请的mii_bus
of_mdiobus_register(new_bus, mdio_node); //注册new_bus即注册mii_bus
mdio->phy_mask = ~0; //屏蔽自动检测,使用设备树中配置的phy节点,不进行逐个地址扫描
//include/linux/phy.h
mdiobus_register(mdio);
//drivers/net/phy/mdio_bus.c
__mdiobus_register(bus, THIS_MODULE);
//设置mii_bus中的device结构体,并注册device
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);
device_register(&bus->dev); //注册设备即mii_bus->device
priv->mii = new_bus;//设置网卡驱动的私有数据结构中的mii_bus为new_bus,mac驱动中操作phy时会传入priv,最终调用priv-->mii->read/write
2.创建一个phy_device
创建phy_device会先解析出设备树中的phy地址,通过mdio读取phy的id,最后会用来和phy_driver匹配。申请一个phy_device,设置mdio_device的父设备为mii_bus,设置mdio_device的device总线类型为mdio_bus_type,将mdio_device和mii_bus绑定mdiodev->bus = bus,设置mdiodev->bus_match最终在匹配phy_device和phy_driver时被调用内部通过匹配ID进行匹配。设置phy状态机,用来轮询phy的状态,在up网卡时会调用网卡驱动的open函数最终开启状态机并周期性调度状态机。
//include/linux/of_mdio.h
addr = of_mdio_parse_addr(&mdio->dev, child); //解析设备树中的phy地址
//drivers/net/mdio/of_mdio.c
of_mdiobus_register_phy(mdio, child, addr); //创建phy_device
//drivers/net/phy/phy_device.c
get_phy_device(mdio, addr, is_c45); //获取phy id 并创建phy,设置phy状态机回调函数
get_phy_c22_id(bus, addr, &phy_id); //通过mdio读取phy的ID用来和phy_driver匹配
phy_device_create(bus, addr, phy_id, is_c45, &c45_ids); //最终创建phy_device并设置phy状态机
//申请一个phy_device
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
//设置mdio_device为phy_device->mdio_device
mdiodev = &dev->mdio;
//设置mdio_device->device.parent = &mii_bus->device
mdiodev->dev.parent = &bus->dev;
//设置mdio_device的device的总线为mdio_bus_type, mdio_bus总线在drivers/net/phy/mdio_bus.c中注册
mdiodev->dev.bus = &mdio_bus_type;
mdiodev->dev.type = &mdio_bus_phy_type;
//mdiodev->bus = mii_bus,将phy_deviec中的mdio_device的mii_bus和上面申请的mii_bus绑定
mdiodev->bus = bus;
mdiodev->bus_match = phy_bus_match;//最终用来匹配phye_device和phy_driver,通过比较ID来匹配
mdiodev->addr = addr;
mdiodev->flags = MDIO_DEVICE_FLAG_PHY;
mdiodev->device_free = phy_mdio_device_free;
mdiodev->device_remove = phy_mdio_device_remove;
//phy_device的状态机,用来轮询检测phy的link状态
INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
void phy_state_machine(struct work_struct *work)
{
switch (phydev->state) {
// 在 PHY_DOWN/PHY_READY 状态下不动作
case PHY_DOWN:
case PHY_READY:
break;
/* PHY_UP 状态下,表明网口被 up 起来,需要启动自协商并且查询自协商后的 link 状态
如果自协商结果是 link up,进入 PHY_RUNNING 状态
如果自协商结果是 link down,进入 PHY_NOLINK 状态 */
case PHY_UP:
needs_aneg = true;
break;
// 在运行的过程中定时轮询 link 状态
case PHY_NOLINK:
case PHY_RUNNING:
err = phy_check_link_status(phydev);
break;
}
// 如果需要,启动自协商配置
if (needs_aneg)
err = phy_start_aneg(phydev);
// 如果 phy link 状态有变化,通知给对应网口 netdev
if (old_state != phydev->state) {
phydev_dbg(phydev, "PHY state change %s -> %s\n",
phy_state_to_str(old_state),
phy_state_to_str(phydev->state));
if (phydev->drv && phydev->drv->link_change_notify)
phydev->drv->link_change_notify(phydev);
}
// 重新启动 work,周期为 1s
if (phy_polling_mode(phydev) && phy_is_started(phydev))
phy_queue_state_machine(phydev, PHY_STATE_TIME);
}
3.注册phy_device
将创建的phy_device进行注册,先将phy_device中的mdio_device添加到mii_bus的mdio_map数组中,以phy的addr作为数组项,然后将phy_device中的mdio_device的device添加到mdio总线上。最终总线进行phy_device和phy_driver进行匹配,调用到mdio_device->bus_match即phy_bus_match,函数内部根据phy的ID进行phy_device和phy_driver匹配。
//注册创建的phy_device
of_mdiobus_phy_device_register(mdio, phy, child, addr);
//drivers/net/phy/phy_device.c
phy_device_register(phydev); //注册phy_device
mdiobus_register_device(&phydev->mdio);//注册phy_device中的mdio_device
//将mdio_device放入mii_bus中的mdio_map中
mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev;
//drivers/base/core.c
err = device_add(&phydev->mdio.dev); //向mdio 总线添加设备
//drivers/base/bus.c
error = bus_add_device(dev); //放入链表
bus_probe_device(dev); //probe 枚举设备找到匹配的 drv dev
//drivers/base/dd.c
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver(struct device_driver *drv, void *_data);
driver_match_device(drv, dev); //是否匹配
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
//调用mdio_device->phy_bus_match
//上面设置的mdiodev->bus_match = phy_bus_match;
mdio->bus_match(dev, drv);//即调用phy_bus_match
phy_bus_match(struct device *dev, struct device_driver *drv)
{
//比较ID来匹配phy_device和phy_driver
(phydrv->phy_id & phydrv->phy_id_mask) ==
(phydev->phy_id & phydrv->phy_id_mask);
}
4.创建phylink
创建一个phylink,具体为创建一个pl->resolve的工作队列,设置mac的具体的phylink操作函数pl->mac_ops = mac_ops。工作队列主要用来轮询phy_device状态机更新的phy状态,然后调用设置的mac_ops来更新mac为和phy一致的状态。如当phy状态为link_down是调用stmmac_mac_link_down。设置一个定时器,定时器处理函数中调度该工作队列,定时器会在网口up时开启定时器来周期性的调度工作队列。
stmmac_phy_setup(priv); //设置创建phylink
phylink_create(&priv->phylink_config, fwnode, mode, &stmmac_phylink_mac_ops); //创建phylink
//pl->resolve 工作队列,用来轮询phy_device状态机更新过来的状态,调用mac_ops更新mac为相应状态
INIT_WORK(&pl->resolve, phylink_resolve);
//phylink 相关的操作函数,phy状态改变后最终由phylink_resolve调用mac_ops中相应状态的回调函数
pl->mac_ops = mac_ops;
static const struct phylink_mac_ops stmmac_phylink_mac_ops = {
.validate = stmmac_validate,
.mac_pcs_get_state = stmmac_mac_pcs_get_state,
.mac_config = stmmac_mac_config,
.mac_an_restart = stmmac_mac_an_restart,
.mac_link_down = stmmac_mac_link_down,
.mac_link_up = stmmac_mac_link_up,
};
//设置一个定时器在phylink_fixed_poll用来不断启动phylink_resolve 任务
timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
void phylink_fixed_poll(struct timer_list *t)
{
mod_timer(t, jiffies + HZ); //重启定时器
phylink_run_resolve(pl);
queue_work(system_power_efficient_wq, &pl->resolve); //调度pl->resolve
}
5.注册netdev
最终调用register_netdev来注册netdev
ret = register_netdev(ndev); //最终注册net device
整体注册流程
//rk3568 网口驱动 phy注册 分析
//vi ./drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
module_platform_driver(rk_gmac_dwmac_driver); //注册网络驱动
static int rk_gmac_probe(struct platform_device *pdev);
//drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
int stmmac_dvr_probe(struct device *device, struct plat_stmmacenet_data *plat_dat, struct stmmac_resources *res);
ndev = alloc_etherdev_mqs(sizeof(struct stmmac_priv), MTL_MAX_TX_QUEUES, MTL_MAX_RX_QUEUES);//分配net_device 结构体
ndev->netdev_ops = &stmmac_netdev_ops; //设置net_device的ops
//drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
stmmac_mdio_register(ndev); //注册mdio bus
new_bus = mdiobus_alloc(); //分配一个mii_bus 结构体,mii_bus主要绑定mac驱动中通过mdio与phy通信的接口
new_bus->read = &stmmac_mdio_read;
new_bus->write = &stmmac_mdio_write; //绑定mac 中实现的与phy通信读写函数,用于mdio回调
//drivers/of/of_mdio.c
//new_bus, mdio, bus都为申请的mii_bus
of_mdiobus_register(new_bus, mdio_node); //注册new_bus即注册mii_bus
mdio->phy_mask = ~0; //屏蔽自动检测,使用设备树中配置的phy节点,不进行逐个地址扫描
//include/linux/phy.h
mdiobus_register(mdio);
//drivers/net/phy/mdio_bus.c
__mdiobus_register(bus, THIS_MODULE);
//设置mii_bus中的device结构体,并注册device
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);
device_register(&bus->dev); //注册设备即mii_bus->device
priv->mii = new_bus;//设置网卡驱动的私有数据结构中的mii_bus为new_bus,mac驱动中操作phy时会传入priv,最终调用priv-->mii->read/write
//include/linux/of_mdio.h
addr = of_mdio_parse_addr(&mdio->dev, child); //解析设备树中的phy地址
//drivers/net/mdio/of_mdio.c
of_mdiobus_register_phy(mdio, child, addr); //创建phy_device
//drivers/net/phy/phy_device.c
get_phy_device(mdio, addr, is_c45); //获取phy id 并创建phy,设置phy状态机回调函数
get_phy_c22_id(bus, addr, &phy_id); //通过mdio读取phy的ID用来和phy_driver匹配
phy_device_create(bus, addr, phy_id, is_c45, &c45_ids); //最终创建phy_device并设置phy状态机
//申请一个phy_device
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
//设置mdio_device为phy_device->mdio_device
mdiodev = &dev->mdio;
//设置mdio_device->device.parent = &mii_bus->device
mdiodev->dev.parent = &bus->dev;
//设置mdio_device的device的总线为mdio_bus_type, mdio_bus总线在drivers/net/phy/mdio_bus.c中注册
mdiodev->dev.bus = &mdio_bus_type;
mdiodev->dev.type = &mdio_bus_phy_type;
//mdiodev->bus = mii_bus,将phy_deviec中的mdio_device的mii_bus和上面申请的mii_bus绑定
mdiodev->bus = bus;
mdiodev->bus_match = phy_bus_match;
mdiodev->addr = addr;
mdiodev->flags = MDIO_DEVICE_FLAG_PHY;
mdiodev->device_free = phy_mdio_device_free;
mdiodev->device_remove = phy_mdio_device_remove;
//phy_device的状态机,用来轮询检测phy的link状态
INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
//注册创建的phy_device
of_mdiobus_phy_device_register(mdio, phy, child, addr);
//drivers/net/phy/phy_device.c
phy_device_register(phydev); //注册phy_device
mdiobus_register_device(&phydev->mdio);//注册phy_device中的mdio_device
//将mdio_device放入mii_bus中的mdio_map中
mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev;
//drivers/base/core.c
err = device_add(&phydev->mdio.dev); //向mdio 总线添加设备
//drivers/base/bus.c
error = bus_add_device(dev); //放入链表
bus_probe_device(dev); //probe 枚举设备找到匹配的 drv dev
//drivers/base/dd.c
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver(struct device_driver *drv, void *_data);
driver_match_device(drv, dev); //是否匹配
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
//调用mdio_device->phy_bus_match
//上面设置的mdiodev->bus_match = phy_bus_match;
mdio->bus_match(dev, drv);//即调用phy_bus_match
phy_bus_match(struct device *dev, struct device_driver *drv)
{
//比较ID来匹配phy_device和phy_driver
(phydrv->phy_id & phydrv->phy_id_mask) ==
(phydev->phy_id & phydrv->phy_id_mask);
}
stmmac_phy_setup(priv); //设置创建phylink
phylink_create(&priv->phylink_config, fwnode, mode, &stmmac_phylink_mac_ops); //创建phylink
//pl->resolve 工作队列,用来轮询phy_device状态机更新过来的状态,调用mac_ops更新mac为相应状态
INIT_WORK(&pl->resolve, phylink_resolve);
//phylink 相关的操作函数,phy状态改变后最终由phylink_resolve调用mac_ops中相应状态的回调函数
pl->mac_ops = mac_ops;
static const struct phylink_mac_ops stmmac_phylink_mac_ops = {
.validate = stmmac_validate,
.mac_pcs_get_state = stmmac_mac_pcs_get_state,
.mac_config = stmmac_mac_config,
.mac_an_restart = stmmac_mac_an_restart,
.mac_link_down = stmmac_mac_link_down,
.mac_link_up = stmmac_mac_link_up,
};
//设置一个定时器在phylink_fixed_poll用来不断启动phylink_resolve 任务
timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
void phylink_fixed_poll(struct timer_list *t)
{
mod_timer(t, jiffies + HZ); //重启定时器
phylink_run_resolve(pl);
queue_work(system_power_efficient_wq, &pl->resolve); //调度pl->resolve
}
ret = register_netdev(ndev); //最终注册net device
网口up操作
当命令行执行ifconfig ethx up时,对应网卡的open函数会被调用。会通过phylink连接phy,设置phy状态通知函数phylink_phy_change用来通知pl->resolve工作队列来更新mac状态。设置好后开启phylink,启动定时器来调度pl->resolve,开启phy_device的状态机。
//打开网口 mac连接phy device
//drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
static int stmmac_open(struct net_device *dev);
ret = stmmac_init_phy(dev);
phylink_of_phy_connect(priv->phylink, node, 0); //通过phylink连接phy
of_phy_find_device(phy_node); //通过phy_node节点获取phy_device
phy_attach_direct(pl->netdev, phy_dev, flags, pl->link_interface); //将netdev和phy_device连接
phydev->attached_dev = dev;
dev->phydev = phydev;
phylink_bringup_phy(pl, phy_dev, pl->link_config.interface); //启动phy
phy->phylink = pl;
//设置phy_device通知函数,在状态机中调用查询phy状态来通知phylink_resolve调用mac_ops中相应状态的回调函数
phy->phy_link_change = phylink_phy_change;
phylink_start(priv->phylink); //开启phylink
mod_timer(&pl->link_poll, jiffies + HZ);//定时器处理函数中调度pl->resolve,轮询phy_device更新过来的状态,更新mac为相应状态
phy_start(pl->phydev);
phy_start_machine(phydev); //开启状态机
phy_trigger_machine(phydev);
phy_queue_state_machine(phydev, 0);
mod_delayed_work(system_power_efficient_wq, &phydev->state_queue, jiffies);
void phy_state_machine(struct work_struct *work) //phy_device状态机函数
{
phy_check_link_status(phydev);
phy_link_up(phydev);/phy_link_down(phydev);
phydev->phy_link_change(phydev, true/false); //状态机中调用phy_device通知函数
phylink_run_resolve; //调度pl->resolve 更新mac为相应状态
phy_queue_state_machine(phydev, PHY_STATE_TIME); //重新调度phy_device状态轮询检测工作队列,周期1秒
}
网口的数据收发流程后续有时间再进行更新。