MAC和PHY 通信

目录

一、概念介绍

二、 MAC + PHY 组成

 三、PHY 驱动

一、概念介绍

                以太网的接口电路主要有(控制器)+ (物理层接口)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侧提供的
CRSCarrier Sense,载波侦测信号,不需要同步于参考时钟,只要有数据传输,CRS就有效,另外,CRS只有PHY在半双工模式下有效
COLCollision 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 = <&ethphy0>;
    phy-supply = <&reg_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 = <&ethphy1>;
    phy-supply = <&reg_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))

 

  • 20
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值