Linux 网络驱动分析

一、网卡基础知识

网口扫盲一:网卡初步认识
网口扫盲二:Mac与Phy组成原理的简单分析
MAC和PHY接口介绍

在这里插入图片描述

由上图可知,MAC与PHY 数据通信分为两部分:

  • 1.MDIO 总线: 用于Mac读写Phy寄存器,获取PHY状态信息以及对PHY配置
  • 2.MII 总线接口: 用于MAC与PHY的数据交互(网络数据)
  • 3.linux驱动中,将MAC和MII总线看着一个整体,去绑定phy

二、Mdio总线

1.mdio bus 初始化

代码路径:drivers/net/phy/phy_device.c
在phy_device.c的phy_init函数中,会首先对mdio bus进行相关初始化,调用流程关系如下:

. drivers/net/phy/phy_device.c 
	└── subsys_initcall(phy_init);	 # 早期初始化调用 
		└── mdio_bus_init(); 		# drivers/net/phy/mdio_bus.c
			├── class_register(&mdio_bus_class);
			└── bus_register(&mdio_bus_type);	# 完成对mdio bus 注册

2.mdio 匹配规则

mdio_bus_type->match
mdio_bus_dev->bus_match
phy_bus_match
dev.phy_id 与drv.phy_id进行匹配
/**
 * mido bus 数据类型
 */
struct bus_type mdio_bus_type = {
	.name		= "mdio_bus",
	.match		= mdio_bus_match,
	.pm		= MDIO_BUS_PM_OPS,
};

/**
 * mdio_bus_match
 * @dev: target MDIO device
 * @drv: given MDIO driver
 */
static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{
	struct mdio_device *mdio = to_mdio_device(dev);

	if (of_driver_match_device(dev, drv)) /* 优先使用设备树名称匹配方法 */
		return 1;

	if (mdio->bus_match)				/* 使用mido device 的匹配规则 */
		return mdio->bus_match(dev, drv);/* 实际上 mido 匹配都会走此线路 */

	return 0;
}

/** 在drivers/net/phy/phy_device.c phy_device_create 函数中
	创建一个phydevice 时会将mido device的bus_match 指向phy_bus_match函数 
	即使用此函数规则来匹配mdiodevice 与mido driver*/
static int phy_bus_match(struct device *dev, struct device_driver *drv)
{
	struct phy_device *phydev = to_phy_device(dev);
	struct phy_driver *phydrv = to_phy_driver(drv);
	const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids);
	int i;

	if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY)) /* 判断是否是 phy driver */
		return 0;

	if (phydrv->match_phy_device) /* 如果phy driver 有自己的匹配规则,则优先使用 */
		return phydrv->match_phy_device(phydev);

	if (phydev->is_c45) {
		for (i = 1; i < num_ids; i++) { /* phydev 存在多个id 逐个匹配*/
			if (!(phydev->c45_ids.devices_in_package & (1 << i)))
				continue;

			if ((phydrv->phy_id & phydrv->phy_id_mask) ==
			    (phydev->c45_ids.device_ids[i] &
			     phydrv->phy_id_mask))
				return 1;
		}
		return 0;
	} else { /* 判断dev id 与drv id */
		return (phydrv->phy_id & phydrv->phy_id_mask) ==
			(phydev->phy_id & phydrv->phy_id_mask);
	}
}

三、平台mac 初始化

mdio
mdio
struct net_device
mac
mii_bus
phy
struct phy_device
struct phy_device

网络驱动涉及到很多数据结构,这里简短描述一下他们的关系:

  • mac对应的驱动设备为 net_device 这里等同与一个网卡。
  • mii_bus : 实质上是一个device,可以将它看成Mac的附属数据结构,在mac驱动中实现read/write.用于mac与phy通过mdio通信。
    在mii_bus中,也记录着当前mdio总线上,扫描的所有phy dev与phy dev对应irq。
  • mido:这里指mdio总线,phy drv与phy dev都挂接到此。
  • phy dev: 1.通过解析设备树来生成phy dev 2.不开启设备树,采用扫描mido总线来获取phy dev。并指向mii_bus 用于回调mido read/write
  • phy drv: phy驱动由phy厂商提供,最终注册到mdio总线,与phy dev进行匹配。
  • net_device 注册时,会回调of_phy_connect函数,完成Mac 与phy dev的连接。

平台Mac控制器 驱动代码会注册到plat总线上,当设备树

  • mac驱动代码标准存放地址:drivers/net/ethernet/xxx xxx为芯片厂商平台
  • 现在有很多平台mac代码实现路径为: drivers/net/ethernet/stmicro/stmmac/dwmac-xxx.c xxx为芯片厂商平台
    个人猜想可能:1.这些芯片平台内部Mac 使用的是stm提供的。 2.stm封装了一层Mac注册框架,比较好用。

1.mac 平台相关初始化

这部分功能由芯片原厂实现,主要工作是:

  • 1.初始化MAC控制器
  • 2.申请mii_bus 挂接mido读写函数
  • 3.调用of_mdiobus_register扫描并注册phy

2.mii 注册分析

  • 1.需设置该mii_bus的名称与id,而在系统中可根据该id值搜索一个mii_bus;
  • 2.完成read、write、reset方法,其中read、write主要用于与该mii_bus下的设备进行命令的交互;而reset方法主要用于对mii_bus的reset。
  • 3.phy_mask主要用于设置需要忽略的phy addr,如我们需要忽略对phy addr 0的查找,则将phy_mask设置为0x01即可。
  • 4.而irq主要指向一个数组,该数组中存储了每一个phy addr对应的irq,主要用于为每一个 phy addr对应的中断,该中断主要用于link up/down,针对该部分内容主要涉及到phy state machine,大部分的mii_bus一般不提供该irq,但phy state machine提供了phy_poll机制,即是没有该irq,也可以进行phy link up/down,类似于mmc子模块中mmc card的poll机制)

of_mdiobus_register(hemac->mii_bus, mii_np); /* 解析设备树,注册mii总线:*/

代码路径: drivers/of/of_mdio.c

struct mii_bus {
	struct module *owner;
	const char *name;
	char id[MII_BUS_ID_SIZE];
	void *priv;
    /* mido 读写寄存器函数 在MAC平台驱动中实现 */
	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 *parent;
	enum {
		MDIOBUS_ALLOCATED = 1,
		MDIOBUS_REGISTERED,
		MDIOBUS_UNREGISTERED,
		MDIOBUS_RELEASED,
	} state;
	struct device dev; /* 可以看出 mii_bus结构体 是设备,并非总线 */
	struct mdio_device *mdio_map[PHY_MAX_ADDR]; /* 当前mac下挂接的phy设备列表 */

	/* PHY addresses to be ignored when probing */
	u32 phy_mask;
	u32 phy_ignore_ta_mask;
	int irq[PHY_MAX_ADDR];
};

/* 看着注释可知,这里的mii 在代码中命名为mido 不要和配置寄存器的mdio总线搞混!!!
 * of_mdiobus_register - Register mii_bus and create PHYs from the device tree
 * @mdio: pointer to mii_bus structure
 * @np: pointer to device_node of MDIO bus.
 */
int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
{
    ....
	mdio->phy_mask = ~0; /* 注意,这里全部取反,不通过扫描的方式查找phy ,采用设备树匹配的方式 */
	mdio->dev.of_node = np;
    
	rc = mdiobus_register(mdio); // 注册一个新的mii_bus device 并扫描phy设备
	.....
	return 0;
}

3.phy扫描

mdiobus_register通过宏会替换成__mdiobus_register函数,在此函数中,主要功能如下:

  • 1.判断mii 下有无mdio读写函数
  • 2.初始化mii dev结构体、设置名字并注册
  • 3.调用mdiobus_scan函数,通过mido扫描所有phy,并注册到mdiobus中(list扫描方式)
  • 4.添加mii bus已注册标识
/* phy.h: #define mdiobus_register(bus) __mdiobus_register(bus, THIS_MODULE)   drivers/net/phy/mdio_bus.c*/
/* 完成 phy与mii bus的绑定,并启动phy */
int __mdiobus_register(struct mii_bus *bus, struct module *owner)
{
	struct mdio_device *mdiodev;
	int i, err;

    /* 必须要有读写函数,在mac里面实现 */
	if (NULL == bus || NULL == bus->name ||
	    NULL == bus->read || NULL == bus->write) 
		return -EINVAL;

    /* dev 相关初始化 */
	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);

	err = device_register(&bus->dev); /* 注册dev到 bus 总线,疑问: dev的bus_type 是什么类型? */
	.....
        
	if (bus->reset) /* 复位 PHY */
		bus->reset(bus);

	for (i = 0; i < PHY_MAX_ADDR; i++) { /* */
		if ((bus->phy_mask & (1 << i)) == 0) { /* phy_mask 已经取反为1 不进入mdiobus_scan 函数 */
			struct phy_device *phydev = mdiobus_scan(bus, i);
		}
	}

	bus->state = MDIOBUS_REGISTERED; /* 添加已注册标识 */
	.....
}

四、phy dev 注册

1.list扫描方式注册

/* 注册phydev */

struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr)
{
	struct phy_device *phydev;
	phydev = get_phy_device(bus, addr, false); 		/* 通过扫描当前mac下的mdio总线,生成对应的phy device 设备 */
	of_mdiobus_link_mdiodev(bus, &phydev->mdio);	/* 判断设备树中子节点 addr是否正确,并将对应的of节点挂载到 dev下*/
	int err = phy_device_register(phydev);			/* 注册phydev->mdiodev 到 mido bus */
    ...
}

int phy_device_register(struct phy_device *phydev)
{
	int err;
	err = mdiobus_register_device(&phydev->mdio); /* 存放到mii_bus 的mdio_map中; mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev; */
	err = phy_scan_fixups(phydev);
	phydev->mdio.dev.groups = phy_dev_groups;
	err = device_add(&phydev->mdio.dev); /* 添加设备到 mdio_bus中 */
	....
}
/* drivers/net/phy/phy_device.c */

struct phy_device {
	struct mdio_device mdio;

	/* Information about the PHY type */
	/* And management functions */
	struct phy_driver *drv;
    ...
};

struct mdio_device {
	struct device dev;
	const struct dev_pm_ops *pm_ops;
	struct mii_bus *bus; /* 指向mii_bus 用于回调mido read/write */ 

	int (*bus_match)(struct device *dev, struct device_driver *drv); /* phy dev与drv匹配函数 */
	void (*device_free)(struct mdio_device *mdiodev);
	void (*device_remove)(struct mdio_device *mdiodev);
    
	/* Bus address of the MDIO device (0-31) */
	int addr;
	int flags;
};




/* 通过mido 总线读写mii标准寄存器来扫描phy */
struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45)
{
	struct phy_c45_device_ids c45_ids = {0};
	u32 phy_id = 0;
	int r;

    /* 读取MII标准寄存器: 失败: 该地址phy不存在   成功: 获取当前PHY状态 */
	r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids); 
	if (r)
		return ERR_PTR(r);

	/* If the phy_id is mostly Fs, there is no device there */
	if ((phy_id & 0x1fffffff) == 0x1fffffff)
		return ERR_PTR(-ENODEV);

    /* 通过 bus addr 等信息 创建一个phy device */
	return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);
}


/* 扫描到phy 后,创建一个phydev,并对其初始化 */
/* phy_device 结构体中,包含一个 mdio_device:
在mdio_device中,
*/
struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id,
				     bool is_c45,
				     struct phy_c45_device_ids *c45_ids)
{
	struct phy_device *dev;
	struct mdio_device *mdiodev;

	/* We allocate the device, and initialize the default values */
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);

    /* 初始化  mido dev */
	mdiodev = &dev->mdio;
	mdiodev->dev.release = phy_device_release;
	mdiodev->dev.parent = &bus->dev;
	mdiodev->dev.bus = &mdio_bus_type; /* mido bus */
	mdiodev->bus = bus;		/* 指向mii_bus  用于回调mido的read/write 函数 */
	mdiodev->pm_ops = MDIO_BUS_PHY_PM_OPS; 
	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;

	dev->speed = 0;
	dev->duplex = -1;
	dev->pause = 0;
	dev->asym_pause = 0;
	dev->link = 1;
	dev->interface = PHY_INTERFACE_MODE_GMII;

	dev->autoneg = AUTONEG_ENABLE; /* 自协商 */
	dev->is_c45 = is_c45;
	dev->phy_id = phy_id;
	if (c45_ids) dev->c45_ids = *c45_ids; /* 匹配表 */
	dev->irq = bus->irq[addr];
	dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr); /* 设置phy名称 */
	dev->state = PHY_DOWN; /* 设置phy 状态 */
    ....
	return dev;
}

2.设备树匹配方式注册

在mac设备树节点中,有mdio-bus子节点。此节点中可以定义多个phy节点,来描述对应的phy device。
phy设备树节点格式如下:

phy-handle = <&phy0>; /* 指定mac 要连接的phy dev节点 */
mdio-bus {
    phy0: ethernet-phy@0 {		/* phy节点名称 */
        compatible = "ethernet-phy-id001c.c943";	/* 匹配id */
        reg = <0>;				/* mido addr */
        fixed-link = <0 1 100 0 0>;
        phy-mode = "rmii";		/* rmii 模式 */
        qca,ar8327-initvals = <	/* 自定义 phy driver 数据,最终通过dev.of_node 给到phy driver */
                0x04 0x40000000 
                0x624 0x7f7f7f 
                0x10 0x10261320 
                0x7c 0x0000007d 
            >;
    };
};

回过头来看of_mdiobus_register函数,虽然不是走的list 自动扫描mdio 总线下的phy。但是最终还是会调用phy_device_create与phy_device_register 创建并注册phydev到mdio bus。



int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
{
    ...
	/* Loop over the child nodes and register a phy_device for each phy */
	for_each_available_child_of_node(np, child) { /* 循环遍历mdi*/
		addr = of_mdio_parse_addr(&mdio->dev, child); /* 获取phy节点的reg属性的值,判断phy的mdio addr是否合法 */
		...
 
/* 通过Compatible 属性来匹配id,只能是如下格式化字符串:
 * "ethernet-phy-idX.X"  "ethernet-phy-ieee802.3-c45" "ethernet-phy-ieee802.3-c22"  */
		if (of_mdiobus_child_is_phy(child)) /* 通过Compatible 属性id来判断是否是phy设备 */
			of_mdiobus_register_phy(mdio, child, addr); /* 最终 调用phy_device_create与phy_device_register 创建并注册phydev到mdio bus*/
		else /* 非 phy 创建一个mdio dev 并注册到mdio bus中*/
			of_mdiobus_register_device(mdio, child, addr);
	}


     /* 如果存在子节点 reg属性为空,会执行下面代码,通过mdio总线扫描出phy addr */
	if (!scanphys) return 0;
    
	/* auto scan for PHYs with empty reg property */
	for_each_available_child_of_node(np, child) {
		/* Skip PHYs with reg property set */
		if (of_find_property(child, "reg", NULL)) /* 跳过reg 属性不为空的节点 */
			continue;

		for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
			/* skip already registered PHYs */
			if (mdiobus_is_registered_device(mdio, addr)) /* 跳过已注测的addr */
				continue;

			if (of_mdiobus_child_is_phy(child))
				of_mdiobus_register_phy(mdio, child, addr); /* 通过标准寄存器判断是否phy,如果是phy就创建phydev并注册 */
		}
	}

	return 0;
}

3.mac与phy dev匹配

在Mac初始化函数中,最终会调用register_netdev 函数,此函数中会调用register_netdev -> register_netdevice -> netdev->netdev_ops->ndo_init
ndo_init 是Mac平台代码实现并挂接上去的,此函数中会回调of_phy_connect 完成mac 与phy dev的匹配。

struct phy_device *of_phy_connect(struct net_device *dev,
				  struct device_node *phy_np,
				  void (*hndlr)(struct net_device *), u32 flags,
				  phy_interface_t iface)
{
	struct phy_device *phy = of_phy_find_device(phy_np); /* 解析mac设备树的"phy-handle"对应的phy dev*/
	int ret;
	if (!phy) return NULL;
	phy->dev_flags = flags;
	ret = phy_connect_direct(dev, phy, hndlr, iface); /* 连接mac 与phy dev*/
	...
}

int phy_connect_direct(struct net_device *dev, struct phy_device *phydev,
		       void (*handler)(struct net_device *),
		       phy_interface_t interface)
{
	int rc;
	rc = phy_attach_direct(dev, phydev, phydev->dev_flags, interface);
	if (rc) return rc;

	phy_prepare_link(phydev, handler); /* 设置adjust_link回调函数(mac 平台实现) phydev->adjust_link = handler; */
	phy_start_machine(phydev);	/* 启动状态机 */
	if (phydev->irq > 0) 		/* 存在中断,初始化中断 */
		phy_start_interrupts(phydev); /* 如果中断号申请失败,会设置为轮询模式 PHY_POLL*/

	return 0;
}

五、phy drv 注册

phy driver 一般由phy原厂提供,以下代码以phy ip101为蓝本进行分析。
简单的phy驱动要实现的如下:

  • 1.实现一个phy_driver 结构体
  • 2.使用phy_driver_register函数将phy_driver 注册到mido总线

1.phy drv实现

#define IP101G_PHY_ID        0x02430c54
#define IP101G_PHY_ID_MSK    0x0ffffffc
static struct phy_driver __maybe_unused ip101g_phy_drvs[] = {
    {
        .phy_id         = IP101G_PHY_ID,		/* 匹配id */
        .phy_id_mask    = IP101G_PHY_ID_MSK,	/* id 匹配掩码 */
        .name           = "ip101g 10/100 Ethernet Phy", /* 名称 */
        .features       = (PHY_BASIC_FEATURES | SUPPORTED_Pause | SUPPORTED_Asym_Pause), /* 驱动能力 */
        .config_init    = ip101g_config_init,	
        .config_aneg	= genphy_config_aneg,	/* */
        .read_status    = genphy_read_status,	/* 获取phy连接状态 */
    }, 
};
module_phy_driver(ip101g_phy_drvs); /* 最终调用phy_driver_register函数注册到mdio总线 */

2.dev与drv匹配

在phy_bus_match函数中dev.phy_id 与drv.phy_id匹配成功后,总线会调用driver的probe函数;

struct mdio_driver_common {
	struct device_driver driver;
	int flags;
};

struct phy_driver {
	struct mdio_driver_common mdiodrv; /* 此结构体中包含 dev_drv */
	u32 phy_id;
	.....
}

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 */
	new_driver->mdiodrv.driver.probe = phy_probe;	 /* phy probe 探测函数 */
	new_driver->mdiodrv.driver.remove = phy_remove;
	new_driver->mdiodrv.driver.owner = owner;
	retval = driver_register(&new_driver->mdiodrv.driver);
	....
}

从上面的代码可知,匹配成功后会调用phy_probe函数;

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);
	int err = 0;

	phydev->drv = phydrv;

	/* Disable the interrupt if the PHY doesn't support it
	 * but the interrupt is still a valid one
	 */
	if (!(phydrv->flags & PHY_HAS_INTERRUPT) &&
	    phy_interrupt_is_valid(phydev))
		phydev->irq = PHY_POLL;

	if (phydrv->flags & PHY_IS_INTERNAL)
		phydev->is_internal = true;

	mutex_lock(&phydev->lock);

	/* Start out supporting everything. Eventually,
	 * a controller will attach, and may modify one
	 * or both of these values
	 */
	phydev->supported = phydrv->features;
	of_set_phy_supported(phydev);
	phydev->advertising = phydev->supported;

	/* Get the EEE modes we want to prohibit. We will ask
	 * the PHY stop advertising these mode later on
	 */
	of_set_phy_eee_broken(phydev);

	/* Set the state to READY by default */
	phydev->state = PHY_READY;

	if (phydev->drv->probe) /* 如果phy_drv-> probe 存在就调用 */
		err = phydev->drv->probe(phydev);
    ...
}

六、PHY状态机

1.工作队列初始化

在phy_device_create函数中,初始化phy时,会phy的state_queue与phy_queue工作队列,用于时刻检测网络状态。

/* phy_device 结构体中,包含一个 mdio_device:
在mdio_device中,
*/
struct phy_device *phy_device_create(...)
{
	struct phy_device *dev;
    ....
	dev->state = PHY_DOWN;
	INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); /* 触发state_queue 会调用 phy_state_machine 函数*/
	INIT_WORK(&dev->phy_queue, phy_change); /* 用于中断触发 */
	....
}

2.状态机工作流程

  • 1.PHY的状态定义如下:
enum phy_state {
	PHY_DOWN = 0, // PHY芯片和驱动没准备好,一般情况下少发生
	PHY_STARTING, // PHY芯片OK了,但驱动还没有准备好
	PHY_READY,    // 准备好了,在probe中赋值,接下来会切到PHY_UP
	PHY_PENDING,
	PHY_UP,       // phy启动了,可以工作了,接下来会到PHY_AN
	PHY_AN,       // 自动协商
	PHY_RUNNING,  // 正在运行中,在网络连接(插上网线)时会到这个状态
	PHY_NOLINK,   // 断网了
	PHY_FORCING,  // 强制,当自动协商不使能时,就会进行此状态(实际上会读PHY寄存器进行设置速率、双工,等)
	PHY_CHANGELINK, // 变化,这个状态很重要,当连接时,会换到PHY_RUNNING,当断网时,会切到PHY_NOLINK
	PHY_HALTED,
	PHY_RESUMING
};
  • 2.状态机工作原理
/**
 * phy_state_machine - Handle the state machine
 * @work: work_struct that describes the work to be done
 */
void phy_state_machine(struct work_struct *work)
{
	struct delayed_work *dwork = to_delayed_work(work);
	struct phy_device *phydev =
			container_of(dwork, struct phy_device, state_queue);
	bool needs_aneg = false, do_suspend = false;
	enum phy_state old_state;
	int err = 0;
	int old_link;

	mutex_lock(&phydev->lock);

	old_state = phydev->state;

	if (phydev->drv->link_change_notify)
		phydev->drv->link_change_notify(phydev);

	switch (phydev->state) {
	case PHY_DOWN:
	case PHY_STARTING:
	case PHY_READY:
	case PHY_PENDING:
		break;
	case PHY_UP:
		needs_aneg = true;
		phydev->link_timeout = PHY_AN_TIMEOUT; // 超时,自动协商不成功时,则会在超时后强制设置速率等参数
		break;
	case PHY_AN:
		err = phy_read_status(phydev); // 读phy状态,包括link,速率、双工,等等状态信息
		if (err < 0)
			break;

		/* If the link is down, give up on negotiation for now */
		if (!phydev->link) {
			phydev->state = PHY_NOLINK;
			netif_carrier_off(phydev->attached_dev); // 通知内核其它网络模块(phy是最底一层)断网了。
			phydev->adjust_link(phydev->attached_dev); // 调整参数(速率、双工)
			break;

			break;
		}

		/* Check if negotiation is done.  Break if there's an error */
		err = phy_aneg_done(phydev);// 检测是否完成自动协商,打印up信息
		if (err < 0)
			break;

		/* If AN is done, we're running */
		if (err > 0) {
			phydev->state = PHY_RUNNING; // 完成后,变成PHY_RUNNING状态
			netif_carrier_on(phydev->attached_dev); // 发通知,连接OK
			phydev->adjust_link(phydev->attached_dev);// 调整参数(速率、双工) // 打印、调用参数

		} else if (0 == phydev->link_timeout--)
			needs_aneg = true;
		break;
	case PHY_NOLINK:
		if (phy_interrupt_is_valid(phydev))
			break;

		err = phy_read_status(phydev);// 读phy 连接状态
		if (err)
			break;

		if (phydev->link) { // 重连成功
			if (AUTONEG_ENABLE == phydev->autoneg) { // 如果是自动协商使能,就进行自动协商
				err = phy_aneg_done(phydev);		// 检测是否完成自动协商,打印up信息
				if (err < 0)
					break;

				if (!err) {  // 自协商失败,进入自协商状态
					phydev->state = PHY_AN;
					phydev->link_timeout = PHY_AN_TIMEOUT;
					break;
				}
			}
			phydev->state = PHY_RUNNING; // 自协商成功,进入运行状态
			netif_carrier_on(phydev->attached_dev); // 发通知,连接OK
			phydev->adjust_link(phydev->attached_dev); // 调整参数(速率、双工)
		}
		break;
	case PHY_RUNNING:
		/* Only register a CHANGE if we are polling and link changed
		 * since latest checking.
		 */
		if (phydev->irq == PHY_POLL) { 		// 轮询方式 获取状态(无中断)
			old_link = phydev->link;		// 记录上次连接状态
			err = phy_read_status(phydev);	// 读取连接状态
			if (err)
				break;

			if (old_link != phydev->link)  // 状态发生改变
				phydev->state = PHY_CHANGELINK; 
		}

         // 中断触发,连接失败 调整状态
		if (!phydev->link && phydev->state == PHY_RUNNING) {
			phydev->state = PHY_CHANGELINK;
			phydev_err(phydev, "no link in PHY_RUNNING\n");
		}
		break;
	case PHY_CHANGELINK:
		err = phy_read_status(phydev);  // 读取状态
		if (err)
			break;

		if (phydev->link) { // 处于连接状态
			phydev->state = PHY_RUNNING;
			netif_carrier_on(phydev->attached_dev);// 发通知,连接OK
		} else {
			phydev->state = PHY_NOLINK;	// 连接失败
			netif_carrier_off(phydev->attached_dev); // 发通知,断开连接
		}

		phydev->adjust_link(phydev->attached_dev);  // 调整连接状态

		if (phy_interrupt_is_valid(phydev))
			err = phy_config_interrupt(phydev,
						   PHY_INTERRUPT_ENABLED);
		break;
	case PHY_HALTED:
	...
	}

	if (needs_aneg)
		err = phy_start_aneg_priv(phydev, false);
    ...
	if (phydev->irq == PHY_POLL) // 无中断,采用轮询的方式
		queue_delayed_work(system_power_efficient_wq,		// 1秒后再次触发此函数
          		&phydev->state_queue,PHY_STATE_TIME * HZ);	//#define PHY_STATE_TIME		1
}
  • 上电时状态变化:
    PHY_READY -> PHY_UP -> PHY_AN -> PHY_RUNNING

  • 拨出网线时状态变化:
    PHY_RUNNING ->PHY_CHANGELINK -> PHY_NOLINK

  • 插上网线时状态变化:
    PHY_NOLINK -> PHY_RUNNING

  • 自动协商过程:
    phy_aneg_done -> genphy_aneg_done -> phy_read(phydev, MII_BMSR);

    PHY_UP -> phy_start_aneg -> genphy_config_aneg -> genphy_config_advert -> genphy_restart_aneg -> PHY_AN -> PHY_NOLINK(串口打印Down) -> phy_aneg_done -> PHY_RUNNING(串口打印Up)

3.状态机触发

状态机由三处地方触发:

  • 1.phy_start_machine // 开启状态机
  • 2.phy_trigger_machine // 中断触发
  • 3.PHY_POLL // 轮询触发,无中断

1.开启状态机

在phy_connect_direct 函数中 会调用phy_start_machine 函数,触发phy状态机工作,如果是PHY_POLL轮询的方式,会1s 进入一次状态机处理函数。

2.中断触发状态机

在phy_device_create 函数中,初始化中断工作队列,并在phy_connect_direct函数中,申请中断。
中断触发状态机流程:phy_interrupt -> phy_change -> phy_trigger_machine -> phy_state_machine

void phy_change(struct work_struct *work)
{
    ....
	phy_trigger_machine(phydev, true);
	return;
    ....
}

3.轮询触发

在phy_state_machine 状态机函数中,判断irq的值为 PHY_POLL 会delay 1s后触发phy_state_machine状态机函数。

if (phydev->irq == PHY_POLL)  // 轮询触发,无中断
	queue_delayed_work(system_power_efficient_wq, &phydev->state_queue,PHY_STATE_TIME * HZ); // 1s 后调用

mii_bus的irq主要指向一个数组,该数组中存储了每一个phy addr对应的irq,主要用于为每一个 phy addr对应的中断,该中断主要用于link up/down,针对该部分内容主要涉及到phy state machine,大部分的mii_bus一般不提供该irq,但phy state machine提供了phy_poll机制,即是没有该irq,也可以进行phy link up/down,类似于mmc子模块中mmc card的poll机制)。

  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值