Linux ネットワーク ドライバー - MAC、PHY レイヤー ドライバー フレームワーク (3)

I.MX6ULL ネットワーク周辺機器デバイス ツリー

   I.MX6ULL には 2 つの 10/100M ネットワーク MAC ペリフェラルがあるため、I.MX6ULL ネットワーク ドライバーは主にこれら 2 つのネットワーク MAC ペリフェラルのドライバーになります。これら 2 つのペリフェラルのドライバーは同じです。そのうちの 1 つを分析しましょう。まず、デバイス ツリーでなければなりません。NXP の I.MX シリーズ SOC ネットワーク バインディング ドキュメントは Documentation/devicetree/bindings/net/fsl-fec.txt です。これはバインディング ドキュメントには、I.MX シリーズ SOC ネットワーク デバイス ツリー ノードの要件が記載されています。

①. 必要な属性

  互換性: これは必ず必要です。通常は「fsl,-fec」です。たとえば、I.MX6ULL の互換性属性は「fsl,imx6ul-fec」および「fsl,imx6q-fec」です。

 reg: SOC ネットワーク ペリフェラル レジスタのアドレス範囲。

 割り込み: ネットワークの中断。

 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 ノードのコンテナとして使用されます。つまり、PHY 関連の属性を指定します。 mdio 子ノードの下にある情報 詳細については、PHY バインディング ドキュメント Documentation/devicetree/bindings/net/phy.txt を参照してください。

  PHY ノードの関連する属性は次のとおりです。

  割り込み: 割り込み属性。必須ではない場合があります。

  interrupt-parent: 割り込みコントローラ ハンドル。必須ではありません。

  reg: PHY チップ アドレス、必須です!

  互換性: 互換性リスト。通常は「ethernet-phy-ieee802.3-c22」または「ethernet-phy-ieee802.3-c45」で、それぞれ IEEE802.3 の 22 クラスタと 45 クラスタに対応します。デフォルトは 22 クラスタです。他の値に設定することもできます。PHY の ID が不明な場合は、互換属性を「ethernet-phy-idAAAA.BBBB」に設定できます。AAAA と BBBB の意味は次のとおりです。

 AAAA: PHY の 16 ビット ID レジスタ 1 の値、つまり OUI のビット 3 ~ 18 を 16 進形式で示します。

 BBBB: PHY の 16 ビット ID レジスタ 2 の値、つまり OUI のビット 19 ~ 24 の 16 進数形式。

 max-speed: PHY でサポートされる最高速度 (10、100、1000 など)。

imx6ull.dtsi を開き、図に示すように、I.MX6ULL の 2 つのネットワーク ペリフェラル ノードを見つけます。

 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 ノード情報が含まれます。ここには、ethphy0 と ethphy1 という 2 つの PHY サブノードがあり、それぞれ 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 情報です。これらが 2 つの部分に分かれている理由は、ENET1 のリセット ピンが GPIO5_IO07 であり、GPIO5_IO07 の対応するピンが SNVS_TAMPER7 であり、iomuxc_snvs ノードの下に配置する必要があるためです。 , そのため、2 つのパートに分かれています。16 行目と 17 行目に注目してください。この 2 行は GPIO1_IO07 と GPIO1_IO06 を ENET2 の MDC と MDIO として設定しています。なぜ ENET1 の MDC と MDIO として設定しないのかと疑問に思われるかもしれません。筆者が実際に測定したところ、ネットワークポートを 2 つ開いている場合、GPIO1_IO07 と GPIO1_IO06 を ENET1 の MDC と MDIO に設定すると、ネットワークが異常動作するようになりました。前述したように、1 つの MDIO インターフェイスで 32 個の PHY を管理できるため、ENET2 の MDC と MDIO を設定した後、ENET1 の PHY チップも管理できます。

I.MX6ULL ネットワーク ドライバーのソース コード分析

1. fec_probe 関数の簡単な分析

   I.MX6ULL の場合、ネットワーク ドライバーは主に 2 つの部分に分かれています: I.MX6ULL ネットワーク ペリフェラル ドライバーと PHY チップ ドライバーです。ネットワーク ペリフェラル ドライバーは NXP によって作成され、PHY チップには一般的なドライバー ファイルが含まれています。一部の PHY チップ メーカーは、独自のターゲットもターゲットにする チップに対応する PHY ドライバーを作成します。一般に、SOC 内蔵ネットワーク MAC + 外部 PHY チップのソリューションではドライバーを作成する必要はなく、基本的に直接使用できます。ただし、学習するには、特定のネットワーク ドライバーの作成プロセスを簡単に分析する必要があります。

   まず I.MX6ULL のネットワーク コントローラー ドライバーを見てみましょう サンプル コード 69.4.1.1 を見ると、compatibility 属性には「fsl, imx6ul-fec」と「fsl, imx6q-」という 2 つの値があることがわかります。 fec」を Linux 経由で実行します。カーネル ソース コード内でこれら 2 つの文字列を検索して、対応するドライバー ファイルを見つけます。ドライバー ファイルは 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」の2つの属性値、つまり送信サイズを取得します。キューと受信キュー、デバイス ツリー では両方のプロパティが 1 に設定されます。

  17 行目では、alloc_etherdev_mqs 関数を使用して net_device を適用します。

  行 25、net_device のプライベート データ メモリの最初のアドレスを取得します。net_device のプライベート データは、I.MX6ULL ネットワーク デバイス構造を格納するために使用されます。この構造は fec_enet_private です。

 行 30、「fep->」で始まる後続のコード行はすべて、ネットワーク デバイス構造体の各メンバー変数を初期化するためのものです。構造体のタイプは fec_enet_private で、この構造体は 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」属性が存在するか確認し、存在する場合はマジックパケットがあることを意味します レジストリ、レジストリはマジックパッケージをサポートしています。

62 行目では、I.MX6ULL ネットワーク ペリフェラルに対応するデバイス ノードを指定して PHY を取得する「phy-handle」属性の値を取得します。デバイス ツリーの 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 チップで構成されていることがわかります。1 つの MDIO インターフェイスを通じて複数の PHY チップを構成でき、異なる PHY チップは異なるアドレスで区別されます。Punctual Atom ALPHA 開発ボードの ENET の PHY アドレスは 0X00、ENET2 の PHY アドレスは 0X01 です。これら 2 つの PHY アドレスは、デバイス ツリーを通じて Linux システムに伝える必要があり、次の 2 行のコードの @ の後ろの値が 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 か、PHY を保存します。 IMX6ULL は 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 メンバーを初期化します。

    net_device の netdev_ops 変数と ethtool_ops 変数は、それぞれ fec_netdev_ops と fec_enet_ethtool_ops に初期化されます。fec_enet_init 関数は、netif_napi_add を呼び出してポーリング関数を設定し、NXP によって正式に作成されたネットワーク ドライバーが NAPI 互換ドライバーであることを示します。

     napi の例は、netif_napi_add 関数を通じてネットワーク カードに追加されます。NAPI ドライバーは、受信データをポーリングして処理するためのポーリング関数を提供する必要があります。ここでのポーリング関数は、fec_enet_rx_napi です。この関数は、ネットワーク データを分析するときに詳細に説明されます受信および処理プロセスは後で行います。

  最後に、fec_enet_init 関数は、IMX6ULL ネットワーク周辺機器関連のハードウェア レジスタを設定します。

 146 行目、デバイス ツリーから割り込み番号を取得します。

 153 行目、割り込みを申請します。割り込み処理関数は fec_enet_interrupt です。重要です。この関数は後で分析します。

161 行目では、割り込みをウェイクアップするための属性「fsl,wakeup_irq」の値をデバイス ツリーから取得します。

行 167、初期化完了完了。ある実行ユニットが別の実行ユニットが何かの実行を完了するのを待つために使用されます。

行 168 の関数 fec_enet_mii_init は、MII/RMII インターフェイスの初期化を完了します。

   mii_bus の 2 つのメンバー変数 read および write は、それぞれ PHY レジスタの読み取り / 書き込みを行うための操作関数です。ここでは、fec_enet_mdio_read および fec_enet_mdio_write として設定されています。これら 2 つの関数は、I.MX シリーズ SOC が PHY 内部レジスタを読み取りおよび書き込みするための関数です。 PHY レジスタの読み取りまたは設定は、これら 2 つの MDIO バス機能を通じて行われます。fec_enet_mii_init 関数は、最終的に MIDO バスを Linux カーネルに登録します。

 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 }

     サンプル コード コードの最初の行は、デバイス ツリーから 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 の 2 つのラインに分かれています。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関数で、この2つの関数は読み出し/一部PHYチップの動作関数であり、SOCが異なればMDIOの主制御部分が異なるため、ドライバライタで書き込む必要があります。fec_probe 関数を分析するときにすでに述べたように、fec_probe 関数は fec_enet_mii_init 関数を呼び出して、mii_bus での読み取りおよび書き込み関数の初期化を含む MII インターフェイスの初期化を完了します。最後に、初期化された mii_bus は of_mdiobus_register または mdiobus_register 関数を通じて 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 関数を呼び出して、mdio バスを Linux カーネルに登録します。

25行目は、サンプルコード69.4.1.2の2つの子ノード「ethphy0:ethernet-phy@0」と「ethphy1:ethernet-phy@1」など、mdioノード配下のすべての子ノードをポーリングし、これら2つの子ノードを記述しています。 PHYチップ情報。

26行目では、デバイスツリーの子ノードのPHYアドレス、つまり0である2つの子ノードethphy0:ethernet-phy@0と「ethphy1:ethernet-phy@1」に対応するPHYチップアドレスを抽出しています。と 1 をそれぞれ指定します。

32 行目では、of_mdiobus_register_phy 関数を呼び出して、phy を Linux カーネルに登録します。

簡単にまとめると、of_mdiobus_register 関数には 2 つの主な機能があります。1 つは mdiobus_register 関数を通じて Linux カーネルに mdio バスを登録することで、もう 1 つは of_mdiobus_register_phy 関数を通じて PHY をカーネルに登録することです。

次に、of_mdiobus_register_phy 関数を簡単に分析して、PHY デバイスを Linux カーネルに登録する方法を確認します。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_compatibilityを使用して、PHYノードの互換性属性が「ethernet-phy-ieee802.3-c45」であるかどうかを確認し、そうであれば他の処理を行います。この章で設定した互換性属性は「ethernet-phy」です。 -ieee802 .3-c22」。

    15 行目では、get_phy_device 関数を呼び出して PHY デバイスを取得します。この関数は、phy_device_create を呼び出して phy_device デバイスを作成して戻ります。

   19 行目は PHY チップの割り込み情報を取得するためのものですが、この章では使用しません。

   36 行目では、phy_device_register 関数を呼び出して、PHY デバイスを Linux カーネルに登録します。

    上記の分析から、MDIO バスを Linux カーネルに登録すると、同時に PHY デバイスも Linux カーネルに登録されることがわかります。プロセスは次の図に示されています。

 MIDO バスを登録するときは、デバイス ツリーから PHY デバイスを検索し、phy_device_register 関数を通じて PHY デバイスをカーネルに登録します。次に、PHY サブシステムについて学びましょう。

3. fec_drv_remove 関数の簡単な分析

   fec_drv_remove 関数は、I.MX6ULL ネットワークドライバーをアンインストールするときに実行されます。関数の内容は次のとおりです。

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を呼び出しており、この関数では送信キューと受信キューバッファの申請を実現するために、fec_enet_alloc_rxq_buffersとfec_enet_alloc_txq_buffersの2つの関数が呼び出されています。

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 のネットワークデータ送信は、上位層から渡された sk_buff 内のデータをハードウェア経由で送信する fec_enet_start_xmit 関数によって完了します。関数のソースコードは次のとおりです。

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 は上位層アプリケーションから送信されるネットワーク データ、2 番目のパラメータ ndev はデータを送信するデバイスです。

        14行目、skbがGSO(Generic Segmentation Offload)かどうかを判断し、GSOであればfec_enet_txq_submit_tso関数で送信し、そうでない場合はfec_enet_txq_submit_skbで送信します。TSO と GSO について簡単に説明します。

       TSO: 正式名は TCP セグメンテーション オフロードで、ネットワーク カードを使用して大きなデータ パケットを自動的にセグメント化し、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 のポーリング機能で完了し、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 レジスタのビット 23 に 1 を書き込むことで関連する割り込みを閉じることができます。

   18行目で__napi_schedule関数を呼び出してNAPIスケジューリングを開始しますが、このときnapiのpoll関数(このネットワークドライバのfec_enet_rx_napi関数)が実行されます。

fec_enet_rx_napi 割り込みサービス関数の簡単な分析

     fec_enet_init 関数はネットワークを初期化するときに netif_napi_add を呼び出して、NAPI ポーリング関数を 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 デバイスと PHY ドライバーに分かれています。プラットフォーム バスと同様に、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 チップの 2 つの ID レジスタを読み取って、PHY チップ ID 情報を取得します。

    15 行目では、phy_device_create 関数を呼び出して phy_device を作成します。この関数は、最初に phy_device メモリに適用し、次に phy_device の各構造体メンバーを初期化し、最後に作成された phy_device を返します。phy_device_register 関数は、作成した phy_device を登録します。

    2.PHYドライバー

    PHY ドライバーは、include/linux/phy.h ファイルにも定義されている構造体 phy_driver で表され、構造体の内容は次のとおりです (スペースを減らすため、コメント部分は省略しています)。

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 バスである mdio_bus_type に設定されます。 MDIO バスについては後で説明しますが、関数のプロトタイプは次のとおりです。

   int phy_driver_register(struct phy_driver *new_driver)

   関数のパラメータと戻り値は次の意味を持ちます。

   new_driver: 登録する必要がある PHY ドライバー。

  戻り値: 成功の場合は 0、失敗の場合は負の値。

 ②. 複数のPHYドライバーを連続登録

   メーカーはさまざまな PHY チップを製造しますが、これらの PHY チップの内部差異は一般的に大きくありません。ドライバーを 1 つずつ登録すると、重複したドライバー ファイルが大量に生成されるため、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 です)。PHY チップは MIDO インターフェイスを通じて管理され、MDIO バスの主な仕事は PHY デバイスと PHY ドライバーを一致させることであるため、このバスは MDIO バスです。ファイル 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 バスである「mdio_bus_type」という名前のバスを定義しています。バスの名前は「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 を使用してデバイスとドライバーを照合します。つまり、互換性のある属性値が of_match_table の照合テーブルの内容と一致するかどうかを確認します。ただし、この章のチュートリアルでは、PHY ドライバーとデバイスのマッチングは of_driver_match_device を通じて行われません。

   9行目と10行目では、PHYドライバがマッチング関数match_phy_deviceを提供しているかどうかを確認し、提供している場合はPHYドライバが提供しているマッチング関数を直接呼び出してデバイスとのマッチングを完了させています。

   12 行目と 13 行目、上記の 2 つの照合方法が無効な場合は、最後の方を使用します。phy_driver には 2 つのメンバー変数 phy_id と phy_id_mask があり、このドライバーによって照合された PHY チップ ID と ID マスクを示します。PHY ドライバー ライターこれら 2 つのメンバー変数には値を割り当てる必要があります。phy_device には PHY チップの ID を示すメンバー変数 phy_id もあり、phy_device の phy_id は、PHY デバイスの登録時に get_phy_id 関数を呼び出して PHY チップの内部 ID レジスタを直接読み取ることで取得されます。当然のことながら、PHY ドライバーと PHY デバイスの ID が一致するには、これらの ID が同じである必要があります。したがって、最後の方法は、PHY ドライバーの phy_id が PHY デバイスの phy_id と一致するかどうかを比較することです。ここでは、PHY ドライバーの phy_id_mask との AND 演算を実行する必要があります。結果が一致する場合、それは、ドライバーとデバイスが一致しています。

   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 ドライバであり、一般的な PHY ドライバがデフォルトですでに存在していることを意味しますLinux システムの起動後。

    genphy_driver は 2 つの配列要素を持つ配列で、10/100/1000M ネットワーク用と 10G ネットワーク用の 2 つの一般的な PHY ドライバーがあることを示します。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 配列には 2 つの要素があり、genphy_driver[0] は「Generic PHY」という名前の 10/100/1000M PHY ドライバー、genphy_driver[1] は「Generic 10G PHY」という名前の 10G PHY ドライバーです。追加で作成された PHY ドライバの多くは、一般的な PHY ドライバの一部の機能も使用することに注意してください。たとえば、Atom ALPHA 開発ボードで使用される LAN8720A は SMSC の製品です。同社はすべての独自のドライバ ファイル smsc.c を作成しました。 PHY チップ。このドライバー ファイルでは、多数の一般的な PHY ドライバー関連関数が使用されています。

5. LAN8720Aドライバー

    最後に、LAN8720A の Linux ドライバーを見てみましょう。LAN8720A のドライバー ファイルは、drivers/net/phy/smsc.c です。このファイルは、SMSC によって独自の PHY チップの一部用に書かれたドライバー ファイルであり、PHY が含まれていますLAN8720Aのチップ。デフォルトでは、LAN8720A ドライバーは有効になっていません。このドライバー オプションを有効にするように Linux カーネルを設定する必要があります。設定パスは次のとおりです:

        サンプル コードから、smsc_phy_driver が LAN83C185、LAN8187、LAN8700 などの多くの SMSC PHY チップをサポートしていることがわかります。もちろん、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」として出力されます。

 図から、現時点では PHY ドライバーが「SMSC LAN8710/8720」を使用していることがわかります。ifconfig コマンドを使用してネットワーク カードを開くと、現在の PHY ドライバー名も要求されます。ネットワークテストに関しては非常に簡単で、ホストまたはubuntuのアドレスにpingを実行することができ、pingが実行できれば、ネットワークが正常に動作していることを意味します。

おすすめ

転載: blog.csdn.net/wanglei_11/article/details/130010058
おすすめ