在Linux内核启动日志中可以看到PHY使用的驱动为SMSC LAN8720:
在开发板的设备树描述文件中,两个以太网口的描述如下:
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1 &pinctrl_fec1_reset>;
phy-mode = "rmii";
phy-handle = <ðphy0>;
phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;
phy-reset-duration = <200>;
status = "okay";
};
&fec2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet2 &pinctrl_fec2_reset>;
phy-mode = "rmii";
phy-handle = <ðphy1>;
phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
phy-reset-duration = <200>;
status = "okay";
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethphy0: ethernet-phy@0 {
compatible = "ethernet-phy-ieee802.3-c22";
smsc,disable-energy-detect;
reg = <0>;
};
ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
smsc,disable-energy-detect;
reg = <1>;
};
};
};
以太网外设节点名称为fec1和fec2,并且这是补充描述,所以在imx6ull芯片级的描述文件找到描述:
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1 &pinctrl_fec1_reset>;
phy-mode = "rmii";
phy-handle = <ðphy0>;
phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;
phy-reset-duration = <200>;
status = "okay";
};
&fec2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet2 &pinctrl_fec2_reset>;
phy-mode = "rmii";
phy-handle = <ðphy1>;
phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
phy-reset-duration = <200>;
status = "okay";
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethphy0: ethernet-phy@0 {
compatible = "ethernet-phy-ieee802.3-c22";
smsc,disable-energy-detect;
reg = <0>;
};
ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
smsc,disable-energy-detect;
reg = <1>;
};
};
};
找到兼容性为"fsl,imx6ul-fec",根据此兼容性可以找到驱动源码为drivers/net/ethernet/freescale/fec_main.c
。
绑定文档为:Documentation/devicetree/bindings/net/fsl-fec.txt
,在绑定文档中给出FEC外设所必须的描述:
其中phy-mode节点的值如下:
一些可选属性如下(节选):
mdio用于和外接的PHY芯片通信,和实际开发板上外接的PHY芯片有关,兼容性是"ethernet-phy-ieee802.3-c22",根据该兼容性找到对应的绑定文档为Documentation/devicetree/bindings/net/phy.txt
。
其中给出必须的属性如下:
where a is the interrupt number and b is a field that represents an encoding of the sense and level information for the interrupt. This should be encoded based on the information in section 2) depending on the type of interrupt controller you have.兼容性是可选属性,指定PHY芯片内部寄存器的信息。
在原子的教程中写道,在开启两个网口的情况下,将GPIO1_IO07和GPIO_1O06设置为ENET1的MDC和MDIO会导致网络工作不正常,所以设置在ENET2上。
对于imx6ull而言,网络驱动主要分为两部分:imx6ull网络外设驱动以及PHY芯片驱动。
其中imx6ull网络外设驱动是NXP原厂写好的,PHY芯片驱动是通用驱动文件。
根据设备树描述找到兼容性为"fsl,imx6ul-fec",根据此兼容性可以找到驱动源码为drivers/net/ethernet/freescale/fec_main.c
。
(1)申请net_device
(2)处理fec私有数据
(3)从设备树获取phy-handle属性的值、获取phy-mode
(4)获取时钟并使能时钟
(5)复位PHY
phy复位函数如下:
(6)初始化enet
在该函数中会分配队列、申请DMA、设置MAC地址。初始化net_device的netdev_ops和ethtool_ops成员:
之后,还设置poll函数来支持NAPI机制:
(7)MII/RMII接口的初始化
(8)申请中断
(9)注册网卡到系统
其中最重要的是 fec_enet_start_xmit 函数实现,用于启动发送数据包:
在该发送函数中,上层的数据通过skb传入,首先对skb进行判断,是否为GSO数据,如果是则使用 fec_enet_txq_submit_tso 发送,如果不是则使用 fec_enet_txq_submit_skb 发送。
网络中断的服务函数如下,可以看到中断服务函数中最重要的是进行napi的调度:
启动napi调度后,就会用初始化的时候注册的napi轮询函数去接收数据:
PHY子系统也是一个设备、总线和驱动模型,分为PHY设备、PHY驱动以及MDIO总线。
Linux内核使用phy_device结构体来表示PHY设备,定义在文件include/linux/phy.h
中。
一个PHY设备对应一个phy_device实例,使用下面的API完成PHY设备的注册:
int phy_device_register(struct phy_device *phy);
Linux内核使用phy_driver结构体表示PHY驱动,定义在文件include/linux/phy.h
中。
这个结构体比较大,编写PHY驱动的主要工作就是实现这些函数。
PHY子系统中,因为PHY设备是通过MDIO接口来管理的,总线模型就是MDIO总线,负责匹配PHY设备和PHY驱动。
在文件drivers/net/phy/mdio_bus.c
中定义了mdio_bys_type结构体来表示MDIO总线:
总线的匹配函数是mdio_bus_match,该函数实现如下:
可以看到,如果PHY设备与PHY驱动匹配,则使用指定的PHY驱动,如果不匹配则使用Linux内核自带的通用PHY驱动。
通用PHY驱动名字为“Generic PHY”,在drivers/net/phy/phy_device.c
文件中实现。
phy_init是整个PHY子系统的入口函数,其中向内核注册一个通用PHY驱动:genphy_driver,该数组定义如下:
其中提供了两个通用的PHY驱动,一个是针对10/100/1000M网络,另一个是针对10G网络的。
SMSC针对自家的PHY芯片也编写了对应的驱动文件,其中包含了LAN8720A这个PHY芯片,实现在drivers/net/phy/smsc.c
文件中。