Linux 网络驱动-MAC、PHY层驱动框架(三)

这篇具有很好参考价值的文章主要介绍了Linux 网络驱动-MAC、PHY层驱动框架(三)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

I.MX6ULL 网络外设设备树

   I.MX6ULL 有两个 10/100M 的网络 MAC 外设,因此 I.MX6ULL 网络驱动主要就是这两个网络 MAC 外设的驱动。这两个外设的驱动都是一样的,我们分析其 中一个就行了,首先肯定是设备树, NXP 的 I.MX 系 列 SOC 网 络 绑 定 文 档 为 Documentation/devicetree/bindings/net/fsl-fec.txt,此绑定文档描述了 I.MX 系列 SOC 网络设备树 节点的要求。

①、必要属性

  compatible:这个肯定是必须的,一般是“fsl,-fec”,比如 I.MX6ULL 的 compatible 属 性就是"fsl,imx6ul-fec",和"fsl,imx6q-fec"。

 reg:SOC 网络外设寄存器地址范围。

 interrupts:网络中断。

 phy-mode:网络所使用的 PHY 接口模式,是 MII 还是 RMII。

②、可选属性

 phy-reset-gpios:PHY 芯片的复位引脚。 

 phy-reset-duration:PHY 复位引脚复位持续时间,单位为毫秒。只有当设置了 phy-reset-gpios 属性此属性才会有效,如果不设置此属性的话 PHY 芯片复位引脚的复位持续时间默认为 1 毫秒,数值不能大于 1000 毫秒,大于 1000 毫秒的话就会强制设置为 1 毫秒。

 phy-supply:PHY 芯片的电源调节。

 phy-handle:连接到此网络设备的 PHY 芯片句柄。

 fsl,num-tx-queues:此属性指定发送队列的数量,如果不指定的话默认为 1。

 fsl,num-rx-queues:此属性指定接收队列的数量,如果不指定的话默认为 2。

 fsl,wakeup_irq:此属性设置唤醒中断索引。

 stop-mode:如果此属性存在的话表明 SOC 需要设置 GPR 位来请求停止模式 。

③、可选子节点

  mdio:可以设置名为“mdio”的子节点,此子节点用于指定网络外设所使用的 MDIO 总线, 主要作为 PHY 节点的容器,也就是在 mdio 子节点下指定 PHY 相关的属性信息,具体信息可 以参考 PHY 的绑定文档 Documentation/devicetree/bindings/net/phy.txt。

  PHY 节点相关属性内容如下:

  interrupts:中断属性,可以不需要。

  interrupt-parent:中断控制器句柄,可以不需要。

  reg:PHY 芯片地址,必须的!

  compatible:兼容性列表,一般为“ethernet-phy-ieee802.3-c22”或“ethernet-phy-ieee802.3- c45”,分别对应 IEEE802.3 的 22 簇和 45 簇,默认是 22 簇。也可以设置为其他值,如果 PHY 的 ID 不知道的话可以 compatible 属性可以设置为“ethernet-phy-idAAAA.BBBB”,AAAA 和 BBBB 的含义如下:

 AAAA:PHY 的 16 位 ID 寄存器 1 值,也就是 OUI 的 bit3~18,16 进制格式。

 BBBB:PHY 的 16 位 ID 寄存器 2 值,也就是 OUI 的 bit19~24,16 进制格式。

 max-speed:PHY 支持的最高速度,比如 10、100 或 1000。

打开imx6ull.dtsi,找到I.MX6ULL的两个网络外设节点,如图:

 fec1: ethernet@02188000 {
     compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec";
     reg = <0x02188000 0x4000>;
     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";
     stop-mode = <&gpr 0x10 3>;
     fsl,num-tx-queues=<1>;
     fsl,num-rx-queues=<1>;
     fsl,magic-packet;
     fsl,wakeup_irq = <0>;
     status = "disabled";
};

 fec2: ethernet@020b4000 {
    compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec";
    reg = <0x020b4000 0x4000>;
    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";
    stop-mode = <&gpr 0x10 4>;
    fsl,num-tx-queues=<1>;
    fsl,num-rx-queues=<1>;
    fsl,magic-packet;
    fsl,wakeup_irq = <0>;
    status = "disabled";
};

    

    fec1 和 fec2 分别对应 I.MX6ULL 的 ENET1 和 ENET2,至于节点的具体属性就不分析了, 上面在讲解绑定文档的时候就已经详细的讲过了。示例代码 69.4.1.1 是 NXP 官方编写的,我们 不需要去修改,但是示例代码 69.4.1.1 是不能工作的,还需要根据实际情况添加或修改一些属 性。打开 imx6ull-alientek-emmc.dts,找到如下内容:

&fec1 {
      pinctrl-names = "default";
      pinctrl-0 = <&pinctrl_enet1
            &pinctrl_enet1_reset>;
      phy-mode = "rmii";
      phy-handle = <&ethphy0>;
      phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;
      phy-reset-duration = <200>;
      status = "okay";
};

&fec2 {
      pinctrl-names = "default";
      pinctrl-0 = <&pinctrl_enet2
      &pinctrl_enet2_reset>;
      phy-mode = "rmii";
      phy-handle = <&ethphy1>;
      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";
             reg = <0>;
         };

        ethphy1: ethernet-phy@1 {
            compatible = "ethernet-phy-ieee802.3-c22";
            reg = <1>;
         };
};


   第 1~10 行:ENET1 网口的节点属性,第 3、4 行设置 ENET1 所使用的引脚 pinctrl 节点信 息,第 5 行设置网络对应的 PHY 芯片接口为 RMII,这个要根据实际的硬件来设置。第 6 行设 置 PHY 芯片的句柄为 ethphy0,MDIO 节点会设置 PHY 信息。其他的属性信息就很好理解了, 基本已经在上面讲解绑定文档的时候说过了。

   第 12~36 行:ENET2 网口的节点属性,基本和 ENET1 网口一致,区别就是多了第 22~35 行的 mdio 子节点,前面讲解绑定文档的时候说了,mido 子节点用于描述 MIDO 总线,在此子 节点内会包含 PHY 节点信息。这里一共有两个 PHY 子节点:ethphy0 和 ethphy1,分别对应 ENET1 和 ENET2 的 PHY 芯片。比如第 26 行的“ethphy0: ethernet-phy@0”就是 ENET1 的 PHY 节点名字,“@”后面的 0 就是此 PHY 芯片的芯片地址,reg 属性也是描述 PHY 芯片地址的, 这点和 IIC 设备节点很像。其他地方就没什么好多的了,绑定文档已经讲解的很清楚了。

  最后就是设备树中网络相关引脚的描述,打开 imx6ull-alientek-emmc.dts,找到如下所示内容:

1 pinctrl_enet1: enet1grp {
2     fsl,pins = <
3     MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0
4     MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0
5     MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0
6     MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0
7     MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0
8     MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0
9     MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0
10    MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b009
11 >;
12 };
13
14 pinctrl_enet2: enet2grp {
15     fsl,pins = <
16     MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0
17     MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0
18     MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0
19     MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0
20     MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0
21     MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0
22     MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0
23     MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0
24     MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0
25     MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b009
26     >;
27 };
28
29 /*enet1 reset zuozhongkai*/
30 pinctrl_enet1_reset: enet1resetgrp {
31     fsl,pins = <
32 /* used for enet1 reset */
33     MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0 
34     >;
35 };
36
37 /*enet2 reset zuozhongkai*/
38 pinctrl_enet2_reset: enet2resetgrp {
39     fsl,pins = <
40 /* used for enet2 reset */
41     MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0 
42     >;
43 };


   pinctrl_enet1 和 pinctrl_enet1_reset 是 ENET1 所有的 IO 引脚 pinctrl 信息,之所以分两部分 主 要 是 因 为 ENET1 的 复 位 引 脚 为 GPIO5_IO07 , 而 GPIO5_IO07 对 应 的 引 脚 就 是 SNVS_TAMPER7,要放到 iomuxc_snvs 节点下,所以就分成了两部分。 注意第 16、17 行,这两行设置 GPIO1_IO07 和 GPIO1_IO06 为 ENET2 的 MDC 和 MDIO, 大家可能会疑问,为什么不将其设置为 ENET1 的 MDC 和 MDIO 呢?经过笔者实测,在开启两 个网口的情况下,将 GPIO1_IO07 和 GPIO1_IO06 设置为 ENET1 的 MDC 和 MDIO 会对导致网 络工作不正常。前面说了,一个 MDIO 接口可以管理 32 个 PHY,所以设置 ENET2 的 MDC 和 MDIO 以后也是可以管理 ENET1 上的 PHY 芯片。

I.MX6ULL 网络驱动源码简析

1、fec_probe 函数简析

   对于 I.MX6ULL 而言网络驱动主要分两部分:I.MX6ULL 网络外设驱动以及 PHY 芯片驱 动,网络外设驱动是 NXP 编写的,PHY 芯片有通用驱动文件,有些 PHY 芯片厂商还会针对自 己的芯片编写对应的 PHY 驱动。总体来说,SOC 内置网络 MAC+外置 PHY 芯片这种方案我们 是不需要编写什么驱动的,基本可以直接使用。但是为了学习,我们还是要简单分析一下具体 的网络驱动编写过程。

   首先来看一下 I.MX6ULL 的网络控制器部分驱动,从示例代码 69.4.1.1 中可以看出, compatible 属性有两个值“fsl,imx6ul-fec”和“fsl,imx6q-fec”,通过在 linux 内核源码中搜索这两个 字符串即可找到对应的驱动文件,驱动文件为 drivers/net/ethernet/freescale/fec_main.c,打开 fec_main.c,找到如下所示内容:

1 static const struct of_device_id fec_dt_ids[] = {
2     { .compatible = "fsl,imx25-fec", .data =
            &fec_devtype[IMX25_FEC], },
3     { .compatible = "fsl,imx27-fec", .data =
            &fec_devtype[IMX27_FEC], },
4     { .compatible = "fsl,imx28-fec", .data =
            &fec_devtype[IMX28_FEC], },
5     { .compatible = "fsl,imx6q-fec", .data =
            &fec_devtype[IMX6Q_FEC], },
6     { .compatible = "fsl,mvf600-fec", .data =
            &fec_devtype[MVF600_FEC], },
7     { .compatible = "fsl,imx6sx-fec", .data =
            &fec_devtype[IMX6SX_FEC], },
8     { .compatible = "fsl,imx6ul-fec", .data =
            &fec_devtype[IMX6UL_FEC], },
9     { /* sentinel */ }
10 };
11
12 static struct platform_driver fec_driver = {
13         .driver = {
14             .name = DRIVER_NAME,
15             .pm = &fec_pm_ops,
16             .of_match_table = fec_dt_ids,
17         },
18         .id_table = fec_devtype,
19         .probe = fec_probe,
20         .remove = fec_drv_remove,
21 };

  第 8 行,匹配表包含“fsl,imx6ul-fec”,因此设备树和驱动匹配上,当匹配成功以后第 19 行 的 fec_probe 函数就会执行,我们简单分析一下 fec_probe 函数,函数内容如下:

1 static int fec_probe(struct platform_device *pdev)
2 {
3       struct fec_enet_private *fep;
4       struct fec_platform_data *pdata;
5       struct net_device *ndev;
6       int i, irq, ret = 0;
7       struct resource *r;
8       const struct of_device_id *of_id;
9       static int dev_id;
10      struct device_node *np = pdev->dev.of_node, *phy_node;
11      int num_tx_qs;
12      int num_rx_qs;
13 
14     fec_enet_get_queue_num(pdev, &num_tx_qs, &num_rx_qs);
15 
16     /* Init network device */
17     ndev = alloc_etherdev_mqs(sizeof(struct fec_enet_private),
18              num_tx_qs, num_rx_qs);
19     if (!ndev)
20        return -ENOMEM;
21 
22     SET_NETDEV_DEV(ndev, &pdev->dev);
23 
24    /* setup board info structure */
25    fep = netdev_priv(ndev);
26 
27    of_id = of_match_device(fec_dt_ids, &pdev->dev);
28    if (of_id)
29      pdev->id_entry = of_id->data;
30      fep->quirks = pdev->id_entry->driver_data;
31 
32   fep->netdev = ndev;
33   fep->num_rx_queues = num_rx_qs;
34   fep->num_tx_queues = num_tx_qs;
35 
36 #if !defined(CONFIG_M5272)
37     /* default enable pause frame auto negotiation */
38     if (fep->quirks & FEC_QUIRK_HAS_GBIT)
39          fep->pause_flag |= FEC_PAUSE_FLAG_AUTONEG;
40 #endif
41 
42     /* Select default pin state */
43     pinctrl_pm_select_default_state(&pdev->dev);
44 
45     r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
46     fep->hwp = devm_ioremap_resource(&pdev->dev, r);
47     if (IS_ERR(fep->hwp)) {
48          ret = PTR_ERR(fep->hwp);
49          goto failed_ioremap;
50      }
51 
52      fep->pdev = pdev;
53      fep->dev_id = dev_id++;
54 
55      platform_set_drvdata(pdev, ndev);
56 
57      fec_enet_of_parse_stop_mode(pdev);
58 
59      if (of_get_property(np, "fsl,magic-packet", NULL))
60         fep->wol_flag |= FEC_WOL_HAS_MAGIC_PACKET;
61 
62      phy_node = of_parse_phandle(np, "phy-handle", 0);
63      if (!phy_node && of_phy_is_fixed_link(np)) {
64         ret = of_phy_register_fixed_link(np);
65        if (ret < 0) {
66              dev_err(&pdev->dev,
67                       "broken fixed-link specification\n");
68                 goto failed_phy;
69         }
70         phy_node = of_node_get(np);
71      }
72     fep->phy_node = phy_node;
73 
74      ret = of_get_phy_mode(pdev->dev.of_node);
75      if (ret < 0) {
76        pdata = dev_get_platdata(&pdev->dev);
77        if (pdata)
78            fep->phy_interface = pdata->phy;
79        else
80            fep->phy_interface = PHY_INTERFACE_MODE_MII;
81      } else {
82            fep->phy_interface = ret;
83      }
84 
85      fep->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
86      if (IS_ERR(fep->clk_ipg)) {
87          ret = PTR_ERR(fep->clk_ipg);
88          goto failed_clk;
89      }
90 
91      fep->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
92      if (IS_ERR(fep->clk_ahb)) {
93           ret = PTR_ERR(fep->clk_ahb);
94           goto failed_clk;
95      }
96 
97      fep->itr_clk_rate = clk_get_rate(fep->clk_ahb);
98 
99      /* enet_out is optional, depends on board */
100       fep->clk_enet_out = devm_clk_get(&pdev->dev, "enet_out");
101       if (IS_ERR(fep->clk_enet_out))
102          fep->clk_enet_out = NULL;
103
104     fep->ptp_clk_on = false;
105     mutex_init(&fep->ptp_clk_mutex);
106
107     /* clk_ref is optional, depends on board */
108     fep->clk_ref = devm_clk_get(&pdev->dev, "enet_clk_ref");
109     if (IS_ERR(fep->clk_ref))
110        fep->clk_ref = NULL;
111
112     fep->bufdesc_ex = fep->quirks & FEC_QUIRK_HAS_BUFDESC_EX;
113     fep->clk_ptp = devm_clk_get(&pdev->dev, "ptp");
114     if (IS_ERR(fep->clk_ptp)) {
115        fep->clk_ptp = NULL;
116        fep->bufdesc_ex = false;
117     }
118
119     pm_runtime_enable(&pdev->dev);
120     ret = fec_enet_clk_enable(ndev, true);
121     if (ret)
122       goto failed_clk;
123
124    fep->reg_phy = devm_regulator_get(&pdev->dev, "phy");
125    if (!IS_ERR(fep->reg_phy)) {
126       ret = regulator_enable(fep->reg_phy);
127    if (ret) {
128        dev_err(&pdev->dev,
129                 "Failed to enable phy regulator: %d\n", ret);
130       goto failed_regulator;
131     }
132 } else {
133       fep->reg_phy = NULL;
134 }
135
136    fec_reset_phy(pdev);
137
138    if (fep->bufdesc_ex)
139        fec_ptp_init(pdev);
140
141     ret = fec_enet_init(ndev);
142     if (ret)
143     goto failed_init;
144
145     for (i = 0; i < FEC_IRQ_NUM; i++) {
146       irq = platform_get_irq(pdev, i);
147      if (irq < 0) {
148          if (i)
149             break;
150           ret = irq;
151           goto failed_irq;
152      }
153      ret = devm_request_irq(&pdev->dev, irq, fec_enet_interrupt,
154                                                0, pdev->name, ndev);
155      if (ret)
156           goto failed_irq;
157
158       fep->irq[i] = irq;
159     }
160
161     ret = of_property_read_u32(np, "fsl,wakeup_irq", &irq);
162     if (!ret && irq < FEC_IRQ_NUM)
163           fep->wake_irq = fep->irq[irq];
164     else
165            fep->wake_irq = fep->irq[0];
166
167     init_completion(&fep->mdio_done);
168     ret = fec_enet_mii_init(pdev);
169     if (ret)
170        goto failed_mii_init;
171
172     /* Carrier starts down, phylib will bring it up */
173     netif_carrier_off(ndev);
174     fec_enet_clk_enable(ndev, false);
175     pinctrl_pm_select_sleep_state(&pdev->dev);
176
177     ret = register_netdev(ndev);
178     if (ret)
179       goto failed_register;
180
181    device_init_wakeup(&ndev->dev, fep->wol_flag &
182    FEC_WOL_HAS_MAGIC_PACKET);
183
184    if (fep->bufdesc_ex && fep->ptp_clock)
185     netdev_info(ndev, "registered PHC device %d\n", fep->dev_id);
186
187    fep->rx_copybreak = COPYBREAK_DEFAULT;
188    INIT_WORK(&fep->tx_timeout_work, fec_enet_timeout_work);
189    return 0;
......
206    return ret;
207 }


   第 14 行,使用 fec_enet_get_queue_num 函数来获取设备树中的“fsl,num-tx-queues”和 “fsl,num-rx-queues”这两个属性值,也就是发送队列和接收队列的大小,设备树中这两个属性 都设置为 1。

  第 17 行,使用 alloc_etherdev_mqs 函数申请 net_device。

  第 25 行,获取 net_device 中私有数据内存首地址,net_device 中的私有数据用来存放 I.MX6ULL 网络设备结构体,此结构体为 fec_enet_private。

 第 30 行,接下来所有以“fep->”开头的代码行就是初始化网络设备结构体各个成员变量, 结构体类型为 fec_enet_privatede,这个结构体是 NXP 自己定义的。

 第 45 行,获取设备树中 I.MX6ULL 网络外设(ENET)相关寄存器起始地址,ENET1 的寄存 器起始地址 0X02188000,ENET2 的寄存器起始地址 0X20B4000。

 第 46 行,对第 45 行获取到的地址做虚拟地址转换,转换后的 ENET 虚拟寄存器起始地址 保存在 fep 的 hwp 成员中。

第 57 行,使用 fec_enet_of_parse_stop_mode 函数解析设备树中关于 ENET 的停止模式属性 值,属性名字为“stop-mode”,我们没有用到。

第 59 行,从设备树查找“fsl,magic-packet”属性是否存在,如果存在的话就说明有魔术包, 有魔术包的话就将 fep 的 wol_flag 成员与 FEC_WOL_HAS_MAGIC_PACKET 进行或运算,也 就是在 wol_flag 中做登记,登记支持魔术包。

第 62 行,获取“phy-handle”属性的值,phy-handle 属性指定了 I.MX6ULL 网络外设所对 应获取 PHY 的设备节点。在设备树的 fec1 和 fec2 两个节点中 phy-handle 属性值分别为:

phy-handle = <&ethphy0>;
phy-handle = <&ethphy1>;

而 ethphy0 和 ethphy1 都定义在 mdio 子节点下,内容如下所示:

mdio {
        #address-cells = <1>;
        #size-cells = <0>;
        ethphy0: ethernet-phy@0 {
            compatible = "ethernet-phy-ieee802.3-c22";
            reg = <0>;
        };
        ethphy1: ethernet-phy@1 {
            compatible = "ethernet-phy-ieee802.3-c22";
            reg = <1>;
        };
};

    可以看出 ethphy0 和 ethphy1 都是与 MDIO 相关的,而 MDIO 接口是配置 PHY 芯片的,通 过一个 MDIO 接口可以配置多个 PHY 芯片,不同的 PHY 芯片通过不同的地址进行区别。正点 原子 ALPHA 开发板中 ENET 的 PHY 地址为 0X00,ENET2 的 PHY 地址为 0X01。这两个 PHY 地址要通过设备树告诉 Linux 系统,下面两行代码@后面的数值就是 PHY 地址:

ethphy0: ethernet-phy@2
ethphy1: ethernet-phy@1

  并且 ethphy0 和 ethphy1 节点中的 reg 属性也是 PHY 地址,如果我们要更换其他的网络 PHY 芯片,第一步就是要修改设备树中的 PHY 地址。

  第 74 行,获取 PHY 工作模式,函数 of_get_phy_mode 会读取属性 phy-mode 的值,” phy-mode”中保存了 PHY 的工作方式,即 PHY 是 RMII 还是 MII,IMX6ULL 中的 PHY 工作在 RMII 模式,设备树描述如下所示:

 第 85、91、100、108 和 113 行,分别获取时钟 ipg、ahb、enet_out、enet_clk_ref 和 ptp,对 应结构体 fec_enet_private 有如下成员函数:

struct clk *clk_ipg;
struct clk *clk_ahb;
struct clk *clk_ref;
struct clk *clk_enet_out;
struct clk *clk_ptp;

第 120 行,使能时钟。

第 136 行,调用函数 fec_reset_phy 复位 PHY。

第 141 行,调用函数 fec_enet_init()初始化 enet,此函数会分配队列、申请 dma、设置 MAC 地址,初始化 net_device 的 netdev_ops 和 ethtool_ops 成员,如图所示:

Linux 网络驱动-MAC、PHY层驱动框架(三)

    net_device 的 netdev_ops 和 ethtool_ops 成变量分别初始化成了 fec_netdev_ops 和 fec_enet_ethtool_ops。fec_enet_init 函数还会调用 netif_napi_add 来设置 poll 函 数,说明 NXP 官方编写的此网络驱动是 NAPI 兼容驱动,

Linux 网络驱动-MAC、PHY层驱动框架(三)

     通过 netif_napi_add 函数向网卡添加了一个 napi 示例,使用 NAPI 驱动要提供一个 poll 函数来轮询处理接收数据,此处的 poll 函数为 fec_enet_rx_napi,后面分析 网络数据接收处理流程的时候详细讲解此函数

  最后,fec_enet_init 函数会设置 IMX6ULL 网络外设相关硬件寄存器.

 第 146 行,从设备树中获取中断号。

 第 153 行,申请中断,中断处理函数为 fec_enet_interrupt,重点!后面会分析此函数。

第 161 行,从设备树中获取属性“fsl,wakeup_irq”的值,也就是唤醒中断.

第 167 行,初始化完成量 completion,用于一个执行单元等待另一个执行单元执行完某事.

第 168 行,函数 fec_enet_mii_init 完成 MII/RMII 接口的初始化.

Linux 网络驱动-MAC、PHY层驱动框架(三)

   mii_bus 下的 read 和 write 这两个成员变量分别是读/写 PHY 寄存器的操作函数,这里设置 为 fec_enet_mdio_read 和 fec_enet_mdio_write,这两个函数就是 I.MX 系列 SOC 读写 PHY 内部 寄存器的函数,读取或者配置 PHY 寄存器都会通过这两个 MDIO 总线函数完成。 fec_enet_mii_init 函数最终会向 Linux 内核注册 MIDO 总线.

 node = of_get_child_by_name(pdev->dev.of_node, "mdio"); 
 if (node) {
        err = of_mdiobus_register(fep->mii_bus, node);
       of_node_put(node);
5 } else {
6       err = mdiobus_register(fep->mii_bus);
7 }

     示例代码代码 第 1 行就是从设备树中获取 mdio 节点,如果节点存在的话就会通 过 of_mdiobus_register 或者 mdiobus_register 来向内核注册 MDIO 总线,如果采用设备树的话就 使用 of_mdiobus_register 来注册 MDIO 总线,否则就使用 mdiobus_register 函数。

    继续回到示例代码 ,接着分析 fec_probe 函数。

第 173 行,先调用函数 netif_carrier_off 通知内核,先关闭链路,phylib 会打开

第 174 行,调用函数 fec_enet_clk_enable 使能网络相关时钟。

第 177 行,调用函数 register_netdev 注册 net_device!

2、MDIO 总线注册

  MDIO 我们讲了很多次了,就是用来管理 PHY 芯片的,分为 MDIO 和 MDC 两根线,Linux 内核专门为 MDIO 准备一个总线,叫做 MDIO 总线,采用 mii_bus 结构体表示,定义在 include/linux/phy.h 文件中,mii_bus 结构体如下所示:

1 struct mii_bus {
2       const char *name;
3       char id[MII_BUS_ID_SIZE];
4       void *priv;
5       int (*read)(struct mii_bus *bus, int phy_id, int regnum);
6       int (*write)(struct mii_bus *bus, int phy_id, int regnum,
                  u16 val);
7        int (*reset)(struct mii_bus *bus);
8 
9       /*
10       * A lock to ensure that only one thing can read/write
11       * the MDIO bus at a time
12      */
13      struct mutex mdio_lock;
14
15      struct device *parent;
16      enum {
17         MDIOBUS_ALLOCATED = 1,
18         MDIOBUS_REGISTERED,
19         MDIOBUS_UNREGISTERED,
20         MDIOBUS_RELEASED,
21      } state;
22      struct device dev;
23
24      /* list of all PHYs on bus */
25      struct phy_device *phy_map[PHY_MAX_ADDR];
26
27      /* PHY addresses to be ignored when probing */
28      u32 phy_mask;
29
30      /*
31       * Pointer to an array of interrupts, each PHY's
32       * interrupt at the index matching its address
33      */
34      int *irq;
35 };     
 

     重点是第 5、6 两行的 read 和 write 函数,这两个函数就是读/些 PHY 芯片的操作函数,不 同的 SOC 其 MDIO 主控部分是不一样的,因此需要驱动编写人员去编写。我们前面在分析 fec_probe 函数的时候已经讲过了,fec_probe 函数会调用 fec_enet_mii_init 函数完成 MII 接口的 初始化,其中就包括初始化 mii_bus 下的 read 和 write 这两个函数。最终通过 of_mdiobus_register 或者 mdiobus_register 函数将初始化以后的 mii_bus 注册到 Linux 内核,of_mdiobus_register 函 数其实最终也是调用的 mdiobus_register 函数来完成 mii_bus 注册的。of_mdiobus_register 函数 内容如下(限于篇幅,有省略):

            示例代码of_mdiobus_register 函数
1 int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
2 {
3        struct device_node *child;
4        const __be32 *paddr;
5        bool scanphys = false;
6        int addr, rc, i;
7 
8       /* Mask out all PHYs from auto probing. Instead the PHYs listed 
9       * in the device tree are populated after the bus has been 
        *registered */
10       mdio->phy_mask = ~0;
11
12      /* Clear all the IRQ properties */
13     if (mdio->irq)
14     for (i=0; i<PHY_MAX_ADDR; i++)
15       mdio->irq[i] = PHY_POLL;
16
17     mdio->dev.of_node = np;
18
19    /* Register the MDIO bus */
20    rc = mdiobus_register(mdio);
21    if (rc)
22      return rc;
23
24    /* Loop over the child nodes and register a phy_device for each 
one     */
25    for_each_available_child_of_node(np, child) {
26    addr = of_mdio_parse_addr(&mdio->dev, child);
27    if (addr < 0) {
28     scanphys = true;
29     continue;
30    }
31
32    rc = of_mdiobus_register_phy(mdio, child, addr);
33    if (rc)
34   continue;
35  }
36
37   if (!scanphys)
38    return 0;
39 
......
62   return 0;
63 }

第 20 行,调用 mdiobus_register 函数来向 Linux 内核注册 mdio 总线!

第 25 行,轮询 mdio 节点下的所有子节点,比如示例代码 69.4.1.2 中的“ethphy0: ethernet-phy@0”和“ethphy1: ethernet-phy@1”这两个子节点,这两个子节点描述的是 PHY 芯片信息。

第 26 行,提取设备树子节点中 PHY 地址,也就是 ethphy0: ethernet-phy@0”和“ethphy1: ethernet-phy@1”这两个子节点对应的 PHY 芯片地址,分别为 0 和 1。

第 32 行,调用 of_mdiobus_register_phy 函数向 Linux 内核注册 phy。

简单总结一下,of_mdiobus_register 函数有两个主要的功能,一个是通过 mdiobus_register 函数向 Linux 内核注册 mdio 总线,另一个就是通过 of_mdiobus_register_phy 函数向内核注册 PHY。

接下来简单分析一下 of_mdiobus_register_phy 函数,看看是如何向 Linux 内核注册 PHY 设 备的,of_mdiobus_register_phy 函数内容如下所示:

1    static int of_mdiobus_register_phy(struct mii_bus *mdio,
                         struct device_node *child,
2                                 u32 addr)
3 {
4        struct phy_device *phy;
5        bool is_c45;
6        int rc;
7        u32 phy_id;
8 
9       is_c45 = of_device_is_compatible(child,
10      "ethernet-phy-ieee802.3-c45");
11
12     if (!is_c45 && !of_get_phy_id(child, &phy_id))
13      phy = phy_device_create(mdio, addr, phy_id, 0, NULL);
14    else
15      phy = get_phy_device(mdio, addr, is_c45);
16    if (!phy || IS_ERR(phy))
17      return 1;
18
19    rc = irq_of_parse_and_map(child, 0);
20    if (rc > 0) {
21      phy->irq = rc;
22    if (mdio->irq)
23    mdio->irq[addr] = rc;
24    } else {
25     if (mdio->irq)
26     phy->irq = mdio->irq[addr];
27    }
28
29    /* Associate the OF node with the device structure so it
30     * can be looked up later */
31    of_node_get(child);
32    phy->dev.of_node = child;
33
34    /* All data is now stored in the phy struct;
35     * register it */
36    rc = phy_device_register(phy);
37    if (rc) {
38      phy_device_free(phy);
39      of_node_put(child);
40      return 1;
41    }
42
43    dev_dbg(&mdio->dev, "registered phy %s at address %i\n",
44         child->name, addr);
45
46    return 0;
47 }

     第 9 行,使用函数of_device_is_compatible 检查 PHY节点的 compatible 属性是否为“ethernet-phy-ieee802.3-c45”,如果是的话要做其他的处理,本章节我们设置的 compatible 属性为“ethernet-phy-ieee802.3-c22”。

    第 15 行,调用 get_phy_device 函数获取 PHY 设备,此函数里面会调用 phy_device_create 来创建一个 phy_device 设备并返回.

   第 19 行,获取 PHY 芯片的中断信息,本章节并未用到。

   第 36 行,调用 phy_device_register 函数向 Linux 内核注册 PHY 设备。

    从上面的分析可以看出,向 Linux 内核注册 MDIO 总线的时候也会同时向 Linux 内核注册 PHY 设备,流程如图所示:

Linux 网络驱动-MAC、PHY层驱动框架(三)

 注册 MIDO 总线的时候会从设备树中查找 PHY 设备,然后通过 phy_device_register 函数向 内核注册 PHY 设备,接下来我们就来学习一下 PHY 子系统。

3、fec_drv_remove 函数简析

   卸载 I.MX6ULL 网络驱动的时候 fec_drv_remove 函数就会执行,函数内容如下所示:

1 static int fec_drv_remove(struct platform_device *pdev)
2 {
3      struct net_device *ndev = platform_get_drvdata(pdev);
4      struct fec_enet_private *fep = netdev_priv(ndev);
5 
6      cancel_delayed_work_sync(&fep->time_keep);
7      cancel_work_sync(&fep->tx_timeout_work);
8      unregister_netdev(ndev);
9      fec_enet_mii_remove(fep);
10     if (fep->reg_phy)
11       regulator_disable(fep->reg_phy);
12     if (fep->ptp_clock)
13         ptp_clock_unregister(fep->ptp_clock);
14      of_node_put(fep->phy_node);
15     free_netdev(ndev);
16
17    return 0;
18 }

第 8 行,调用 unregister_netdev 函数注销前面注册的 net_device。

第 9 行,调用 fec_enet_mii_remove 函数来移除掉 MDIO 总线相关的内容,此函数会调用 mdiobus_unregister 来注销掉 mii_bus,并且通过函数 mdiobus_free 释放掉 mii_bus

第 15 行,调用 free_netdev 函数释放掉前面申请的 net_device。

4、fec_netdev_ops 操作集

    fec_probe 函数设置了网卡驱动的 net_dev_ops 操作集为 fec_netdev_ops,fec_netdev_ops 内 容如下:

1 static const struct net_device_ops fec_netdev_ops = {
2         .ndo_open = fec_enet_open,
3         .ndo_stop = fec_enet_close,
4         .ndo_start_xmit = fec_enet_start_xmit,
5         .ndo_select_queue = fec_enet_select_queue,
6         .ndo_set_rx_mode = set_multicast_list,
7         .ndo_change_mtu = eth_change_mtu,
8         .ndo_validate_addr = eth_validate_addr,
9         .ndo_tx_timeout = fec_timeout,
10        .ndo_set_mac_address = fec_set_mac_address,
11        .ndo_do_ioctl = fec_enet_ioctl,
12 #ifdef CONFIG_NET_POLL_CONTROLLER
13        .ndo_poll_controller = fec_poll_controller,
14 #endif
15        .ndo_set_features = fec_set_features,
16 };

  fec_enet_open 函数简析

    打开一个网卡的时候 fec_enet_open 函数就会执行,函数源码如下所示(限于篇幅原因,有 省略):


            示例代码 fec_enet_open 函数
1 static int fec_enet_open(struct net_device *ndev)
2 {
3       struct fec_enet_private *fep = netdev_priv(ndev);
4       const struct platform_device_id *id_entry =
5       platform_get_device_id(fep->pdev);
6       int ret;
7 
8       pinctrl_pm_select_default_state(&fep->pdev->dev);
9       ret = fec_enet_clk_enable(ndev, true);
10     if (ret)
11        return ret;
12
13     /* I should reset the ring buffers here, but I don't yet know
14      * a simple way to do that.
15     */
16
17     ret = fec_enet_alloc_buffers(ndev);
18     if (ret)
19       goto err_enet_alloc;
20
21     /* Init MAC prior to mii bus probe */
22     fec_restart(ndev);
23
24     /* Probe and connect to PHY when open the interface */
25     ret = fec_enet_mii_probe(ndev);
26     if (ret)
27     goto err_enet_mii_probe;
28
29     napi_enable(&fep->napi);
30     phy_start(fep->phy_dev);
31     netif_tx_start_all_queues(ndev);
32
......
47
48     return 0;
49
50    err_enet_mii_probe:
51    fec_enet_free_buffers(ndev);
52    err_enet_alloc:
53    fep->miibus_up_failed = true;
54    if (!fep->mii_bus_share)
55    pinctrl_pm_select_sleep_state(&fep->pdev->dev);
56    return ret;
57 }

第 9 行,调用 fec_enet_clk_enable 函数使能 enet 时钟。

第 17 行,调用 fec_enet_alloc_buffers 函数申请环形缓冲区 buffer,此函数里面会调用fec_enet_alloc_rxq_buffers 和 fec_enet_alloc_txq_buffers 这两个函数分别实现发送队列和接收队 列缓冲区的申请。

第 22 行,重启网络,一般连接状态改变、传输超时或者配置网络的时候都会调用 fec_restart 函数。

第 25 行,打开网卡的时候调用 fec_enet_mii_probe 函数来探测并连接对应的 PHY 设备。

第 29 行,调用 napi_enable 函数使能 NAPI 调度。

第 30 行,调用 phy_start 函数开启 PHY 设备。

第 31 行,调用 netif_tx_start_all_queues 函数来激活发送队列。

fec_enet_close 函数简析

  关闭网卡的时候 fec_enet_close 函数就会执行,函数内容如下:

1 static int fec_enet_close(struct net_device *ndev)
2 {
3      struct fec_enet_private *fep = netdev_priv(ndev);
4 
5      phy_stop(fep->phy_dev);
6 
7      if (netif_device_present(ndev)) {
8          napi_disable(&fep->napi);
9          netif_tx_disable(ndev);
10         fec_stop(ndev);
11     }
12
13    phy_disconnect(fep->phy_dev);
14    fep->phy_dev = NULL;
15
16    fec_enet_clk_enable(ndev, false);
17    pm_qos_remove_request(&fep->pm_qos_req);
18    pinctrl_pm_select_sleep_state(&fep->pdev->dev);
19    pm_runtime_put_sync_suspend(ndev->dev.parent);
20    fec_enet_free_buffers(ndev);
21
22    return 0;
23 }

第 5 行,调用 phy_stop 函数停止 PHY 设备。

第 8 行,调用 napi_disable 函数关闭 NAPI 调度。

第 9 行,调用 netif_tx_disable 函数关闭 NAPI 的发送队列。

第 10 行,调用 fec_stop 函数关闭 I.MX6ULL 的 ENET 外设。

第 13 行,调用 phy_disconnect 函数断开与 PHY 设备的连接。

第 16 行,调用 fec_enet_clk_enable 函数关闭 ENET 外设时钟。

第 20 行,调用 fec_enet_free_buffers 函数释放发送和接收的环形缓冲区内存。

fec_enet_start_xmit 函数简析

     I.MX6ULL 的网络数据发送是通过 fec_enet_start_xmit 函数来完成的,这个函数将上层传递 过来的 sk_buff 中的数据通过硬件发送出去,函数源码如下:

1 static netdev_tx_t fec_enet_start_xmit(struct sk_buff *skb,
                                      struct net_device *ndev)
2 {
3      struct fec_enet_private *fep = netdev_priv(ndev);
4      int entries_free;
5      unsigned short queue;
6      struct fec_enet_priv_tx_q *txq;
7      struct netdev_queue *nq;
8      int ret;
9 
10     queue = skb_get_queue_mapping(skb);
11     txq = fep->tx_queue[queue];
12     nq = netdev_get_tx_queue(ndev, queue);
13
14     if (skb_is_gso(skb))
15       ret = fec_enet_txq_submit_tso(txq, skb, ndev);
16    else
17      ret = fec_enet_txq_submit_skb(txq, skb, ndev);
18    if (ret)
19      return ret;
20
21    entries_free = fec_enet_get_free_txdesc_num(fep, txq);
22    if (entries_free <= txq->tx_stop_threshold)
23         netif_tx_stop_queue(nq);
24
25    return NETDEV_TX_OK;
26 }

        此函数的参数第一个参数 skb 就是上层应用传递下来的要发送的网络数据,第二个参数 ndev 就是要发送数据的设备。

        第 14 行,判断 skb 是否为 GSO(Generic Segmentation Offload),如果是 GSO 的话就通过 fec_enet_txq_submit_tso 函数发送,如果不是的话就通过 fec_enet_txq_submit_skb 发送。这里简 单讲一下 TSO 和 GSO:

       TSO:全称是 TCP Segmentation Offload,利用网卡对大数据包进行自动分段处理,降低 CPU 负载。

      GSO:全称是 Generic Segmentation Offload,在发送数据之前先检查一下网卡是否支持 TSO, 如果支持的话就让网卡分段,不过不支持的话就由协议栈进行分段处理,分段处理完成以后再 交给网卡去发送。

     第 21 行,通过 fec_enet_get_free_txdesc_num 函数获取剩余的发送描述符数量。

     第 23 行,如果剩余的发送描述符的数量小于设置的阈值(tx_stop_threshold)的话就调用函数 netif_tx_stop_queue 来暂停发送,通过暂停发送来通知应用层停止向网络发送 skb,发送中断中 会重新开启的。

fec_enet_interrupt 中断服务函数简析

   前面说了 I.MX6ULL 的网络数据接收采用 NAPI 框架,所以肯定要用到中断。fec_probe 函 数会初始化网络中断,中断服务函数为 fec_enet_interrupt,函数内容如下:

1 static irqreturn_t fec_enet_interrupt(int irq, void *dev_id)
2 {
3      struct net_device *ndev = dev_id;
4      struct fec_enet_private *fep = netdev_priv(ndev);
5      uint int_events;
6      irqreturn_t ret = IRQ_NONE;
7 
8      int_events = readl(fep->hwp + FEC_IEVENT);
9      writel(int_events, fep->hwp + FEC_IEVENT);
10     fec_enet_collect_events(fep, int_events);
11
12     if ((fep->work_tx || fep->work_rx) && fep->link) {
13        ret = IRQ_HANDLED;
14
15           if (napi_schedule_prep(&fep->napi)) {
16             /* Disable the NAPI interrupts */
17            writel(FEC_ENET_MII, fep->hwp + FEC_IMASK);
18             __napi_schedule(&fep->napi);
19           }
20     }
21
22    if (int_events & FEC_ENET_MII) {
23       ret = IRQ_HANDLED;
24      complete(&fep->mdio_done);
25    }
26
27   if (fep->ptp_clock)
28   fec_ptp_check_pps_event(fep);
29
30   return ret;
31 }

     可以看出中断服务函数非常短!而且也没有见到有关数据接收的处理过程,那是因为 I.MX6ULL 的网络驱动使用了 NAPI,具体的网络数据收发是在 NAPI 的 poll 函数中完成的,中断里面只需要进行 napi 调度即可,这个就是中断的上半部和下半部处理机制。

    第 8 行,读取 NENT 的中断状态寄存器 EIR,获取中断状态.

    第 9 行,将第 8 行获取到的中断状态值又写入 EIR 寄存器,用于清除中断状态寄存器。

    第 10 行,调用 fec_enet_collect_events 函数统计中断信息,也就是统计都发生了哪些中断。 fep 中成员变量 work_tx 和 work_rx 的 bit0、bit1 和 bit2 用来做不同的标记,work_rx 的 bit2 表 示接收到数据帧,work_tx 的 bit2 表示发送完数据帧。

    第 15 行,调用 napi_schedule_prep 函数检查 NAPI 是否可以进行调度。

    第 17 行,如果使能了相关中断就要先关闭这些中断,向 EIMR 寄存器的 bit23 写 1 即可关 闭相关中断。

   第 18 行,调用__napi_schedule 函数来启动 NAPI 调度,这个时候 napi 的 poll 函数就会执 行,在本网络驱动中就是 fec_enet_rx_napi 函数.

fec_enet_rx_napi中断服务函数简析

     fec_enet_init 函数初始化网络的时候会调用 netif_napi_add 来设置 NAPI 的 poll 函数为 fec_enet_rx_napi,函数内容如下:

1 static int fec_enet_rx_napi(struct napi_struct *napi, int budget)
2 {
3        struct net_device *ndev = napi->dev;
4        struct fec_enet_private *fep = netdev_priv(ndev);
5        int pkts;
6 
7        pkts = fec_enet_rx(ndev, budget);
8 
9        fec_enet_tx(ndev);
10
11       if (pkts < budget) {
12               napi_complete(napi);
13            writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
14        }
15       return pkts;
16 }

第 7 行,调用 fec_enet_rx 函数进行真正的数据接收。

第 9 行,调用 fec_enet_tx 函数进行数据发送。

第 12 行,调用 napi_complete 函数来宣布一次轮询结束。

第 13 行,设置 ENET 的 EIMR 寄存器,重新使能中断。

Linux 内核 PHY 子系统与 MDIO 总线简析

      上一小节在讲解 MDIO 总线的时候讲过,注册 MDIO 总线的时候也会向内核注册 PHY 设 备,本节我们就来简单了解一下 PHY 子系统。PHY 子系统就是用于 PHY 设备相关内容的,分 为 PHY 设备和 PHY 驱动,和 platform 总线一样,PHY 子系统也是一个设备、总线和驱动模型。

1、PHY 设备

    首先看一下 PHY 设备,Linux 内核使用 phy_device 结构体来表示 PHY 设备,结构体定义 在 include/linux/phy.h,结构体内容如下:

1 struct phy_device {
2      /* Information about the PHY type */
3      /* And management functions */
4      struct phy_driver *drv; /* PHY 设备驱动 */
5      struct mii_bus *bus; /* 对应的 MII 总线 */
6      struct device dev; /* 设备文件 */
7      u32 phy_id; /* PHY ID */
8 
9      struct phy_c45_device_ids c45_ids;
10     bool is_c45; 
11     bool is_internal;
12     bool has_fixups;
13     bool suspended;
14
15     enum phy_state state; /* PHY 状态 */
16     u32 dev_flags;
17     phy_interface_t interface; /* PHY 接口 */
18
19     /* Bus address of the PHY (0-31) */
20     int addr; /* PHY 地址(0~31) */
21
22     /*
23      * forced speed & duplex (no autoneg)
24      * partner speed & duplex & pause (autoneg)
25     */
26     int speed; /* 速度 */
27     int duplex; /* 双共模式 */
28     int pause; 
29     int asym_pause;
30
31     /* The most recently read link state */
32     int link;
33
34     /* Enabled Interrupts */
35     u32 interrupts; /* 中断使能标志 */
36
37     /* Union of PHY and Attached devices' supported modes */
38     /* See mii.h for more info */
39     u32 supported;
40     u32 advertising;
41     u32 lp_advertising;
42     int autoneg;
43     int link_timeout;
44
45     /*
46      * Interrupt number for this PHY
47      * -1 means no interrupt
48     */
49    int irq; /* 中断号 */
50
51    /* private data pointer */
52    /* For use by PHYs to maintain extra state */
53    void *priv; /* 私有数据 */
54
55    /* Interrupt and Polling infrastructure */
56    struct work_struct phy_queue;
57    struct delayed_work state_queue;
58    atomic_t irq_disable;
59    struct mutex lock;
60    struct net_device *attached_dev; /* PHY 芯片对应的网络设备 */
61    void (*adjust_link)(struct net_device *dev);
62 };

      一个 PHY 设备对应一个 phy_device 实例,然后需要向 Linux 内核注册这个实例。使用 phy_device_register 函数完成 PHY 设备的注册,函数原型如下:

     int phy_device_register(struct phy_device *phy)

     函数参数和返回值含义如下:

     phy:需要注册的 PHY 设备。

     返回值:0 成功,负值 失败。

    PHY 设备的注册过程一般是先调用 get_phy_device 函数获取 PHY 设备,此函数内容如下:

1 struct phy_device *get_phy_device(struct mii_bus *bus, int addr,
                                   bool is_c45)
2 {
3      struct phy_c45_device_ids c45_ids = {0};
4      u32 phy_id = 0;
5      int r;
6 
7      r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);
8      if (r)
9      return ERR_PTR(r);
10
11     /* If the phy_id is mostly Fs, there is no device there */
12     if ((phy_id & 0x1fffffff) == 0x1fffffff)
13       return NULL;
14
15    return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);
16 }

      第 7 行,调用 get_phy_id 函数获取 PHY ID,也就是读取 PHY 芯片的那两个 ID 寄存器, 得到 PHY 芯片 ID 信息。

    第 15 行,调用 phy_device_create 函数创建 phy_device,此函数先申请 phy_device 内存,然 后初始化 phy_device 的各个结构体成员,最终返回创建好的 phy_device。phy_device_register 函 数注册的就是这个创建好的 phy_device。

    2、PHY 驱动

    PHY 驱动使用结构体 phy_driver 表示,结构体也定义在 include/linux/phy.h 文件中,结构体 内容如下(为了缩小篇幅,省略了注释部分):

1 struct phy_driver {
2     u32 phy_id; /* PHY ID */
3     char *name;
4     unsigned int phy_id_mask; /* PHY ID 掩码 */
5     u32 features;
6     u32 flags;
7     const void *driver_data;
8 
9     int (*soft_reset)(struct phy_device *phydev);
10    int (*config_init)(struct phy_device *phydev);
11    int (*probe)(struct phy_device *phydev);
12    int (*suspend)(struct phy_device *phydev);
13    int (*resume)(struct phy_device *phydev);
14    int (*config_aneg)(struct phy_device *phydev);
15    int (*aneg_done)(struct phy_device *phydev);
16    int (*read_status)(struct phy_device *phydev);
17    int (*ack_interrupt)(struct phy_device *phydev);
18    int (*config_intr)(struct phy_device *phydev);
19    int (*did_interrupt)(struct phy_device *phydev);
20    void (*remove)(struct phy_device *phydev);
21    int (*match_phy_device)(struct phy_device *phydev);
22    int (*ts_info)(struct phy_device *phydev,
                       struct ethtool_ts_info *ti);
23    int (*hwtstamp)(struct phy_device *phydev, struct ifreq *ifr);
24    bool (*rxtstamp)(struct phy_device *dev, struct sk_buff *skb,
                  int type);
25    void (*txtstamp)(struct phy_device *dev, struct sk_buff *skb,
                   int type);
26    int (*set_wol)(struct phy_device *dev,
                     struct ethtool_wolinfo *wol);
27    void (*get_wol)(struct phy_device *dev,
                              struct ethtool_wolinfo *wol);
28    void (*link_change_notify)(struct phy_device *dev);
29    int (*read_mmd_indirect)(struct phy_device *dev, int ptrad,
30                        int devnum, int regnum);
31    void (*write_mmd_indirect)(struct phy_device *dev, int ptrad,
32    int devnum, int regnum, u32 val);
33    int (*module_info)(struct phy_device *dev,
34    struct ethtool_modinfo *modinfo);
35    int (*module_eeprom)(struct phy_device *dev,
36    struct ethtool_eeprom *ee, u8 *data);
37
38     struct device_driver driver;
39 };

      可以看出,phy_driver 重点是大量的函数,编写 PHY 驱动的主要工作就是实现这些函数, 但是不一定全部实现,稍后我们会简单分析一下 Linux 内核通用 PHY 驱动。

     ①、注册 PHY 驱动

     phy_driver 结构体初始化完成以后,就需要向 Linux 内核注册,PHY 驱动的注册使用 phy_driver_register 函数,注册phy驱动的时候会设置驱动的总线为mdio_bus_type,也就是MDIO 总线,关于 MDIO 总线稍后会讲解,函数原型如下:

   int phy_driver_register(struct phy_driver *new_driver)

   函数参数和返回值含义如下:

   new_driver:需要注册的 PHY 驱动。

  返回值:0 成功,负值 失败。

 ②、连续注册多个 PHY 驱动

   一个厂家会生产多种 PHY 芯片,这些 PHY 芯片内部差别一般不大,如果一个个的去注册 驱动将会导致一堆重复的驱动文件,因此 Linux 内核提供了一个连续注册多个 PHY 驱动的函数 phy_drivers_register。首先准备一个 phy_driver 数组,一个数组元素就表示一个 PHY 芯片的驱 动,然后调用 phy_drivers_register 一次性注册整个数组中的所有驱动,函数原型如下:

  int phy_drivers_register(struct phy_driver *new_driver, int n)

  函数参数和返回值含义如下:

  new_driver:需要注册的多个 PHY 驱动数组。

  n:要注册的驱动数量。

  返回值:0 成功,负值 失败。

③、卸载 PHY 驱动

 卸载 PHY 驱动的话使用 phy_driver_unregister 函数,函数原型如下:

 void phy_driver_unregister(struct phy_driver *drv)

 函数参数和返回值含义如下:

 new_driver:需要卸载的 PHY 驱动。

  返回值:无。

3、MDIO 总线

     前面说了,PHY 子系统也是遵循设备、总线、驱动模型的,设备和驱动就是 phy_device 和 phy_driver。总线就是 MDIO 总线,因为 PHY 芯片是通过 MIDO 接口来管理的,MDIO 总线最 主要的工作就是匹配 PHY 设备和 PHY 驱动。在文件 drivers/net/phy/mdio_bus.c 中有如下定义:

1 struct bus_type mdio_bus_type = {
2      .name = "mdio_bus",
3      .match = mdio_bus_match,
4      .pm = MDIO_BUS_PM_OPS,
5      .dev_groups = mdio_dev_groups,
6 };

     示例代码定义了一个名为“mdio_bus_type”的总线,这个就是 MDIO 总线,总线 的名字为“mdio_bus”,重点是总线的匹配函数为 mdio_bus_match。此函数内容如下:

1 static int mdio_bus_match(struct device *dev,
                         struct device_driver *drv)
2 {
3       struct phy_device *phydev = to_phy_device(dev);
4       struct phy_driver *phydrv = to_phy_driver(drv);
5 
6       if (of_driver_match_device(dev, drv))
7              return 1;
8 
9       if (phydrv->match_phy_device)
10            return phydrv->match_phy_device(phydev);
11
12      return (phydrv->phy_id & phydrv->phy_id_mask) ==
13           (phydev->phy_id & phydrv->phy_id_mask);
14 }

     第 6 行,采用设备树的话先尝试使用 of_driver_match_device 来对设备和驱动进行匹配,也 就是检查 compatible 属性值与匹配表 of_match_table 里面的内容是否一致。但是对于本章教程 而言,并不是通过 of_driver_match_device 来完成 PHY 驱动和设备匹配的。

   第 9、10 行,检查 PHY 驱动有没有提供匹配函数 match_phy_device,如果有的话就直接调 用 PHY 驱动提供的匹配函数完成与设备的匹配。

   第 12、13 行,如果上面两个匹配方法都无效的话就使用最后一种,phy_driver 里面有两个 成员变量 phy_id 和 phy_id_mask,表示此驱动所匹配的 PHY 芯片 ID 以及 ID 掩码,PHY 驱动 编写人员需要给这两个成员变量赋值。phy_device 也有一个 phy_id 成员变量,表示此 PHY 芯 片的 ID,phy_device 里面的 phy_id 是在注册 PHY 设备的时候调用 get_phy_id 函数直接读取 PHY 芯片内部 ID 寄存器得到的!很明显 PHY 驱动和 PHY 设备中的 ID 要一样,这样才能匹配 起来。所以最后一种方法就是对比 PHY 驱动和 PHY 设备中的 phy_id 是否一致,这里需要与 PHY 驱动里面的 phy_id_mask 进行与运算,如果结果一致的话就说明驱动和设备匹配。

   如果 PHY 设备和 PHY 驱动匹配,那么就使用指定的 PHY 驱动,如果不匹配的话就使用 Linux 内核自带的通用 PHY 驱动。

4、通用 PHY 驱动

     前面多次提到Linux内核已经集成了通用PHY驱动,通用PHY驱动名字为“Generic PHY”, 打开 drivers/net/phy/phy_device.c,找到 phy_init 函数,内容如下:

1 static int __init phy_init(void)
2 {
3      int rc;
4 
5      rc = mdio_bus_init();
6      if (rc)
7         return rc;
8 
9      rc = phy_drivers_register(genphy_driver,
10                      ARRAY_SIZE(genphy_driver));
11     if (rc)
12        mdio_bus_exit();
13
14      return rc;
15 }

    phy_init 是整个 PHY 子系统的入口函数,第 9 行会调用 phy_drivers_register 函数向内核直 接注册一个通用 PHY 驱动:genphy_driver,也就是通用 PHY 驱动,也就是说 Linux 系统启动 以后默认就已经存在了通用 PHY 驱动。

    genphy_driver 是一个数组,有两个数组元素,表示有两个通用的 PHY 驱动,一个是针对 10/100/1000M 网络的,一个是针对10G 网络的。genphy_driver 定义在 drivers/net/phy/phy_device.c 里面,内容如下:

1 static struct phy_driver genphy_driver[] = {
2 {
3      .phy_id = 0xffffffff,
4      .phy_id_mask = 0xffffffff,
5      .name = "Generic PHY",
6      .soft_reset = genphy_soft_reset,
7      .config_init = genphy_config_init,
8      .features = PHY_GBIT_FEATURES | SUPPORTED_MII |
9            SUPPORTED_AUI | SUPPORTED_FIBRE |
10           SUPPORTED_BNC,
11     .config_aneg = genphy_config_aneg,
12     .aneg_done = genphy_aneg_done,
13     .read_status = genphy_read_status,
14     .suspend = genphy_suspend,
15     .resume = genphy_resume,
16     .driver = { .owner = THIS_MODULE, },
17 }, {
18     .phy_id = 0xffffffff,
19     .phy_id_mask = 0xffffffff,
20    .name = "Generic 10G PHY",
21    .soft_reset = gen10g_soft_reset,
22    .config_init = gen10g_config_init,
23    .features = 0,
24    .config_aneg = gen10g_config_aneg,
25    .read_status = gen10g_read_status,
26    .suspend = gen10g_suspend,
27    .resume = gen10g_resume,
28    .driver = {.owner = THIS_MODULE, },
29 } };

          genphy_driver 数组有两个元素,genphy_driver[0]为 10/100/1000M 的 PHY 驱动,名字为 “Generic PHY”,genphy_driver[1]为 10G 的 PHY 驱动,名字为“Generic 10G PHY”。注意,很 多另外编写的 PHY 驱动也会用到通用 PHY 驱动的一些函数,比如正点原子 ALPHA 开发板所 用的 LAN8720A 是 SMSC 公司的产品,此公司针对自家的所有 PHY 芯片编写了一个驱动文件 smsc.c,这驱动文件里面用到了大量的通用 PHY 驱动相关函数。

5、LAN8720A 驱动

    最 后 我 们 来 看 一 下 LAN8720A 的 Linux 驱 动 , LAN8720A 的 驱 动 文 件 为 drivers/net/phy/smsc.c,这个文件是 SMSC 针对自家的一些 PHY 芯片编写的驱动文件,其中就 包含了 LAN8720A 这个 PHY 芯片。默认情况下,LAN8720A 这个驱动是没有打开的,我们需 要配置 linux 内核,打开此驱动选项,配置路径如下:

Linux 网络驱动-MAC、PHY层驱动框架(三)

Linux 网络驱动-MAC、PHY层驱动框架(三)

        从示例代码 可以看出,smsc_phy_driver 还是支持了不少 SMSC 家的 PHY 芯片, 比如 LAN83C185、LAN8187、LAN8700 等等,当然了,肯定也包括了 LAN8720 系列,第 93~116 行就是 LAN8710/LAN8720 系列 PHY 驱动。

     第 94 行,PHY ID 为 0X0007C0F0

     第 95 行,PHY 的 ID 掩码为 0XFFFFFFF0,也就是前 28 位有效,在进行匹配的时候只需 要比较前 28 位,第 4 位不用比较。

     第 74 行,驱动名字为“SMSC LAN8710/LAN8720”,系统启动以后,打开网卡就会提示当 前 PHY 驱动名字为“SMSC LAN8710/LAN8720”。

    最后,第 118 行使用 module_phy_driver(本质是一个宏)来完成 smsc_phy_driver 的注册。 此驱动里面的成员函数有一些是 SMSC 自己编写的,有一些是直接用的通用 PHY 驱动的, 比如第 103 行的 genphy_config_aneg、第 112 行的 genphy_suspend 等。

网络驱动实验测试

    LAN8720 PHY 驱动测试

       首先肯定是驱动修改,这个已经在 37.4.3 小节做了详细的讲解,参考修改即可。系统启动 以后就会打印出当前 PHY 驱动名字为“SMSC LAN8710/LAN8720”

Linux 网络驱动-MAC、PHY层驱动框架(三)

 从图 可以看出,此时 PHY 驱动使用的是“SMSC LAN8710/8720”,当我们使用 ifconfig 命令打开网卡的时候也会提示当前 PHY 驱动名字。至于网络的测试就很简单了,大家 可以 ping 一下主机或者 ubuntu 的地址,如果能 ping 通就说明网络工作正常。文章来源地址https://www.toymoban.com/news/detail-486113.html

到了这里,关于Linux 网络驱动-MAC、PHY层驱动框架(三)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 018——红外遥控模块驱动开发(基于HS0038和I.MX6uLL)

    目录 一、 模块介绍 1.1 简介 1.2 协议 二、 驱动代码 三、 应用代码 四、 实验 五、 程序优化         红外遥控被广泛应用于家用电器、工业控制和智能仪器系统中,像我们熟知的有电视机盒子遥控器、空调遥控器。红外遥控器系统分为发送端和接收端,如图下图所示。

    2024年04月16日
    浏览(43)
  • 017——DS18B20驱动开发(基于I.MX6uLL)

    目录 一、 模块介绍 1.1 简介 1.2 主要特点 1.3 存储器介绍 1.4 时序 1.5 命令 1.5.1 命令大全    1.5.2 命令使用 1.5.3 使用示例 1.6 原理图 二、 驱动程序 三、 应用程序 四、 测试         DS18B20 温度传感器具有线路简单、体积小的特点,用来测量温度非常简单,在一根通信线上

    2024年04月12日
    浏览(47)
  • I.MX6ull UART

     一 简介 UART 全称叫做串行接口,通常也叫做 COM 接口,串行接口指的是数据一个一个的顺序传输,通信线路简单。使用两条线即可实现双向通信,一条用于发送,一条用于接收。串口通 信距离远 ,但是速 度相对会低 ,串口是一种很常用的工业接口。I.MX6U 自带的 UART 外设

    2024年02月09日
    浏览(40)
  • i.MX6ULL移植NXP官方Linux内核imx_5.4.47_2.2.0

    系统:Ubuntu18.04 参考资料:百问网 IMX6ULL开发板(从零移植篇-预览版)-V0.1,正点原子驱动开发指南 开发板:100ask i.MX6ULL PRO 交叉编译工具链的获取就不写了 打开 .bashrc 文件。 vi ~/.bashrc 。在该文件最后面添加如下(根据自己的交叉编译工具链) (1)直接从官网下载,非常慢而

    2024年02月12日
    浏览(57)
  • 【Linux 裸机篇(五)】I.MX6ULL BSP工程管理下的 Makefile编写、链接脚本

    文件夹 描述 bsp 存放驱动文件 imx6ul 存放跟芯片有关的文件,比如 NXP 官方的 SDK库文件 obj 存放编译生成的.o 文件 project 存放 start.S 和 main.c 文件,也就是应用文件 行 描述 1~7 定义了一些变量,除了第 2 行以外其它的都是跟编译器有关的,如果使用其它编译器的话只需要修改第

    2023年04月20日
    浏览(44)
  • I.MX6ull EPIT定时器

    一 简介 EPIT定时器是一种增强的周期中断定时器,完成周期性中断定时的功能。 具有以下特点  EPIT定时器是一个32位的定时器  时钟源可选的向下计数器  EPIT 共有 3 个时钟源可选择,ipg_clk、ipg_clk_32k 和 ipg_clk_highfreq  当计数值和比较值相等的时候产生中断  12 位分频器 对

    2024年02月08日
    浏览(48)
  • I.MX6ULL开发笔记(二)——硬件外设操作

    在文章http://t.csdnimg.cn/EGWt9中有介绍Linux下文件目录,那么在Linux系统下,RGB灯也是一个设备,所以我们需要到 /sys 目录下去操作这个设备。 之后,我们进入到 class 目录,这里挂载着开发板上的外设: 在这里就能看到熟悉的硬件接口了,那么我们进入到 leds 的目录下: 可以看

    2024年01月24日
    浏览(51)
  • I.MX6ull GPT高精度定时器

    一 简介 GPT的全称是General Purpose Timer,它是一个32位的向上的定时器, GPT 定时器也可以跟一个值进行比较,当计数器值和这个值相等的话就发生比较事件,产生比较中断。GPT 定时器有一个 12 位的分频器,可以对 GPT 定时器的时钟源进行分频。 分析方式 同EPTI  它具有以下特点

    2024年02月08日
    浏览(49)
  • 使用一根网线,让Ubuntu和正点原子I.MX6ULL开发板互相ping通

    准备一根网线即可 2.1 找根网线将I.MX6ULL和电脑连起来 2.2 让I.MX6ULL通电运行起来,我这里使用的是正点原子版本的内核、 2.3 进入电脑的网络连接后,按照如下步骤操作 2.4 将ip地址、子网掩码、默认网关设置如下,= 注意,子网掩码一定要是255.255.255.0 IP地址推荐使用192.168.5.

    2024年02月19日
    浏览(48)
  • I.MX6ULL_Linux_驱动篇(57)linux Regmap API驱动

    我们在前面学习 I2C 和 SPI 驱动的时候,针对 I2C 和 SPI 设备寄存器的操作都是通过相关的 API 函数进行操作的。这样 Linux 内核中就会充斥着大量的重复、冗余代码,但是这些本质上都是对寄存器的操作,所以为了方便内核开发人员统一访问 I2C/SPI 设备的时候,为此引入了 Reg

    2024年03月26日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包