一、网卡基础知识
网口扫盲一:网卡初步认识
网口扫盲二: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 匹配规则
/**
* 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 初始化
网络驱动涉及到很多数据结构,这里简短描述一下他们的关系:
- 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机制)。