Linux phy驱动开发总结

文章目录

基础

须知

  1. 所有PHY驱动不能单独编译成模块必须在phy目录下集合,最终集成到libphy.ko;
  2. MODULE_DEVICE_TABLE用于声明支持的设备;
  3. module_phy_driver用于声明PHY驱动信息添加到libphy.ko,做;
  4. PHY驱动的所有接口实际都为可选,包括phy匹配mdio总线的实现:match_phy_device;
  5. 如果没有实际match_phy_device,最终设备与驱动匹配还是使用phy_id & phy_id_mask方式匹配,Uboot下策略相似;
  6. MAC主动调用phy_connect连接目标PHY,使用系统设备名(如:dwc_phy-1:00)匹配PHY,Uboot下使用addr相似;
  7. MAC 与MDIO是一个整体,所以在MAC驱动中实例化MDIO;
  8. NT使用 MAC(eqos) + Generic PHY,的组合方式,所以phy_register(&genphy_driver)
  9. 软件上抽象Switch为一个特殊的PHY,PHY下多个Port各实例为独立的PHY,所以1819有14个phy实例;
  10. Port19需要配置为CPU口;
  11. MAC 与MDIO是一个整体,所以MDIO的读写在MAC驱动中实现;
  12. MAC遍历PHY时使用PHY驱动的probe实现,最终会调用phy_device_create,创建phy_device实例;
  13. MAC绑定一个MII设备,PHY也会绑定一个MII设备, 最终实现MAC绑定PHY;
  14. mv88e61xx驱动封装了mdio总线,当设备smi_adr==0,发给switch直接传递,非发给switch进行封装;
  15. phy_connect申请phy对象,具体驱动将其实例化;

数据结构

  • 网卡
struct net_device {
	struct device		dev;				// 继承系统设备    
	char			    name[IFNAMSIZ];
	struct phy_device	*phydev;
    ...
};
  • MDIO总线
struct mii_bus {
	struct device dev;													// 继承系统设备
	const char    *name;
	char id[MII_BUS_ID_SIZE];
	void *priv;															// 私有数据,
	struct device *parent;
	struct mdio_device *mdio_map[PHY_MAX_ADDR];
	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);    
	...
};
  • PHY
struct phy_device {
	struct mdio_device mdio;				// 实体
	struct phy_driver *drv;					// 匹配的phy驱动
	u32 phy_id;
	int speed;
	int duplex;
	void *priv;								// 私有数据,一般用于关联eth网卡实例
};

数据结构UML类图

Linux phy驱动开发总结

MAC驱动

初始化简述

static int DWC_ETH_QOS_probe(struct platform_device *pdev)
	nvt_eth_env_probe(pdev);
	DWC_ETH_QOS_mdio_register(dev);
		struct DWC_ETH_QOS_prv_data *pdata = netdev_priv(dev);
		struct mii_bus *new_bus = NULL;
		for (phyaddr = 0; phyaddr < 32; phyaddr++) {								// 遍历mdio上
		    DWC_ETH_QOS_mdio_read_direct(pdata, phyaddr, MII_BMSR, &mii_status);	// <1>, 操作MDIO的寄存器在MDIO总线上直接访问PHY并读写寄存器
		pdata->phyaddr = phyaddr;
		pdata->bus_id = 0x1;
		new_bus = mdiobus_alloc();															// 申请mdio总线对象
		new_bus->name  = "dwc_phy";
		new_bus->read  = DWC_ETH_QOS_mdio_read;
		new_bus->write = DWC_ETH_QOS_mdio_write;
		new_bus->reset = DWC_ETH_QOS_mdio_reset;
		snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x", new_bus->name, pdata->bus_id);		// 总线名:dwc_phy-1
		new_bus->priv = dev;
		new_bus->phy_mask = 0;
		new_bus->parent = &pdata->pdev->dev;
		mdiobus_register(new_bus);															// 注册实例化后的mdio总线
		DWC_ETH_QOS_init_phy(dev);															// 初始化PHY
			phy_connect(dev, phy_id_fmt, &DWC_ETH_QOS_adjust_link, pdata->interface);		// 连接名字为dwc_phy-1:00的PHY设备,成功后使用回调DWC_ETH_QOS_adjust_link
				bus_find_device_by_name(&mdio_bus_type, NULL, bus_id);						// <2>总线mdio_bus_type,下的bus_id=dwc_phy-1:00的设备;
--------------------------------------------------------------------------------------------------------------------------------------------------
<1> drivers/net/ethernet/novatek/na51055/DWC_ETH_QOS_dev.c:4790:    hw_if->read_phy_regs = read_phy_regs;
<2> 关键信息,遍历总线模型下的所有设备,
		device <= bus => drivers
		所以要使用总线,必然有依附于总线的设备与驱动,其中:
		动态注册:
			设备注册:phy_device_register
			驱动注册:phy_driver_register
		静态注册:
			设备注册:MODULE_DEVICE_TABLE(mdio, marvell_tbl);		
			驱动注册:module_phy_driver(marvell_drivers);

驱动实现

X:\source_code\nt9852x\workspace_sourceInsight\kernel\drivers\net\ethernet\novatek\na51055\DWC_ETH_QOS.c

module_platform_driver(DWC_ETH_QOS_driver);

MODULE_DEVICE_TABLE(of, synopsys_eth_of_dt_ids);

static struct platform_driver DWC_ETH_QOS_driver = {
	.probe  = DWC_ETH_QOS_probe,
	.remove = DWC_ETH_QOS_remove,
	.driver = {
			.name = DEV_NAME,
			.owner = THIS_MODULE,
#ifdef CONFIG_OF
			.of_match_table = synopsys_eth_of_dt_ids,				// 匹配表
#endif
	},
};

static const struct of_device_id synopsys_eth_of_dt_ids[] = {
    { .compatible = "nvt,synopsys_eth" },							// DTS文件中节点中兼容名为:"nvt,synopsys_eth"的设备
    {},
};

MODULE_DEVICE_TABLE(of, synopsys_eth_of_dt_ids);


static int DWC_ETH_QOS_probe(struct platform_device *pdev)
	struct DWC_ETH_QOS_prv_data *pdata = NULL;
	struct net_device *dev = NULL;
	dev = alloc_etherdev_mqs(sizeof(struct DWC_ETH_QOS_prv_data), tx_q_count, rx_q_count);			// 申请网络设备实例
	
	SET_NETDEV_DEV(dev, &pdev->dev);								// <1> 平台设备、网卡实例、网络设备,三者的联系
	pdata = netdev_priv(dev);										// 从网络设备内存中得到私有数据赋值给pdata
	platform_set_drvdata(pdev, dev);								// **设置网络设备为平台设备私有数据
	pdata->pdev = pdev;
	pdata->dev  = dev;
	
	dev->dev_addr[0] = dev_addr[0];									// MAC地址初始化
	dev->dev_addr[1] = dev_addr[1];
	dev->dev_addr[2] = dev_addr[2];
	dev->dev_addr[3] = dev_addr[3];
	dev->dev_addr[4] = dev_addr[4];
	dev->dev_addr[5] = dev_addr[5];
	dev->irq = platform_get_irq(pdev, 0);							// 中断
	dev->netdev_ops = DWC_ETH_QOS_get_netdev_ops();					// 操作集
	pdata->interface = DWC_ETH_QOS_get_phy_interface(pdata);		// 实例的接口
	if (1 == pdata->hw_feat.sma_sel) {
		DWC_ETH_QOS_mdio_register(dev);	
			struct DWC_ETH_QOS_prv_data *pdata = netdev_priv(dev);
			struct mii_bus *new_bus = NULL;
			for (phyaddr = 0; phyaddr < 32; phyaddr++) {			// 
				DWC_ETH_QOS_mdio_read_direct(pdata, phyaddr, MII_BMSR, &mii_status);				// 通用PHY状态寄存器,用于确认PHY是否连接
				if (phy_reg_read_status == 0) {
					if (mii_status != 0x0000 && mii_status != 0xffff) {
						printk(KERN_ALERT "%s: Phy detected at ID/ADDR %d\n", DEV_NAME, phyaddr);
						phy_detected = 1;
						break;																		// 第一个响应的PHY设备既是需要连接的目标设备
			pdata->phyaddr = phyaddr;
			pdata->bus_id  = 0x1;
			DBGPHY_REGS(pdata);										// dump PHY寄存器
			new_bus = mdiobus_alloc();								// 申请mdio总线
				mdiobus_alloc_size(0)
			new_bus->name     = "dwc_phy";							// 总线实例化
			new_bus->read     = DWC_ETH_QOS_mdio_read;
			new_bus->write    = DWC_ETH_QOS_mdio_write;
			new_bus->reset    = DWC_ETH_QOS_mdio_reset;
			snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x", new_bus->name, pdata->bus_id);			// bus->id, "dwc_phy-1",后续总线设备的创建、查找都会使用到,不应修改
			new_bus->priv     = dev;
			new_bus->phy_mask = 0;
			new_bus->parent   = &pdata->pdev->dev;					// <2> 平台设备、mdio总线,两者联系
			mdiobus_register(new_bus);								// mdio总线注册
				bus = new_bus;
				__mdiobus_register(bus, THIS_MODULE)
					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);						// <20> 注册mdio对应的系统设备,名称为:“dwc_phy-1”
					devm_gpiod_get_optional(&bus->dev, "reset", GPIOD_OUT_LOW);						// devm与bus->reset复位PHY
					if (bus->reset)
						bus->reset(bus);
					for (i = 0; i < PHY_MAX_ADDR; i++) {											// 遍历mdio上所有PHY设备
						struct phy_device *		`;
						phydev = mdiobus_scan(bus, i);	
							addr = i;
							phydev = get_phy_device(bus, addr, false);								// 实例PHY设备
								get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);					
									phy_reg = mdiobus_read(bus, addr, MII_PHYSID1);					// 通过mdio总线读取PHY寄存器,phy_id = ((MII_PHYSID1 << 16) | MII_PHYSID2);
									*phy_id = (phy_reg & 0xffff) << 16;
									phy_reg = mdiobus_read(bus, addr, MII_PHYSID2);
									*phy_id |= (phy_reg & 0xffff);
									if ((phy_id & 0x1fffffff) == 0x1fffffff)						// <8>全ff即无PHY
										return ERR_PTR(-ENODEV);
								return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);		// 使用phy_id,创建phy设备
									struct phy_device *dev;
									struct mdio_device *mdiodev;
									dev = kzalloc(sizeof(*dev), GFP_KERNEL);
									mdiodev            = &dev->mdio;
									mdiodev->bus       = bus;										// 将该mdio设备关联mdio总线,
									mdiodev->bus_match = phy_bus_match;								// **<15>匹配方式使用默认的phy_bus_match,使用phy_id匹配
									mdiodev->addr      = addr;
									dev->phy_id        = phy_id;
									dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr);			// <9>系统设备名,与<4><10>需要连接目标设备一致
									request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id));		// **<19> 尝试使用phy_id匹配内核模块,匹配成功调用modprobe,是否与MODULE_DEVICE_TABLE有关??早于<14>
									device_initialize(&mdiodev->dev);
							of_mdiobus_link_mdiodev(bus, &phydev->mdio);							// <11> 匹配DTS中的设备
								for_each_available_child_of_node(bus->dev.of_node, child) {			// <16>DTS下mdio节点下查找
									addr = of_mdio_parse_addr(dev, child);
										of_property_read_u32(np, "reg", &addr);						// <3>从DTS中查找,reg值即是phy地址
									if (addr == mdiodev->addr) {									// 如果phy地址与dtb中定义的一致,返回系统设备
										dev->of_node = child;
										dev->fwnode = of_fwnode_handle(child);
										return;
							phy_device_register(phydev);											// <13>将PHY设备
								mdiobus_register_device(&phydev->mdio);								// <14>将该PHY抽象为mdio总线设备,并注册到总线上,
									mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev;				// 实际上在mdio设备映射表中增加一个设备, mdiodev 直接关联的mdio_bus
								phy_device_reset(phydev, 0);
								phy_scan_fixups(phydev);
								device_add(&phydev->mdio.dev);										// 增加MDIO总线设备添加到系统设备, 增加到系统后,会触发phy_driver PHY驱动 probe
					mdiobus_setup_mdiodev_from_board_info(bus, mdiobus_create_device);				// <12> 匹配板级信息中的设备,回调使用mdiobus_create_device
						list_for_each_entry_safe(be, tmp, &mdio_board_list, list) {
							if (strcmp(bus->id, bi->bus_id))										// <17> 按名字匹配目标板级设备
								continue;
							bi = &be->board_info;
								cb(bus, bi);														// 回调mdiobus_create_device
									mdiodev                = kzalloc(sizeof(*mdiodev), GFP_KERNEL);
									mdiodev->dev.release   = mdio_device_release;
									mdiodev->dev.parent    = &bus->dev;
									mdiodev->dev.bus       = &mdio_bus_type;
									mdiodev->device_free   = mdio_device_free;
									mdiodev->device_remove = mdio_device_remove;
									mdiodev->bus           = bus;
									mdiodev->addr          = addr;
									dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr);
									device_initialize(&mdiodev->dev);
			DWC_ETH_QOS_init_phy(dev);
				snprintf(bus_id, MII_BUS_ID_SIZE, "dwc_phy-%x", pdata->bus_id);
				snprintf(phy_id_fmt, MII_BUS_ID_SIZE + 3, PHY_ID_FMT, bus_id, pdata->phyaddr);
				phydev = phy_connect(dev, phy_id_fmt, &DWC_ETH_QOS_adjust_link, pdata->interface);	// <4> 接连PHY并实例化,设备名为:phy_id_fmt=“dwc_phy-1:00”,回调函数为:DWC_ETH_QOS_adjust_link
					struct phy_device *phydev;
					bus_id = phy_id_fmt;
					d = bus_find_device_by_name(&mdio_bus_type, NULL, bus_id);						// <10> 按名字匹配目标系统设备
					phydev = to_phy_device(d);														// <5> 系统设备与PHY设备的转换,中间使用mdio_device过渡
					rc = phy_connect_direct(dev, phydev, handler, interface);						// <6> 连接网络设备与PHY
						rc = phy_attach_direct(dev, phydev, phydev->dev_flags, interface);
								struct mii_bus *bus = phydev->mdio.bus;
								struct device *d = &phydev->mdio.dev;
								if (ndev_owner != bus->owner && !try_module_get(bus->owner)) {		// <18> 网络设备有自己的MDIO总线,
									dev_err(&dev->dev, "failed to get the bus module\n");
									return -EIO;
								}
								if (!d->driver) {													// <20> 如果没有驱动,使用通用PHY驱动genphy_driver
									d->driver = &genphy_driver.mdiodrv.driver;
									using_genphy = true;
								if (using_genphy) {
									d->driver->probe(d);
								phydev->phy_link_change = phy_link_change;
								phydev->attached_dev = dev;
								dev->phydev = phydev;
								sysfs_create_link(&phydev->mdio.dev.kobj, &dev->dev.kobj, "attached_dev");
								phy_init_hw(phydev);
									phy_device_reset(phydev, 0);
									if (phydev->drv->soft_reset)
										ret = phydev->drv->soft_reset(phydev);						// 软件复位
									else
										ret = genphy_soft_reset(phydev);
									phy_scan_fixups(phydev);
									return phydev->drv->config_init(phydev);						// 初始化
								phy_resume(phydev);
								phy_led_triggers_register(phydev);
						phy_prepare_link(phydev, handler);
							phydev->adjust_link = handler;
						phy_start_machine(phydev);							
					put_device(d);																	// device 使用次数释放,与bus_find_device_by_name成对使用;
					return phydev;
					if (phydev->phy_id == 0) {						// <7>如果phy_id为0即无效id,断开PHY的连接
						phy_disconnect(phydev);
						return -ENODEV;
				pdata->phydev = phydev;
	register_netdev(dev);											// 注册实例化后网络设备
	
	
<1> 涉及的数据结构
struct DWC_ETH_QOS_prv_data *pdata;
struct net_device *dev;
struct mii_bus *bus;
struct phy_device *phydev;
struct mdio_device *mdio;
struct phy_device *phy_dev;
			 
<3> DTS中PHY相关信息
#if IS_ENABLED(CONFIG_OF_MDIO)
#error 
   
drivers/net/phy/mdio_bus.c:249:2: error: #error
 #error
  ^~~~~

	phy@f02b3800 {
		compatible = "nvt,eth_phy";
		reg = <0xf02b3800 0x400>;
	};

	
<4> phy_connect
传入回调函数,在创建的延时对列里周期调用,周期为Hz

<7> IP18xx视为PHY获取到id为0:phy_id = ((MII_PHYSID1 << 16) | MII_PHYSID2);
所以这个需要跳过;

<8> IP18xx的接口兼容mdio,但有差异,最终会响应所有命令,
所以返回数据必定都不会全为1,即0xffff;


<12> mdio总线上创建mdio总线设备
static int mdiobus_create_device(struct mii_bus *bus, struct mdio_board_info *bi)
	mdiodev = mdio_device_create(bus, bi->mdio_addr);
		

<13>将PHY设备注册到总线上


<15> phy_bus_match
		struct phy_device *phydev = to_phy_device(dev);		// mdio总线设备与系统设备转换的方法,中转: mdio_device.dev => phy_device.mdio
		struct phy_driver *phydrv = to_phy_driver(drv);		// mdio总线设备与系统设备转换的方法,中断:mdio_driver_common.driver => phy_driver.mdiodrv
        if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY))			// <15.1>如果不是PHY,不需要匹配,直接返回成功
                return 0;
        if (phydrv->match_phy_device)								// 如果驱动实现了另外的匹配规则,优先使用;
                return phydrv->match_phy_device(phydev);					
        if (phydev->is_c45) {
                。。。
        else
            return (phydrv->phy_id & phydrv->phy_id_mask) == (phydev->phy_id & phydrv->phy_id_mask);		// 主流分支,使用phy_id匹配


<15.1>	关于mdiodrv.flags & MDIO_DEVICE_IS_PHY
注册函数中默认目标为PHY
int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
{
	int retval;

	new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY;
		


<16> NT9852x未启动mdio的设备节点 

<17> 按名字匹配目标板级设备,是否与MODULE_DEVICE_TABLE有关??


<18> 网络设备会注册自己的MDIO总线驱动,使用ndev_mod 与 bus_owner 比较可知,如果增加module->refcnt模块引用,后续卸载会引入不必要的麻烦
	/* For Ethernet device drivers that register their own MDIO bus, we
	 * will have bus->owner match ndev_mod, so we do not want to increment
	 * our own module->refcnt here, otherwise we would not be able to
	 * unload later on.
	 */
		
<19> mdio匹配关键
​```c
可知,本部代码为MAC的平台驱动,平台设备从OF即DTB中抽象而来。

/**
 * __request_module - try to load a kernel module
 * @wait: wait (or not) for the operation to complete
 * @fmt: printf style format string for the name of the module
 * @...: arguments as specified in the format string
 *
 * Load a module using the user mode module loader. The function returns
 * zero on success or a negative errno code or positive exit code from
 * "modprobe" on failure. Note that a successful module load does not mean
 * the module did not then unload and exit on an error of its own. Callers
 * must check that the service they requested is now available not blindly
 * invoke it.
 *
 * If module auto-loading support is disabled then this function
 * becomes a no-operation.
 */
int __request_module(bool wait, const char *fmt, ...)

关于MAC连接PHY

yegaoyang@Cpl-AVI-General-65-187:~/source_code/Nt9852x/workspace_sourceInsight$ grep -wrn "phy_connect" kernel/drivers/net/
kernel/drivers/net/phy/phy_device.c:780: * phy_connect - connect an ethernet device to a PHY device
kernel/drivers/net/phy/phy_device.c:794:struct phy_device *phy_connect(struct net_device *dev, const char *bus_id,
kernel/drivers/net/phy/phy_device.c:825:EXPORT_SYMBOL(phy_connect);
Binary file kernel/drivers/net/phy/phy_device.o matches
kernel/drivers/net/ethernet/agere/et131x.c:3253:        phydev = phy_connect(netdev, phydev_name(phydev),
kernel/drivers/net/ethernet/freescale/fec_main.c:1933:          phy_dev = phy_connect(ndev, phy_name, &fec_enet_adjust_link,
kernel/drivers/net/ethernet/altera/altera_tse_main.c:715:               phydev = phy_connect(dev, phy_id_fmt, &altera_tse_adjust_link,
。。。

都是MAC驱动主动调用phy_connect连接PHY,

PHY驱动

动态注册

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;			// 对应总线为mdio_bus_type;
	new_driver->mdiodrv.driver.probe  = phy_probe;				// 驱动的probe
	new_driver->mdiodrv.driver.remove = phy_remove;
	new_driver->mdiodrv.driver.owner  = owner;
	
static int phy_probe(struct device *dev)
	struct phy_device *phydev = to_phy_device(dev);
	struct device_driver *drv = phydev->mdio.dev.driver;
	struct phy_driver *phydrv = to_phy_driver(drv);

	phydev->drv = phydrv;
	

静态注册

引用:对MODULE_DEVICE_TABLE宏的理解

MODULE_DEVICE_TABLE(mdio, realtek_tbl);
。。。
MODULE_DEVICE_TABLE(usb, skel_table)

这个宏有两个参数,第一个参数设备名,第二个参数该设备加入到模块中时对应产生的设备搜索符号,这个宏生成了一个名为__mod_pci_device_table局部变量,这个变量指向第二个参数。

内核构建时,depmod程序会在所有模块中搜索符号__mod_pci_device_table,把数据(设备列表)从模块中抽出,添加到映射件/lib/modules/KERNEL_VERSION/modules.pcimap中,当depmod结束之后,所有的PCI设备连同他们的模块名字都被该文件列出。
当内核告知热插拔系统一个新的PCI设备被发现时,热插拔系统使用modules.pcimap文件来找寻恰当的驱动程序
也就是说MODULE_DEVICE_TABLE 有两个功能:一是:将设备加入到外设队列中,二是告诉程序阅读者该设备是热插拔设备或是说该设备支持热插拔功能。

更多被用在动态加载的驱动模块,当然内嵌的也可以。

通用PHY

X:\source_code\nt9852x\workspace_sourceInsight\kernel\drivers\net\phy\phy_device.c

static struct phy_driver genphy_driver = {
	.phy_id       = 0xffffffff,
	.phy_id_mask  = 0xffffffff,
	.name         = "Generic PHY",
	.soft_reset	  = genphy_no_soft_reset,
	.config_init  = genphy_config_init,
	.features     = PHY_GBIT_FEATURES | SUPPORTED_MII | SUPPORTED_AUI | SUPPORTED_FIBRE | SUPPORTED_BNC,
	.aneg_done    = genphy_aneg_done,
	.suspend      = genphy_suspend,
	.resume       = genphy_resume,
	.set_loopback = genphy_loopback,
};

参考模板

X:\source_code\Nt9852x\workspace_sourceInsight\kernel\drivers\net\phy\marvell.c

module_phy_driver(marvell_drivers);

static struct mdio_device_id __maybe_unused marvell_tbl[] = {
	{ MARVELL_PHY_ID_88E1101, MARVELL_PHY_ID_MASK },
	。。。
	{ }
};

MODULE_DEVICE_TABLE(mdio, marvell_tbl);

IP18xx驱动调试

须知:

1. 问题点:mdiobus_register,mdio总线注册过程会遍历mido总线上的mdio设备并将他们以phy_device设备注册到系统。物理上,mdio总线直接访问IP1819的Reg2、3必定返回0,必然无法匹配到IP1819的驱动;
2. 设计背景:
   1. switch使用phyaddr = 0,其他phyadd使用Port默认地址;
3. 驱动背景:
   1. mdio_bus注册时会遍历物理mido总线上的设备,使用
      	
      ​	
   2. phy_device包含mdio_devic,而mdio_device关联mii_bus,三者绑定即一个确定的phy_device对应了确定的mdio_device与mii_bus;
4. 使用背景:
   1. net_device网络设备与phy_device PHY设备连接使用phy_connect连接(绑定),由1.3知phy_device确定了mdio_deice与mii_bus;
5. 总线与网络设备捆绑,都是单例模式,__mdiobus_register会遍历mdio设备实例为phy_device,注册到mdio_bus上
   struct mii_bus {
   struct mdio_device *mdio_map[PHY_MAX_ADDR];
   如果替换了bus的副本,phy_device只被记录到了副本,无法在单例mdio总线上存在。
6. <19>早于<14>,所以IP18xx probe阶段使用mdiobus_get_phy会失败,返回NULL;

问题

1. DTS相关
   1. DTS中无mdio节点
   2. 内置PHY未走平台设备驱动模型;

2. 业务相关
   1. 网络设备驱动使用平台设备驱动模型,但捆绑了mdio总线与phy设备

3. 硬件相关
   1. IP18xx的CPU接口兼容mdio**但不是真正的mdio接口**;
   2. IP18xx的CPU接口协议区分mdio仅使用了2bit作为switch_addr,其他为reg_addr(mdio协议中:phy_addr,5bit;reg_addr,5bit,共10bit),所以switch可以访问0x20及以上地址的寄存器;
   3. 在一条已经连接了IP18xx switch的mdio总线上不可能再有除第二个IP18xx之外的设备,因为IP18xx会应答符合自己switch id的所有指令;
   4. __mdiobus_register阶段,不修改read、write,connect阶段再修改;
4. 总线与网络设备捆绑,都是单例模式,__mdiobus_register会遍历mdio设备实例为phy_device,注册到mdio_bus上
 struct mii_bus {
	struct mdio_device *mdio_map[PHY_MAX_ADDR];

如果替换为bus的副本,phy_device就会被记录到了副本上而非单例mdio_bus上,后续无法在单例mdio_bus总线上存在。

  1. <19>早于<14>,所以mdiobus_get_phy会失败,返回NULL,mdiobus_scan属于核心流程,不能修改;
    所以ip18xx_reg_read(write)不能使用mdiobus_get_phy获取phy_device,与流程相关,问题无解;
    所以probe阶段不能直接替换单例mdio_bus的read和write实现,否则:
    mdiobus_scan流程,第一个phy_device创建之后,第二个phy_device创建时,应无法得到单例mdio_bus进而失败;
    解决:probe阶段不修改本体mdio_bus的read、write实现,后续的connect阶段中进行。
    1. bus->name probe阶段可以修改,但bus->id不可以,否则会导致
      后续phy_device创建使用了bus->id命令系统设备,后续phy_connect阶段bus_find_device_by_name会无法查找到mdio_device进而无法转化为phy_device,phy_connect失败

方案与分析

1.1.1、在原厂网络设备probe过程中,phy_id为0而即将退出时,物理上,直接访问IP18xx Reg0,检查时是否为IP1819 switch
如果是:

​ 1.1.1.1、更新phy_id并替换read、write实现;

​ 1.1.1.2、后续流程可继续;

1.1.2、禁用DWC_ETH_QOS_init_phy,借用平台总线设备驱动模型,dts中定义switch节点,平台设备驱动probe中新注册mdio总线,封装原mdio总线(dwc_phy),并改写read、write,读取switch ID 匹配则probe成功,使用phy_connect连接网络设备。

​ 1.1.2.1、 难点:

​ 1.1.2.1.1、需要创建mdio总线;

​ 1.1.2.1.2、需要从平台设备总线中查找到网络设备,设备名;
​ 问题:我们并不知道到连接到哪个网络设备中?
​ 解决:可以在dts中说明phanle引用;
​ 1.1.3、仍使用mdio总线设备,在probe过程中创建mdio总线,拷贝原有mdio总线(dwc_phy-1)并新实现read、write;

​ 1.1.3.1、DWC_ETH_QOS_init_phy过程中,phy_connect连接switch将使用

总结:

方案1

​ 优点:修改少,测试快;

​ 缺点:
Switch驱动相关实现耦合进原厂网络驱动,后续项目维护成本提高;

方案2

​ 优点:符合低耦合的驱动开发思想,借用dts + 平台设备驱动模型,保证驱动灵活性;

​ 缺点:实现周期较方案长

方案3:

​ 优点:灵活,对原MAC驱动修改少,符合PHY驱动开发规范

​ 缺点:Switch被抽象为特殊的PHY,驱动实现上需要符合PHy驱动的开发规范,避免与其他PHY驱动干扰;

总结

  1. mdio创建时会遍历总线上连接的物理设备(PHY、Switch);
  2. phy_connect,使用phyaddr(物理上,PHY设备在mdio总线上的地址)转化为mdio总线设备的名字来查找并直接连接目标mido总线设备最终是phy_device到网络设备,因为mdio总线设备包含于phy_device设备中;

待解决问题

1.1、<17> 按名字匹配目标板级设备,是否与MODULE_DEVICE_TABLE有关??
答:MODULE_DEVICE_TABLE告知了系统如何根据匹配信息查找到目标设备并指向对驱动;

测试结论

1、使用phy_id匹配phy驱动的思路受物理寄存器排布原因无法实现,需要dts、板级信息等直接方式定义;

备注

1. 关于接入libphy.ko

说明如下:

# PHYLIB implies MDIO_DEVICE, in that case, we have a bunch of circular
# dependencies that does not make it possible to split mdio-bus objects into a
# dedicated loadable module, so we bundle them all together into libphy.ko
上一篇:springcloud-bus+config实现动态刷新定点通知


下一篇:Linux 查看本机串口方法