目录
一、概念介绍
以太网的接口电路主要有(控制器)+ (物理层接口)MAC + PHY 组成。
以上三者并不一定独立分开,常见三种类型如下:
1、CPU集成 MAC与PHY(不太常见)
2、 CPU集成MAC,PHY采用独立芯片(比较常见--如CPU+ Marvel phy 88E1101)
3、CPU不集成MAC与PHY,MAC与PHY采用集成芯片 (比较常见--如CPU + 博通交换片 + phy)
二、 MAC + PHY 组成
如上图所示,MAC与PHY之间的接口类型有多种,常用的MII、RMII、SMII、GMII、RGMII、SGMII等。
1、MII 接口
该接口总共有16根线,支持 10Mb/s 与 100Mb/s 的数据传输速率,数据传输的位宽为 4 位。主要包含四个部分:
1)从MAC层到PHY层的发送数据接口;
2)从PHY层到MAC层的接收数据接口;
3)从PHY层到MAC层的状态指示信号;
4)MAC层和PHY层之间传送控制和状态信息的MDIO接口。
数据接口如下:
信号名称 | 说明 |
TX_ER(Transmit Error) | 发送数据错误提示信号,同步于TX_CLK,高电平有效,表示TX_ER有效期内传输的数据无效。对于10Mbps速率下,TX_ER不起作用 |
TX_EN(Transmit Enable) | 发送使能信号,只有在TX_EN有效期内传的数据才有效 |
TX_CLK | 发送参考时钟,100Mbps速率下,时钟频率为25MHz,10Mbps速率下,时钟频率为2.5MHz。注:TX_CLK时钟的方向是从PHY侧指向MAC侧的,因此时钟是由PHY提供的。 |
TXD(Transmit Data)[3:0] | 数据发送信号,共4根信号线 |
RX_ER(Receive Error) | 接收数据错误提示信号,同步于RX_CLK,高电平有效,表示RX_ER有效期内传输的数据无效。对于10Mbps速率下,RX_ER不起作用 |
RX_DV(Reveive Data Valid) | 接收数据有效信号,作用类型于发送通道的TX_EN |
RXD(Receive Data)[3:0] | 数据接收信号,共4根信号线 |
RX_CLK | 接收数据参考时钟,100Mbps速率下,时钟频率为25MHz,10Mbps速率下,时钟频率为2.5MHz。RX_CLK也是由PHY侧提供的。 |
CRS | Carrier Sense,载波侦测信号,不需要同步于参考时钟,只要有数据传输,CRS就有效,另外,CRS只有PHY在半双工模式下有效 |
COL | Collision Detectd,冲突检测信号,不需要同步于参考时钟,只有PHY在半双工模式下有效 |
管理接口如下:
信号名称 | 说明 |
MDC | 作为MDIO的参考时钟 |
MDIO | 传输控制 |
通过管理接口,MAC 就能监视和控制 PHY。使用 SMI 总线通过读写 PHY 的寄存器来完成的 |
2、RMII 接口
信号名称 | 说明 |
TX_EN(Transmit Enable) | 发送使能信号,只有在TX_EN有效期内传的数据才有效 |
CLKREF | 由外部时钟源提供的50MHz参考时钟 |
TXD(Transmit Data)[1:0] | 数据发送信号,共2根信号线 |
RX_ER(Receive Error) | 接收数据错误提示信号,同步于RX_CLK,高电平有效,表示RX_ER有效期内传输的数据无效。对于10Mbps速率下,RX_ER不起作用 |
RXD(Receive Data)[1:0] | 数据接收信号,共2根信号线 |
CRSDV | 该信号是由MII接口中的RX_DV和CRS两个信号合并而成 |
RMII 接口是用2根线来传输数据的,MII 接口是用 4 根线来传输数据的(10/100M),GMII 接口是用 8 根线来传输数据的(1G)。
3、SMII 接口
信号名称 | 说明 |
TXD | 发送数据信号,位宽为1 |
RXD | 接收数据信号,位宽为1 |
SYNC | 收发数据同步信号,每10个时钟周期置1次高电平,指示同步 |
CLK_REF | 所有端口共用的一个参考时钟,频率为125MHz |
4、GMII 接口
发送参考时钟GTX_CLK和接收参考时钟RX_CLK的频率均为125MHz(1000Mbps/8=125MHz) ,GMII接口中的GTX_CLK是由MAC芯片提供给PHY芯片的。
5、SGMII 接口
时钟频率625MHz,在时钟信号的上升沿和下降沿均采样,参考时钟RX_CLK由PHY芯片提供,是可选的,主要用于MAC侧没有时钟的情况,一般情况下,RX_CLK不使用,收发都可以从数据中恢复出时钟。
6、RGMII 接口
时钟频率为125MHz,TX/RX数据宽度从8为变为4位,为了保持1000Mbps的传输速率不变,RGMII接口在时钟的上升沿和下降沿都采样数据。RGMI同时也兼容100Mbps和10Mbps两种速率,此时参考时钟速率分别为25MHz和2.5MHz。
三、PHY 驱动
int phy_driver_register(struct phy_driver *new_driver);
struct phy_driver {
u32 phy_id;
char *name;
unsigned int phy_id_mask;
u32 features;
u32 flags;
/*
* Called to initialize the PHY,
* including after a reset
*/
int (*config_init)(struct phy_device *phydev);
int (*probe)(struct phy_device *phydev);
/* PHY Power Management */
int (*suspend)(struct phy_device *phydev);
int (*resume)(struct phy_device *phydev);
int (*config_aneg)(struct phy_device *phydev);
/* Determines the negotiated speed and duplex */
int (*read_status)(struct phy_device *phydev);
/* Clears any pending interrupts */
int (*ack_interrupt)(struct phy_device *phydev);
/* Enables or disables interrupts */
int (*config_intr)(struct phy_device *phydev);
int (*did_interrupt)(struct phy_device *phydev);
/* Clears up any memory if needed */
void (*remove)(struct phy_device *phydev);
/* Handles SIOCSHWTSTAMP ioctl for hardware time stamping. */
int (*hwtstamp)(struct phy_device *phydev, struct ifreq *ifr);
bool (*rxtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);
void (*txtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);
struct device_driver driver;
};
phy id/phy addr/mdio_read/mdio_write/autoneg自协商等
/* An instance of a PHY, partially borrowed from mii_if_info */
struct mii_phy {
struct mii_phy_def *def;
u32 advertising; /* Ethtool ADVERTISED_* defines */
u32 features; /* Copied from mii_phy_def.features
or determined automaticaly */
int address; /* PHY address */
int mode; /* PHY mode */
int gpcs_address; /* GPCS PHY address */
/* 1: autoneg enabled, 0: disabled */
int autoneg;
/* forced speed & duplex (no autoneg)
* partner speed & duplex & pause (autoneg)
*/
int speed;
int duplex;
int pause;
int asym_pause;
/* Provided by host chip */
struct net_device *dev;
int (*mdio_read) (struct net_device * dev, int addr, int reg);
void (*mdio_write) (struct net_device * dev, int addr, int reg,
int val);
};
设备树及mii bus总线配置
// imx6ull设备树配置
fec1: ethernet@2188000 {
compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec";
reg = <0x02188000 0x4000>;
interrupt-names = "int0", "pps";
interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_ENET>,
<&clks IMX6UL_CLK_ENET_AHB>,
<&clks IMX6UL_CLK_ENET_PTP>,
<&clks IMX6UL_CLK_ENET_REF>,
<&clks IMX6UL_CLK_ENET_REF>;
clock-names = "ipg", "ahb", "ptp",
"enet_clk_ref", "enet_out";
fsl,num-tx-queues = <1>;
fsl,num-rx-queues = <1>;
fsl,stop-mode = <&gpr 0x10 3>;
fsl,magic-packet;
status = "disabled";
};
fec2: ethernet@20b4000 {
compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec";
reg = <0x020b4000 0x4000>;
interrupt-names = "int0", "pps";
interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_ENET>,
<&clks IMX6UL_CLK_ENET_AHB>,
<&clks IMX6UL_CLK_ENET_PTP>,
<&clks IMX6UL_CLK_ENET2_REF_125M>,
<&clks IMX6UL_CLK_ENET2_REF_125M>;
clock-names = "ipg", "ahb", "ptp",
"enet_clk_ref", "enet_out";
fsl,num-tx-queues = <1>;
fsl,num-rx-queues = <1>;
fsl,stop-mode = <&gpr 0x10 4>;
fsl,magic-packet;
status = "disabled";
};
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <ðphy0>;
phy-supply = <®_peri_3v3>;
phy-reset-duration = <200>;
phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>; //phy复位管脚
status = "okay";
};
&fec2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet2>;
phy-mode = "rmii";
phy-handle = <ðphy1>;
phy-supply = <®_peri_3v3>;
phy-reset-duration = <200>;
phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; //phy复位管脚
status = "okay";
mdio { // mdio总线设备节点
#address-cells = <1>;
#size-cells = <0>;
ethphy0: ethernet-phy@0 { // 设备fec1,地址为0
compatible = "ethernet-phy-ieee802.3-c22";
reg = <0>;
smsc,disable-energy-detect;
clocks = <&clks IMX6UL_CLK_ENET_REF>;
clock-names = "rmii-ref";
};
ethphy1: ethernet-phy@1 { // 设备fec2,地址为1
compatible = "ethernet-phy-ieee802.3-c22";
reg = <1>;
smsc,disable-energy-detect;
clocks = <&clks IMX6UL_CLK_ENET2_REF>;
clock-names = "rmii-ref";
};
};
};
MAC驱动和mdio控制器注册
static const struct of_device_id fec_dt_ids[] = {
{ .compatible = "fsl,imx25-fec", .data = &fec_devtype[IMX25_FEC], },
{ .compatible = "fsl,imx27-fec", .data = &fec_devtype[IMX27_FEC], },
{ .compatible = "fsl,imx28-fec", .data = &fec_devtype[IMX28_FEC], },
{ .compatible = "fsl,imx6q-fec", .data = &fec_devtype[IMX6Q_FEC], },
{ /* sentinel */ }
};
static struct platform_driver fec_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &fec_pm_ops,
#endif
.of_match_table = fec_dt_ids,
},
.id_table = fec_devtype,
.probe = fec_probe,
.remove = __devexit_p(fec_drv_remove),
};
module_platform_driver(fec_driver);
MODULE_LICENSE("GPL");
设备树的compatible和驱动的compatible匹配成功之后,会调用fec_probe函数,此为mac驱动的真正入口。
1、注册网络设备net_device
2、申请队列和DMA
3、申请MDIO总线
4、创建并注册PHY设备
fec_probe(struct platform_device *pdev)
-> struct device_node *np = pdev->dev.of_node, *phy_node; // 获取设备树节点句柄,并创建一个phy的设备树节点句柄
-> fec_enet_get_queue_num(pdev, &num_tx_qs, &num_rx_qs); // 从设备树获取fsl,num-tx-queues和fsl,num-rx-queues的属性值
-> ndev = alloc_etherdev_mqs // 申请net_device
-> netdev_priv(ndev) // 获取私有数据空间首地址
--------------------------------------------------------------------------------------------------------------------------
-> of_parse_phandle(np, "phy-handle", 0) // 从mac的设备树节点中获取phy子节点
-> of_get_phy_mode(pdev->dev.of_node) // 从设备树节点中获取phy模式,phy-mode = "rmii";
-> fec_reset_phy(pdev); // 复位phy
-> fec_enet_init(ndev) // 申请队列和DMA,设置MAC地址
-> of_property_read_u32(np, "fsl,wakeup_irq", &irq) // 唤醒中断
-> fec_enet_mii_init(pdev); // 注册MDIO总线、注册phy_device
-> fep->mii_bus = mdiobus_alloc() //申请MDIO总线
-> fep->mii_bus->name = "fec_enet_mii_bus"; // 总线名字
-> fep->mii_bus->read = fec_enet_mdio_read; // 总线的读函数
-> fep->mii_bus->write = fec_enet_mdio_write; // 总线的写函数
-> snprintf(fep->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x",
pdev->name, fep->dev_id + 1); // 总线id
-> of_get_child_by_name(pdev->dev.of_node, "mdio"); // 获取phy节点句柄
-> of_mdiobus_register // 注册mii_bus设备,并通过设备树子节点创建PHY设备 drivers/of/of_mdio.c of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
-> mdio->phy_mask = ~0; // 屏蔽所有PHY,防止自动探测。相反,设备树中列出的phy将在总线注册后填充
-> mdio->dev.of_node = np;
-> mdio->reset_delay_us = DEFAULT_GPIO_RESET_DELAY;
-> mdiobus_register(mdio) // 注册MDIO总线设备
-> bus->dev.parent = bus->parent;
-> bus->dev.class = &mdio_bus_class; // 总线设备类“/sys/bus/mdio_bus”
/*-----------------------------------------
static struct class mdio_bus_class = {
.name = "mdio_bus",
.dev_release = mdiobus_release,
};
-------------------------------------------*/
-> bus->dev.groups = NULL;
-> dev_set_name(&bus->dev, "%s", bus->id); //设置总线设备的名称
-> device_register(&bus->dev); // 注册总线设备
-> if (bus->reset) bus->reset(bus); // 总线复位
---------------------------------------另一条分支解析(可忽略)--------------------------------------------------------
-> phydev = mdiobus_scan(bus, i); // 扫描phy设备
-> phydev = get_phy_device(bus, addr); //获取创建phy设备
->err = phy_device_register(phydev); //注册phy设备
--------------------------------------------------------------------------------------------------------------------
-> for_each_available_child_of_node(np, child) { // 遍历这个平台设备的子节点并为每个phy注册一个phy_device
-> addr = of_mdio_parse_addr(&mdio->dev, child) // 从子节点的"reg"属性中获得PHY设备的地址
-> of_property_read_u32(np, "reg", &addr)
-> if (addr < 0) scanphys = true; continue; // 如果未获得子节点的"reg"属性,则在后面再启用扫描可能存在的PHY的,然后注册
-> of_mdiobus_register_phy(mdio, child, addr) } // 创建并注册PHY设备
-> is_c45 = of_device_is_compatible(child,"ethernet-phy-ieee802.3-c45") //判断设备树中的PHY的属性是否指定45号条款
-> if (!is_c45 && !of_get_phy_id(child, &phy_id)) //如果设备树中的PHY的属性未指定45号条款 且未通过"ethernet-phy-id%4x.%4x"属性指定PHY的ID
-> phy = phy_device_create(mdio, addr, phy_id, 0, NULL);
-> else phy = get_phy_device(mdio, addr, is_c45); //用这个分支
-> get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);//通过mdio得到PHY的ID
-> mdiobus_read(bus, addr, MII_PHYSID1)
-> __mdiobus_read(bus, addr, regnum);
-> bus->read(bus, addr, regnum)
-> mdiobus_read(bus, addr, MII_PHYSID2)
-> phy_device_create(bus, addr, phy_id, is_c45, &c45_ids) // 创建PHY设备
-> struct phy_device *dev;
-> dev = kzalloc(sizeof(*dev), GFP_KERNEL);
dev->dev.release = phy_device_release;
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->addr = addr;
dev->phy_id = phy_id;
if (c45_ids)
dev->c45_ids = *c45_ids;
dev->bus = bus;
dev->dev.parent = bus->parent;
dev->dev.bus = &mdio_bus_type; //PHY设备和驱动都会挂在mdio_bus下,匹配时会调用对应的match函数 --
/*----------------------------------------------------------------------------------------------------
struct bus_type mdio_bus_type = {
.name = "mdio_bus", //总线名称
.match = mdio_bus_match, //用来匹配总线上设备和驱动的函数
.pm = MDIO_BUS_PM_OPS,
.dev_groups = mdio_dev_groups,
};
----------------------------------------------------------------------------------------------------*/
dev->irq = bus->irq != NULL ? bus->irq[addr] : PHY_POLL;
dev_set_name(&dev->dev, PHY_ID_FMT, bus->id, addr);
dev->state = PHY_DOWN; //指示PHY设备和驱动程序尚未准备就绪,在PHY驱动的probe函数中会更改为READY
-> INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); //PHY的状态机(核心WORK)后续解析
-> INIT_WORK(&dev->phy_queue, phy_change); // 由phy_interrupt / timer调度以处理PHY状态的更改
-> request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id)); // 加载内核模块
-> device_initialize(&dev->dev); //设备模型中的一些设备,主要是kset、kobject、ktype的设置
-> irq_of_parse_and_map(child, 0); //将中断解析并映射到linux virq空间(
-> of_node_get(child); //将OF节点与设备结构相关联
-> phy->dev.of_node = child;
-> phy_device_register(phy) // 注册phy设备
-> if (phydev->bus->phy_map[phydev->addr]) //判断PHY是否已经注册了
-> phydev->bus->phy_map[phydev->addr] = phydev; //添加PHY到总线的phy_map里
-> phy_scan_fixups(phydev); //执行匹配的fixups
-> device_add(&phydev->dev); // 注册到linux设备模型框架中
-> if (!scanphys) return 0; // 如果从子节点的"reg"属性中获得PHY设备的地址,scanphys=false,这里就直接返回了,因为不需要再扫描了
------------一般来说只要设备树种指定了PHY设备的"reg"属性,后面的流程可以自动忽略 ------------
-> register_netdev(ndev) // 向内核注册net_device
注:PHY的状态机(核心WORK)后续解析 PHY状态机以及网络相关操作命令解析
PHY总线驱动和加载
#define phy_module_driver(__phy_drivers, __count) \
static int __init phy_module_init(void) \
{ \
return phy_drivers_register(__phy_drivers, __count, THIS_MODULE); \
} \
module_init(phy_module_init); \
static void __exit phy_module_exit(void) \
{ \
phy_drivers_unregister(__phy_drivers, __count); \
} \
module_exit(phy_module_exit)
#define module_phy_driver(__phy_drivers) \
phy_module_driver(__phy_drivers, ARRAY_SIZE(__phy_drivers))