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秒
                                }

网口的数据收发流程后续有时间再进行更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值